feat(sidebar): add per-folder color swatch with picker and neutral conversation rail
- Add `color` column to folder table with migration backfill and hash-based assignment on folder creation - Expose `update_folder_color` via Tauri command and `/update_folder_color` HTTP route - Render a color swatch before each folder name in the sidebar header; offer a 10-color palette (9 hues plus a theme-aware foreground sentinel) through the folder context menu - Show the folder header "new conversation" button only on hover - Drop the expanded-state tint on folder name and count badge; use a fixed neutral rail color for conversation items
This commit is contained in:
@@ -567,6 +567,19 @@ pub async fn reorder_folders(
|
|||||||
.map_err(AppCommandError::from)
|
.map_err(AppCommandError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tauri-runtime")]
|
||||||
|
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
||||||
|
pub async fn update_folder_color(
|
||||||
|
db: tauri::State<'_, AppDatabase>,
|
||||||
|
folder_id: i32,
|
||||||
|
color: String,
|
||||||
|
) -> Result<crate::models::FolderDetail, AppCommandError> {
|
||||||
|
folder_service::update_folder_color(&db.conn, folder_id, &color)
|
||||||
|
.await
|
||||||
|
.map_err(AppCommandError::from)?
|
||||||
|
.ok_or_else(|| AppCommandError::not_found("Folder not found"))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
#[cfg_attr(feature = "tauri-runtime", tauri::command)]
|
||||||
pub async fn create_folder_directory(path: String) -> Result<(), AppCommandError> {
|
pub async fn create_folder_directory(path: String) -> Result<(), AppCommandError> {
|
||||||
std::fs::create_dir_all(&path).map_err(AppCommandError::io)
|
std::fs::create_dir_all(&path).map_err(AppCommandError::io)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub struct Model {
|
|||||||
pub deleted_at: Option<DateTimeUtc>,
|
pub deleted_at: Option<DateTimeUtc>,
|
||||||
pub is_open: bool,
|
pub is_open: bool,
|
||||||
pub sort_order: i32,
|
pub sort_order: i32,
|
||||||
|
pub color: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|||||||
60
src-tauri/src/db/migration/m20260424_000001_folder_color.rs
Normal file
60
src-tauri/src/db/migration/m20260424_000001_folder_color.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
use sea_orm_migration::sea_orm::{ConnectionTrait, DbBackend, Statement};
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(Folder::Table)
|
||||||
|
.add_column(
|
||||||
|
ColumnDef::new(Folder::Color)
|
||||||
|
.string()
|
||||||
|
.not_null()
|
||||||
|
.default("#22c55e"),
|
||||||
|
)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Backfill existing rows with a palette color chosen by (id - 1) mod N,
|
||||||
|
// so existing workspaces get visually distinct swatches after migration.
|
||||||
|
let conn = manager.get_connection();
|
||||||
|
let sql = "UPDATE folder SET color = CASE ((id - 1) % 9) \
|
||||||
|
WHEN 0 THEN '#ef4444' \
|
||||||
|
WHEN 1 THEN '#f97316' \
|
||||||
|
WHEN 2 THEN '#eab308' \
|
||||||
|
WHEN 3 THEN '#84cc16' \
|
||||||
|
WHEN 4 THEN '#22c55e' \
|
||||||
|
WHEN 5 THEN '#06b6d4' \
|
||||||
|
WHEN 6 THEN '#8b5cf6' \
|
||||||
|
WHEN 7 THEN '#d946ef' \
|
||||||
|
WHEN 8 THEN '#ec4899' \
|
||||||
|
END";
|
||||||
|
conn.execute(Statement::from_string(DbBackend::Sqlite, sql.to_string()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.alter_table(
|
||||||
|
Table::alter()
|
||||||
|
.table(Folder::Table)
|
||||||
|
.drop_column(Folder::Color)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DeriveIden)]
|
||||||
|
enum Folder {
|
||||||
|
Table,
|
||||||
|
Color,
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ mod m20260406_000001_agent_setting_model_provider;
|
|||||||
mod m20260420_000001_opened_tabs;
|
mod m20260420_000001_opened_tabs;
|
||||||
mod m20260422_000001_folder_sort_order;
|
mod m20260422_000001_folder_sort_order;
|
||||||
mod m20260423_000001_drop_folder_parent_branch;
|
mod m20260423_000001_drop_folder_parent_branch;
|
||||||
|
mod m20260424_000001_folder_color;
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -30,6 +31,7 @@ impl MigratorTrait for Migrator {
|
|||||||
Box::new(m20260420_000001_opened_tabs::Migration),
|
Box::new(m20260420_000001_opened_tabs::Migration),
|
||||||
Box::new(m20260422_000001_folder_sort_order::Migration),
|
Box::new(m20260422_000001_folder_sort_order::Migration),
|
||||||
Box::new(m20260423_000001_drop_folder_parent_branch::Migration),
|
Box::new(m20260423_000001_drop_folder_parent_branch::Migration),
|
||||||
|
Box::new(m20260424_000001_folder_color::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,35 @@ use crate::db::error::DbError;
|
|||||||
use crate::models::agent::AgentType;
|
use crate::models::agent::AgentType;
|
||||||
use crate::models::{FolderDetail, FolderHistoryEntry};
|
use crate::models::{FolderDetail, FolderHistoryEntry};
|
||||||
|
|
||||||
|
/// Palette kept in sync with the frontend swatch picker. Changes here must be
|
||||||
|
/// mirrored in `src/components/conversations/sidebar-conversation-list.tsx`.
|
||||||
|
/// `"foreground"` is a theme-aware sentinel the frontend resolves to
|
||||||
|
/// `var(--sidebar-foreground)`.
|
||||||
|
pub const FOLDER_COLOR_PALETTE: &[&str] = &[
|
||||||
|
"#ef4444",
|
||||||
|
"#f97316",
|
||||||
|
"#eab308",
|
||||||
|
"#84cc16",
|
||||||
|
"#22c55e",
|
||||||
|
"#06b6d4",
|
||||||
|
"#8b5cf6",
|
||||||
|
"#d946ef",
|
||||||
|
"#ec4899",
|
||||||
|
"foreground",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn pick_folder_color(folder_id: i32, folder_name: &str) -> String {
|
||||||
|
let mut name_hash: u32 = 0;
|
||||||
|
for c in folder_name.chars() {
|
||||||
|
name_hash = name_hash.wrapping_mul(31).wrapping_add(c as u32);
|
||||||
|
}
|
||||||
|
let combined = (folder_id as u32)
|
||||||
|
.wrapping_mul(2654435761)
|
||||||
|
.wrapping_add(name_hash);
|
||||||
|
let idx = (combined as usize) % FOLDER_COLOR_PALETTE.len();
|
||||||
|
FOLDER_COLOR_PALETTE[idx].to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn to_entry(m: folder::Model) -> FolderHistoryEntry {
|
fn to_entry(m: folder::Model) -> FolderHistoryEntry {
|
||||||
FolderHistoryEntry {
|
FolderHistoryEntry {
|
||||||
id: m.id,
|
id: m.id,
|
||||||
@@ -34,6 +63,7 @@ fn to_detail(m: folder::Model) -> FolderDetail {
|
|||||||
default_agent_type,
|
default_agent_type,
|
||||||
last_opened_at: m.last_opened_at,
|
last_opened_at: m.last_opened_at,
|
||||||
sort_order: m.sort_order,
|
sort_order: m.sort_order,
|
||||||
|
color: m.color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +111,7 @@ pub async fn add_folder(
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let active = folder::ActiveModel {
|
let active = folder::ActiveModel {
|
||||||
id: NotSet,
|
id: NotSet,
|
||||||
name: Set(name),
|
name: Set(name.clone()),
|
||||||
path: Set(path.to_string()),
|
path: Set(path.to_string()),
|
||||||
git_branch: Set(None),
|
git_branch: Set(None),
|
||||||
default_agent_type: Set(None),
|
default_agent_type: Set(None),
|
||||||
@@ -91,13 +121,42 @@ pub async fn add_folder(
|
|||||||
deleted_at: Set(None),
|
deleted_at: Set(None),
|
||||||
is_open: Set(true),
|
is_open: Set(true),
|
||||||
sort_order: Set(max_order + 1),
|
sort_order: Set(max_order + 1),
|
||||||
|
// Temporary placeholder — we overwrite below with a hash derived
|
||||||
|
// from the final auto-assigned id so each new folder gets a
|
||||||
|
// deterministic, well-distributed palette color.
|
||||||
|
color: Set(FOLDER_COLOR_PALETTE[0].to_string()),
|
||||||
};
|
};
|
||||||
active.insert(conn).await?
|
let inserted = active.insert(conn).await?;
|
||||||
|
let assigned = pick_folder_color(inserted.id, &name);
|
||||||
|
let mut active = inserted.into_active_model();
|
||||||
|
active.color = Set(assigned);
|
||||||
|
active.update(conn).await?
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(to_entry(model))
|
Ok(to_entry(model))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_folder_color(
|
||||||
|
conn: &DatabaseConnection,
|
||||||
|
folder_id: i32,
|
||||||
|
color: &str,
|
||||||
|
) -> Result<Option<FolderDetail>, DbError> {
|
||||||
|
let row = folder::Entity::find_by_id(folder_id)
|
||||||
|
.filter(folder::Column::DeletedAt.is_null())
|
||||||
|
.one(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let Some(row) = row else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut active = row.into_active_model();
|
||||||
|
active.color = Set(color.to_string());
|
||||||
|
active.updated_at = Set(Utc::now());
|
||||||
|
let updated = active.update(conn).await?;
|
||||||
|
Ok(Some(to_detail(updated)))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_folders(conn: &DatabaseConnection) -> Result<Vec<FolderHistoryEntry>, DbError> {
|
pub async fn list_folders(conn: &DatabaseConnection) -> Result<Vec<FolderHistoryEntry>, DbError> {
|
||||||
let rows = folder::Entity::find()
|
let rows = folder::Entity::find()
|
||||||
.filter(folder::Column::DeletedAt.is_null())
|
.filter(folder::Column::DeletedAt.is_null())
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ mod tauri_app {
|
|||||||
folders::open_folder_by_id,
|
folders::open_folder_by_id,
|
||||||
folders::remove_folder_from_workspace,
|
folders::remove_folder_from_workspace,
|
||||||
folders::reorder_folders,
|
folders::reorder_folders,
|
||||||
|
folders::update_folder_color,
|
||||||
folders::add_folder_to_history,
|
folders::add_folder_to_history,
|
||||||
folders::remove_folder_from_history,
|
folders::remove_folder_from_history,
|
||||||
folders::create_folder_directory,
|
folders::create_folder_directory,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub struct FolderDetail {
|
|||||||
pub default_agent_type: Option<AgentType>,
|
pub default_agent_type: Option<AgentType>,
|
||||||
pub last_opened_at: DateTime<Utc>,
|
pub last_opened_at: DateTime<Utc>,
|
||||||
pub sort_order: i32,
|
pub sort_order: i32,
|
||||||
|
pub color: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -140,6 +140,25 @@ pub async fn reorder_folders(
|
|||||||
Ok(Json(()))
|
Ok(Json(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct UpdateFolderColorParams {
|
||||||
|
pub folder_id: i32,
|
||||||
|
pub color: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_folder_color(
|
||||||
|
Extension(state): Extension<Arc<AppState>>,
|
||||||
|
Json(params): Json<UpdateFolderColorParams>,
|
||||||
|
) -> Result<Json<FolderDetail>, AppCommandError> {
|
||||||
|
let db = &state.db;
|
||||||
|
let folder = folder_service::update_folder_color(&db.conn, params.folder_id, ¶ms.color)
|
||||||
|
.await
|
||||||
|
.map_err(AppCommandError::from)?
|
||||||
|
.ok_or_else(|| AppCommandError::not_found("Folder not found"))?;
|
||||||
|
Ok(Json(folder))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PathParams {
|
pub struct PathParams {
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ pub fn build_router(state: Arc<AppState>, token: String, static_dir: std::path::
|
|||||||
post(handlers::folders::remove_folder_from_workspace),
|
post(handlers::folders::remove_folder_from_workspace),
|
||||||
)
|
)
|
||||||
.route("/reorder_folders", post(handlers::folders::reorder_folders))
|
.route("/reorder_folders", post(handlers::folders::reorder_folders))
|
||||||
|
.route(
|
||||||
|
"/update_folder_color",
|
||||||
|
post(handlers::folders::update_folder_color),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/add_folder_to_history",
|
"/add_folder_to_history",
|
||||||
post(handlers::folders::add_folder_to_history),
|
post(handlers::folders::add_folder_to_history),
|
||||||
|
|||||||
@@ -125,10 +125,7 @@ export const SidebarConversationCard = memo(function SidebarConversationCard({
|
|||||||
<span
|
<span
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none absolute z-0",
|
"pointer-events-none absolute z-0 bg-sidebar-border"
|
||||||
isOpenInTab
|
|
||||||
? "bg-sidebar-primary/85"
|
|
||||||
: "bg-sidebar-primary/30"
|
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
top: "-0.0625rem",
|
top: "-0.0625rem",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
GitBranch,
|
GitBranch,
|
||||||
ListChecks,
|
ListChecks,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
Palette,
|
||||||
Plus,
|
Plus,
|
||||||
Rocket,
|
Rocket,
|
||||||
XCircle,
|
XCircle,
|
||||||
@@ -36,6 +37,7 @@ import {
|
|||||||
openProjectBootWindow,
|
openProjectBootWindow,
|
||||||
updateConversationTitle,
|
updateConversationTitle,
|
||||||
updateConversationStatus,
|
updateConversationStatus,
|
||||||
|
updateFolderColor,
|
||||||
deleteConversation,
|
deleteConversation,
|
||||||
} from "@/lib/api"
|
} from "@/lib/api"
|
||||||
import { isDesktop, openFileDialog } from "@/lib/platform"
|
import { isDesktop, openFileDialog } from "@/lib/platform"
|
||||||
@@ -58,6 +60,9 @@ import {
|
|||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
ContextMenuSeparator,
|
ContextMenuSeparator,
|
||||||
|
ContextMenuSub,
|
||||||
|
ContextMenuSubContent,
|
||||||
|
ContextMenuSubTrigger,
|
||||||
} from "@/components/ui/context-menu"
|
} from "@/components/ui/context-menu"
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
@@ -106,6 +111,31 @@ function compareByCreatedAtDesc(
|
|||||||
return right.id - left.id
|
return right.id - left.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sentinel stored in the DB that resolves to the current sidebar foreground
|
||||||
|
// color — the swatch then always reads as the folder name does, across themes.
|
||||||
|
const FOREGROUND_SWATCH = "foreground"
|
||||||
|
|
||||||
|
// Kept in sync with Rust-side `FOLDER_COLOR_PALETTE` in
|
||||||
|
// `src-tauri/src/db/service/folder_service.rs`. Nine well-separated hues
|
||||||
|
// spanning the color wheel (skipping the blue band that reads as muddy),
|
||||||
|
// plus a theme-aware neutral that tracks the sidebar text color.
|
||||||
|
const FOLDER_SWATCH_PALETTE = [
|
||||||
|
"#ef4444", // red
|
||||||
|
"#f97316", // orange
|
||||||
|
"#eab308", // yellow
|
||||||
|
"#84cc16", // lime
|
||||||
|
"#22c55e", // green
|
||||||
|
"#06b6d4", // cyan
|
||||||
|
"#8b5cf6", // violet
|
||||||
|
"#d946ef", // fuchsia
|
||||||
|
"#ec4899", // pink
|
||||||
|
FOREGROUND_SWATCH,
|
||||||
|
] as const
|
||||||
|
|
||||||
|
function resolveSwatchColor(swatch: string): string {
|
||||||
|
return swatch === FOREGROUND_SWATCH ? "var(--sidebar-foreground)" : swatch
|
||||||
|
}
|
||||||
|
|
||||||
function formatRelative(iso: string): string {
|
function formatRelative(iso: string): string {
|
||||||
const ts = parseTimestamp(iso)
|
const ts = parseTimestamp(iso)
|
||||||
if (!ts) return ""
|
if (!ts) return ""
|
||||||
@@ -129,11 +159,13 @@ const FolderHeader = memo(function FolderHeader({
|
|||||||
count,
|
count,
|
||||||
expanded,
|
expanded,
|
||||||
importing,
|
importing,
|
||||||
|
color,
|
||||||
onToggle,
|
onToggle,
|
||||||
onRemoveFromWorkspace,
|
onRemoveFromWorkspace,
|
||||||
onNewConversation,
|
onNewConversation,
|
||||||
onImport,
|
onImport,
|
||||||
onManageConversations,
|
onManageConversations,
|
||||||
|
onChangeColor,
|
||||||
isDragging,
|
isDragging,
|
||||||
t,
|
t,
|
||||||
}: {
|
}: {
|
||||||
@@ -142,11 +174,13 @@ const FolderHeader = memo(function FolderHeader({
|
|||||||
count: number
|
count: number
|
||||||
expanded: boolean
|
expanded: boolean
|
||||||
importing: boolean
|
importing: boolean
|
||||||
|
color: string
|
||||||
onToggle: (folderId: number) => void
|
onToggle: (folderId: number) => void
|
||||||
onRemoveFromWorkspace: (folderId: number) => void
|
onRemoveFromWorkspace: (folderId: number) => void
|
||||||
onNewConversation: (folderId: number) => void
|
onNewConversation: (folderId: number) => void
|
||||||
onImport: (folderId: number) => void
|
onImport: (folderId: number) => void
|
||||||
onManageConversations: (folderId: number) => void
|
onManageConversations: (folderId: number) => void
|
||||||
|
onChangeColor: (folderId: number, color: string) => void
|
||||||
isDragging?: boolean
|
isDragging?: boolean
|
||||||
t: ReturnType<typeof useTranslations>
|
t: ReturnType<typeof useTranslations>
|
||||||
}) {
|
}) {
|
||||||
@@ -156,7 +190,7 @@ const FolderHeader = memo(function FolderHeader({
|
|||||||
<div className={cn("relative h-[2rem]", isDragging && "opacity-60")}>
|
<div className={cn("relative h-[2rem]", isDragging && "opacity-60")}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-[1.9375rem] w-full items-center",
|
"group flex h-[1.9375rem] w-full items-center",
|
||||||
"rounded-full",
|
"rounded-full",
|
||||||
"transition-colors duration-150",
|
"transition-colors duration-150",
|
||||||
isDragging
|
isDragging
|
||||||
@@ -193,12 +227,15 @@ const FolderHeader = memo(function FolderHeader({
|
|||||||
<ChevronRight className="h-[0.6875rem] w-[0.6875rem]" />
|
<ChevronRight className="h-[0.6875rem] w-[0.6875rem]" />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-[0.375rem]">
|
<div className="flex min-w-0 flex-1 items-center gap-[0.5rem]">
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className="inline-block h-[0.5rem] w-[0.5rem] shrink-0 rounded-[0.125rem]"
|
||||||
|
style={{ backgroundColor: resolveSwatchColor(color) }}
|
||||||
|
/>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0 flex-shrink truncate text-left text-[0.875rem] font-semibold tracking-[-0.00625rem]",
|
"min-w-0 flex-shrink truncate text-left text-[0.875rem] font-semibold tracking-[-0.00625rem]"
|
||||||
"transition-colors duration-150",
|
|
||||||
expanded && "text-sidebar-primary"
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{folderName}
|
{folderName}
|
||||||
@@ -208,10 +245,7 @@ const FolderHeader = memo(function FolderHeader({
|
|||||||
"inline-flex shrink-0 items-center justify-center",
|
"inline-flex shrink-0 items-center justify-center",
|
||||||
"h-[0.9375rem] min-w-[1rem] rounded-[0.3125rem] px-[0.25rem]",
|
"h-[0.9375rem] min-w-[1rem] rounded-[0.3125rem] px-[0.25rem]",
|
||||||
"text-[0.625rem] font-semibold leading-none tabular-nums",
|
"text-[0.625rem] font-semibold leading-none tabular-nums",
|
||||||
"transition-colors duration-150",
|
"bg-[color-mix(in_oklab,var(--sidebar-accent),var(--sidebar-foreground)_6%)] text-muted-foreground/80"
|
||||||
expanded
|
|
||||||
? "bg-sidebar-primary/20 text-sidebar-primary"
|
|
||||||
: "bg-[color-mix(in_oklab,var(--sidebar-accent),var(--sidebar-foreground)_6%)] text-muted-foreground/80"
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{count}
|
{count}
|
||||||
@@ -229,10 +263,8 @@ const FolderHeader = memo(function FolderHeader({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"mr-[0.25rem] flex h-[1.25rem] w-[1.25rem] shrink-0 items-center justify-center",
|
"mr-[0.25rem] flex h-[1.25rem] w-[1.25rem] shrink-0 items-center justify-center",
|
||||||
"rounded-[0.25rem] cursor-pointer outline-none text-muted-foreground/80",
|
"rounded-[0.25rem] cursor-pointer outline-none text-muted-foreground/80",
|
||||||
"transition-colors duration-150",
|
"opacity-0 group-hover:opacity-100 focus-visible:opacity-100",
|
||||||
expanded
|
"transition-opacity duration-150 hover:text-sidebar-foreground"
|
||||||
? "hover:text-sidebar-primary"
|
|
||||||
: "hover:text-sidebar-foreground"
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Plus className="h-[0.75rem] w-[0.75rem]" />
|
<Plus className="h-[0.75rem] w-[0.75rem]" />
|
||||||
@@ -257,6 +289,35 @@ const FolderHeader = memo(function FolderHeader({
|
|||||||
<ListChecks className="h-4 w-4" />
|
<ListChecks className="h-4 w-4" />
|
||||||
{t("folderHeaderMenu.manageConversations")}
|
{t("folderHeaderMenu.manageConversations")}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
<ContextMenuSub>
|
||||||
|
<ContextMenuSubTrigger>
|
||||||
|
<Palette className="h-4 w-4" />
|
||||||
|
{t("folderHeaderMenu.changeColor")}
|
||||||
|
</ContextMenuSubTrigger>
|
||||||
|
<ContextMenuSubContent className="min-w-[9rem] p-2">
|
||||||
|
<div className="grid grid-cols-10 gap-1">
|
||||||
|
{FOLDER_SWATCH_PALETTE.map((swatch) => {
|
||||||
|
const active = swatch.toLowerCase() === color.toLowerCase()
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={swatch}
|
||||||
|
type="button"
|
||||||
|
title={swatch}
|
||||||
|
aria-label={swatch}
|
||||||
|
onClick={() => onChangeColor(folderId, swatch)}
|
||||||
|
className={cn(
|
||||||
|
"h-[1.125rem] w-[1.125rem] cursor-pointer rounded-[0.25rem]",
|
||||||
|
"outline-none ring-offset-1 ring-offset-popover",
|
||||||
|
"transition-[box-shadow,transform] duration-100 hover:scale-110",
|
||||||
|
active && "ring-2 ring-foreground/60"
|
||||||
|
)}
|
||||||
|
style={{ backgroundColor: resolveSwatchColor(swatch) }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ContextMenuSubContent>
|
||||||
|
</ContextMenuSub>
|
||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -281,11 +342,13 @@ interface FolderGroupItemProps {
|
|||||||
sortMode: SidebarSortMode
|
sortMode: SidebarSortMode
|
||||||
selectedConversation: { id: number; agentType: string } | null
|
selectedConversation: { id: number; agentType: string } | null
|
||||||
openTabConversationKeys: Set<string>
|
openTabConversationKeys: Set<string>
|
||||||
|
color: string
|
||||||
onToggle: (folderId: number) => void
|
onToggle: (folderId: number) => void
|
||||||
onRemoveFromWorkspace: (folderId: number) => void
|
onRemoveFromWorkspace: (folderId: number) => void
|
||||||
onNewConversationForFolder: (folderId: number) => void
|
onNewConversationForFolder: (folderId: number) => void
|
||||||
onImport: (folderId: number) => void
|
onImport: (folderId: number) => void
|
||||||
onManageConversations: (folderId: number) => void
|
onManageConversations: (folderId: number) => void
|
||||||
|
onChangeColor: (folderId: number, color: string) => void
|
||||||
onSelect: (id: number, agentType: string) => void
|
onSelect: (id: number, agentType: string) => void
|
||||||
onDoubleClick: (id: number, agentType: string) => void
|
onDoubleClick: (id: number, agentType: string) => void
|
||||||
onRename: (id: number, newTitle: string) => Promise<void>
|
onRename: (id: number, newTitle: string) => Promise<void>
|
||||||
@@ -311,11 +374,13 @@ function FolderGroupItem({
|
|||||||
sortMode,
|
sortMode,
|
||||||
selectedConversation,
|
selectedConversation,
|
||||||
openTabConversationKeys,
|
openTabConversationKeys,
|
||||||
|
color,
|
||||||
onToggle,
|
onToggle,
|
||||||
onRemoveFromWorkspace,
|
onRemoveFromWorkspace,
|
||||||
onNewConversationForFolder,
|
onNewConversationForFolder,
|
||||||
onImport,
|
onImport,
|
||||||
onManageConversations,
|
onManageConversations,
|
||||||
|
onChangeColor,
|
||||||
onSelect,
|
onSelect,
|
||||||
onDoubleClick,
|
onDoubleClick,
|
||||||
onRename,
|
onRename,
|
||||||
@@ -379,11 +444,13 @@ function FolderGroupItem({
|
|||||||
count={conversations.length}
|
count={conversations.length}
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
importing={importing}
|
importing={importing}
|
||||||
|
color={color}
|
||||||
onToggle={handleToggle}
|
onToggle={handleToggle}
|
||||||
onRemoveFromWorkspace={onRemoveFromWorkspace}
|
onRemoveFromWorkspace={onRemoveFromWorkspace}
|
||||||
onNewConversation={onNewConversationForFolder}
|
onNewConversation={onNewConversationForFolder}
|
||||||
onImport={onImport}
|
onImport={onImport}
|
||||||
onManageConversations={onManageConversations}
|
onManageConversations={onManageConversations}
|
||||||
|
onChangeColor={onChangeColor}
|
||||||
isDragging={dragging}
|
isDragging={dragging}
|
||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
@@ -449,6 +516,7 @@ export function SidebarConversationList({
|
|||||||
removeFolderFromWorkspace,
|
removeFolderFromWorkspace,
|
||||||
reorderFolders,
|
reorderFolders,
|
||||||
openFolder,
|
openFolder,
|
||||||
|
refreshFolder,
|
||||||
} = useAppWorkspace()
|
} = useAppWorkspace()
|
||||||
const refreshing = loading
|
const refreshing = loading
|
||||||
const { activeFolder } = useActiveFolder()
|
const { activeFolder } = useActiveFolder()
|
||||||
@@ -464,8 +532,9 @@ export function SidebarConversationList({
|
|||||||
const { addTask, updateTask } = useTaskContext()
|
const { addTask, updateTask } = useTaskContext()
|
||||||
|
|
||||||
const folderIndex = useMemo(() => {
|
const folderIndex = useMemo(() => {
|
||||||
const map = new Map<number, { name: string; path: string }>()
|
const map = new Map<number, { name: string; path: string; color: string }>()
|
||||||
for (const f of allFolders) map.set(f.id, { name: f.name, path: f.path })
|
for (const f of allFolders)
|
||||||
|
map.set(f.id, { name: f.name, path: f.path, color: f.color })
|
||||||
return map
|
return map
|
||||||
}, [allFolders])
|
}, [allFolders])
|
||||||
|
|
||||||
@@ -513,6 +582,19 @@ export function SidebarConversationList({
|
|||||||
setFolderExpanded(loadFolderExpanded())
|
setFolderExpanded(loadFolderExpanded())
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleChangeFolderColor = useCallback(
|
||||||
|
async (folderId: number, color: string) => {
|
||||||
|
try {
|
||||||
|
await updateFolderColor(folderId, color)
|
||||||
|
await refreshFolder(folderId)
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err)
|
||||||
|
toast.error(t("toasts.changeFolderColorFailed", { message: msg }))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[refreshFolder, t]
|
||||||
|
)
|
||||||
|
|
||||||
const scrollRootRef = useRef<OverlayScrollbarsComponentRef>(null)
|
const scrollRootRef = useRef<OverlayScrollbarsComponentRef>(null)
|
||||||
const scrollToActiveRef = useRef<() => void>(() => {})
|
const scrollToActiveRef = useRef<() => void>(() => {})
|
||||||
const pendingScrollRef = useRef(false)
|
const pendingScrollRef = useRef(false)
|
||||||
@@ -961,6 +1043,7 @@ export function SidebarConversationList({
|
|||||||
sortMode={sortMode}
|
sortMode={sortMode}
|
||||||
selectedConversation={selectedConversation}
|
selectedConversation={selectedConversation}
|
||||||
openTabConversationKeys={openTabConversationKeys}
|
openTabConversationKeys={openTabConversationKeys}
|
||||||
|
color={folderIndex.get(folderId)?.color ?? "#22c55e"}
|
||||||
onToggle={toggleFolder}
|
onToggle={toggleFolder}
|
||||||
onRemoveFromWorkspace={handleRemoveFolder}
|
onRemoveFromWorkspace={handleRemoveFolder}
|
||||||
onNewConversationForFolder={
|
onNewConversationForFolder={
|
||||||
@@ -968,6 +1051,7 @@ export function SidebarConversationList({
|
|||||||
}
|
}
|
||||||
onImport={handleImportForFolder}
|
onImport={handleImportForFolder}
|
||||||
onManageConversations={handleManageConversations}
|
onManageConversations={handleManageConversations}
|
||||||
|
onChangeColor={handleChangeFolderColor}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
onRename={handleRename}
|
onRename={handleRename}
|
||||||
|
|||||||
@@ -782,7 +782,8 @@
|
|||||||
"folderRemoved": "تمت إزالة المجلد {name}",
|
"folderRemoved": "تمت إزالة المجلد {name}",
|
||||||
"openFolderFailed": "فشل فتح المجلد",
|
"openFolderFailed": "فشل فتح المجلد",
|
||||||
"removeFolderFailed": "فشل إزالة المجلد: {message}",
|
"removeFolderFailed": "فشل إزالة المجلد: {message}",
|
||||||
"reorderFoldersFailed": "فشل إعادة ترتيب المجلدات: {message}"
|
"reorderFoldersFailed": "فشل إعادة ترتيب المجلدات: {message}",
|
||||||
|
"changeFolderColorFailed": "فشل تغيير اللون: {message}"
|
||||||
},
|
},
|
||||||
"statsLabel": "{folders} مجلدات · {convos} محادثة",
|
"statsLabel": "{folders} مجلدات · {convos} محادثة",
|
||||||
"reorderHandle": "اسحب لإعادة الترتيب",
|
"reorderHandle": "اسحب لإعادة الترتيب",
|
||||||
@@ -802,6 +803,7 @@
|
|||||||
"removeFolderConfirmDescription": "إزالة \"{name}\" من مساحة العمل؟ سيتم إغلاق علامات التبويب والمحطات المرتبطة.",
|
"removeFolderConfirmDescription": "إزالة \"{name}\" من مساحة العمل؟ سيتم إغلاق علامات التبويب والمحطات المرتبطة.",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"manageConversations": "إدارة المحادثات…",
|
"manageConversations": "إدارة المحادثات…",
|
||||||
|
"changeColor": "تغيير اللون",
|
||||||
"removeFromWorkspace": "إزالة من مساحة العمل"
|
"removeFromWorkspace": "إزالة من مساحة العمل"
|
||||||
},
|
},
|
||||||
"manageConversations": {
|
"manageConversations": {
|
||||||
|
|||||||
@@ -782,7 +782,8 @@
|
|||||||
"folderRemoved": "Ordner {name} entfernt",
|
"folderRemoved": "Ordner {name} entfernt",
|
||||||
"openFolderFailed": "Ordner konnte nicht geöffnet werden",
|
"openFolderFailed": "Ordner konnte nicht geöffnet werden",
|
||||||
"removeFolderFailed": "Ordner konnte nicht entfernt werden: {message}",
|
"removeFolderFailed": "Ordner konnte nicht entfernt werden: {message}",
|
||||||
"reorderFoldersFailed": "Ordner konnten nicht neu sortiert werden: {message}"
|
"reorderFoldersFailed": "Ordner konnten nicht neu sortiert werden: {message}",
|
||||||
|
"changeFolderColorFailed": "Farbe konnte nicht geändert werden: {message}"
|
||||||
},
|
},
|
||||||
"statsLabel": "{folders} Ordner · {convos} Konversationen",
|
"statsLabel": "{folders} Ordner · {convos} Konversationen",
|
||||||
"reorderHandle": "Zum Neuordnen ziehen",
|
"reorderHandle": "Zum Neuordnen ziehen",
|
||||||
@@ -802,6 +803,7 @@
|
|||||||
"removeFolderConfirmDescription": "\"{name}\" aus dem Arbeitsbereich entfernen? Zugehörige Tabs und Terminals werden geschlossen.",
|
"removeFolderConfirmDescription": "\"{name}\" aus dem Arbeitsbereich entfernen? Zugehörige Tabs und Terminals werden geschlossen.",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"manageConversations": "Konversationen verwalten…",
|
"manageConversations": "Konversationen verwalten…",
|
||||||
|
"changeColor": "Farbe ändern",
|
||||||
"removeFromWorkspace": "Aus Arbeitsbereich entfernen"
|
"removeFromWorkspace": "Aus Arbeitsbereich entfernen"
|
||||||
},
|
},
|
||||||
"manageConversations": {
|
"manageConversations": {
|
||||||
|
|||||||
@@ -782,7 +782,8 @@
|
|||||||
"folderRemoved": "Removed folder {name}",
|
"folderRemoved": "Removed folder {name}",
|
||||||
"openFolderFailed": "Failed to open folder",
|
"openFolderFailed": "Failed to open folder",
|
||||||
"removeFolderFailed": "Failed to remove folder: {message}",
|
"removeFolderFailed": "Failed to remove folder: {message}",
|
||||||
"reorderFoldersFailed": "Failed to reorder folders: {message}"
|
"reorderFoldersFailed": "Failed to reorder folders: {message}",
|
||||||
|
"changeFolderColorFailed": "Failed to change folder color: {message}"
|
||||||
},
|
},
|
||||||
"statsLabel": "{folders} folders · {convos} conversations",
|
"statsLabel": "{folders} folders · {convos} conversations",
|
||||||
"reorderHandle": "Drag to reorder",
|
"reorderHandle": "Drag to reorder",
|
||||||
@@ -802,6 +803,7 @@
|
|||||||
"removeFolderConfirmDescription": "Remove \"{name}\" from the workspace? Its tabs and terminals will close.",
|
"removeFolderConfirmDescription": "Remove \"{name}\" from the workspace? Its tabs and terminals will close.",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"manageConversations": "Manage conversations…",
|
"manageConversations": "Manage conversations…",
|
||||||
|
"changeColor": "Change color",
|
||||||
"removeFromWorkspace": "Remove from workspace"
|
"removeFromWorkspace": "Remove from workspace"
|
||||||
},
|
},
|
||||||
"manageConversations": {
|
"manageConversations": {
|
||||||
|
|||||||
@@ -782,7 +782,8 @@
|
|||||||
"folderRemoved": "Carpeta {name} eliminada",
|
"folderRemoved": "Carpeta {name} eliminada",
|
||||||
"openFolderFailed": "Error al abrir carpeta",
|
"openFolderFailed": "Error al abrir carpeta",
|
||||||
"removeFolderFailed": "Error al eliminar carpeta: {message}",
|
"removeFolderFailed": "Error al eliminar carpeta: {message}",
|
||||||
"reorderFoldersFailed": "Error al reordenar carpetas: {message}"
|
"reorderFoldersFailed": "Error al reordenar carpetas: {message}",
|
||||||
|
"changeFolderColorFailed": "Error al cambiar el color: {message}"
|
||||||
},
|
},
|
||||||
"statsLabel": "{folders} carpetas · {convos} conversaciones",
|
"statsLabel": "{folders} carpetas · {convos} conversaciones",
|
||||||
"reorderHandle": "Arrastrar para reordenar",
|
"reorderHandle": "Arrastrar para reordenar",
|
||||||
@@ -802,6 +803,7 @@
|
|||||||
"removeFolderConfirmDescription": "¿Eliminar \"{name}\" del espacio de trabajo? Sus pestañas y terminales se cerrarán.",
|
"removeFolderConfirmDescription": "¿Eliminar \"{name}\" del espacio de trabajo? Sus pestañas y terminales se cerrarán.",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"manageConversations": "Gestionar conversaciones…",
|
"manageConversations": "Gestionar conversaciones…",
|
||||||
|
"changeColor": "Cambiar color",
|
||||||
"removeFromWorkspace": "Quitar del espacio de trabajo"
|
"removeFromWorkspace": "Quitar del espacio de trabajo"
|
||||||
},
|
},
|
||||||
"manageConversations": {
|
"manageConversations": {
|
||||||
|
|||||||
@@ -782,7 +782,8 @@
|
|||||||
"folderRemoved": "Dossier {name} retiré",
|
"folderRemoved": "Dossier {name} retiré",
|
||||||
"openFolderFailed": "Échec de l'ouverture du dossier",
|
"openFolderFailed": "Échec de l'ouverture du dossier",
|
||||||
"removeFolderFailed": "Échec de la suppression du dossier : {message}",
|
"removeFolderFailed": "Échec de la suppression du dossier : {message}",
|
||||||
"reorderFoldersFailed": "Échec du réordonnancement des dossiers : {message}"
|
"reorderFoldersFailed": "Échec du réordonnancement des dossiers : {message}",
|
||||||
|
"changeFolderColorFailed": "Échec du changement de couleur : {message}"
|
||||||
},
|
},
|
||||||
"statsLabel": "{folders} dossiers · {convos} conversations",
|
"statsLabel": "{folders} dossiers · {convos} conversations",
|
||||||
"reorderHandle": "Glisser pour réorganiser",
|
"reorderHandle": "Glisser pour réorganiser",
|
||||||
@@ -802,6 +803,7 @@
|
|||||||
"removeFolderConfirmDescription": "Retirer \"{name}\" de l'espace de travail ? Les onglets et terminaux associés seront fermés.",
|
"removeFolderConfirmDescription": "Retirer \"{name}\" de l'espace de travail ? Les onglets et terminaux associés seront fermés.",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"manageConversations": "Gérer les conversations…",
|
"manageConversations": "Gérer les conversations…",
|
||||||
|
"changeColor": "Changer la couleur",
|
||||||
"removeFromWorkspace": "Retirer de l'espace de travail"
|
"removeFromWorkspace": "Retirer de l'espace de travail"
|
||||||
},
|
},
|
||||||
"manageConversations": {
|
"manageConversations": {
|
||||||
|
|||||||
@@ -782,7 +782,8 @@
|
|||||||
"folderRemoved": "フォルダ {name} を削除しました",
|
"folderRemoved": "フォルダ {name} を削除しました",
|
||||||
"openFolderFailed": "フォルダを開けませんでした",
|
"openFolderFailed": "フォルダを開けませんでした",
|
||||||
"removeFolderFailed": "フォルダの削除に失敗しました: {message}",
|
"removeFolderFailed": "フォルダの削除に失敗しました: {message}",
|
||||||
"reorderFoldersFailed": "フォルダの並べ替えに失敗しました: {message}"
|
"reorderFoldersFailed": "フォルダの並べ替えに失敗しました: {message}",
|
||||||
|
"changeFolderColorFailed": "色の変更に失敗しました: {message}"
|
||||||
},
|
},
|
||||||
"statsLabel": "{folders} フォルダ · {convos} 会話",
|
"statsLabel": "{folders} フォルダ · {convos} 会話",
|
||||||
"reorderHandle": "ドラッグして並べ替え",
|
"reorderHandle": "ドラッグして並べ替え",
|
||||||
@@ -802,6 +803,7 @@
|
|||||||
"removeFolderConfirmDescription": "\"{name}\" をワークスペースから削除しますか?関連するタブとターミナルが閉じられます。",
|
"removeFolderConfirmDescription": "\"{name}\" をワークスペースから削除しますか?関連するタブとターミナルが閉じられます。",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"manageConversations": "会話の管理…",
|
"manageConversations": "会話の管理…",
|
||||||
|
"changeColor": "色を変更",
|
||||||
"removeFromWorkspace": "ワークスペースから削除"
|
"removeFromWorkspace": "ワークスペースから削除"
|
||||||
},
|
},
|
||||||
"manageConversations": {
|
"manageConversations": {
|
||||||
|
|||||||
@@ -782,7 +782,8 @@
|
|||||||
"folderRemoved": "폴더 {name}을(를) 제거했습니다",
|
"folderRemoved": "폴더 {name}을(를) 제거했습니다",
|
||||||
"openFolderFailed": "폴더를 열 수 없습니다",
|
"openFolderFailed": "폴더를 열 수 없습니다",
|
||||||
"removeFolderFailed": "폴더 제거 실패: {message}",
|
"removeFolderFailed": "폴더 제거 실패: {message}",
|
||||||
"reorderFoldersFailed": "폴더 순서 변경 실패: {message}"
|
"reorderFoldersFailed": "폴더 순서 변경 실패: {message}",
|
||||||
|
"changeFolderColorFailed": "색상 변경 실패: {message}"
|
||||||
},
|
},
|
||||||
"statsLabel": "{folders}개 폴더 · {convos}개 대화",
|
"statsLabel": "{folders}개 폴더 · {convos}개 대화",
|
||||||
"reorderHandle": "드래그하여 순서 변경",
|
"reorderHandle": "드래그하여 순서 변경",
|
||||||
@@ -802,6 +803,7 @@
|
|||||||
"removeFolderConfirmDescription": "워크스페이스에서 \"{name}\"을(를) 제거하시겠습니까? 관련 탭과 터미널이 닫힙니다.",
|
"removeFolderConfirmDescription": "워크스페이스에서 \"{name}\"을(를) 제거하시겠습니까? 관련 탭과 터미널이 닫힙니다.",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"manageConversations": "대화 관리…",
|
"manageConversations": "대화 관리…",
|
||||||
|
"changeColor": "색상 변경",
|
||||||
"removeFromWorkspace": "워크스페이스에서 제거"
|
"removeFromWorkspace": "워크스페이스에서 제거"
|
||||||
},
|
},
|
||||||
"manageConversations": {
|
"manageConversations": {
|
||||||
|
|||||||
@@ -782,7 +782,8 @@
|
|||||||
"folderRemoved": "Pasta {name} removida",
|
"folderRemoved": "Pasta {name} removida",
|
||||||
"openFolderFailed": "Falha ao abrir pasta",
|
"openFolderFailed": "Falha ao abrir pasta",
|
||||||
"removeFolderFailed": "Falha ao remover pasta: {message}",
|
"removeFolderFailed": "Falha ao remover pasta: {message}",
|
||||||
"reorderFoldersFailed": "Falha ao reordenar pastas: {message}"
|
"reorderFoldersFailed": "Falha ao reordenar pastas: {message}",
|
||||||
|
"changeFolderColorFailed": "Falha ao alterar a cor: {message}"
|
||||||
},
|
},
|
||||||
"statsLabel": "{folders} pastas · {convos} conversas",
|
"statsLabel": "{folders} pastas · {convos} conversas",
|
||||||
"reorderHandle": "Arraste para reordenar",
|
"reorderHandle": "Arraste para reordenar",
|
||||||
@@ -802,6 +803,7 @@
|
|||||||
"removeFolderConfirmDescription": "Remover \"{name}\" do espaço de trabalho? As abas e terminais relacionados serão fechados.",
|
"removeFolderConfirmDescription": "Remover \"{name}\" do espaço de trabalho? As abas e terminais relacionados serão fechados.",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"manageConversations": "Gerenciar conversas…",
|
"manageConversations": "Gerenciar conversas…",
|
||||||
|
"changeColor": "Alterar cor",
|
||||||
"removeFromWorkspace": "Remover do espaço de trabalho"
|
"removeFromWorkspace": "Remover do espaço de trabalho"
|
||||||
},
|
},
|
||||||
"manageConversations": {
|
"manageConversations": {
|
||||||
|
|||||||
@@ -782,7 +782,8 @@
|
|||||||
"folderRemoved": "已移除文件夹 {name}",
|
"folderRemoved": "已移除文件夹 {name}",
|
||||||
"openFolderFailed": "打开文件夹失败",
|
"openFolderFailed": "打开文件夹失败",
|
||||||
"removeFolderFailed": "移除文件夹失败:{message}",
|
"removeFolderFailed": "移除文件夹失败:{message}",
|
||||||
"reorderFoldersFailed": "重新排序文件夹失败:{message}"
|
"reorderFoldersFailed": "重新排序文件夹失败:{message}",
|
||||||
|
"changeFolderColorFailed": "修改颜色失败:{message}"
|
||||||
},
|
},
|
||||||
"statsLabel": "{folders} 个文件夹 · {convos} 个会话",
|
"statsLabel": "{folders} 个文件夹 · {convos} 个会话",
|
||||||
"reorderHandle": "拖拽排序",
|
"reorderHandle": "拖拽排序",
|
||||||
@@ -802,6 +803,7 @@
|
|||||||
"removeFolderConfirmDescription": "从工作区移除 \"{name}\"?其相关 Tab 与终端将会关闭。",
|
"removeFolderConfirmDescription": "从工作区移除 \"{name}\"?其相关 Tab 与终端将会关闭。",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"manageConversations": "会话管理…",
|
"manageConversations": "会话管理…",
|
||||||
|
"changeColor": "修改颜色",
|
||||||
"removeFromWorkspace": "从工作区移除"
|
"removeFromWorkspace": "从工作区移除"
|
||||||
},
|
},
|
||||||
"manageConversations": {
|
"manageConversations": {
|
||||||
|
|||||||
@@ -782,7 +782,8 @@
|
|||||||
"folderRemoved": "已移除資料夾 {name}",
|
"folderRemoved": "已移除資料夾 {name}",
|
||||||
"openFolderFailed": "開啟資料夾失敗",
|
"openFolderFailed": "開啟資料夾失敗",
|
||||||
"removeFolderFailed": "移除資料夾失敗:{message}",
|
"removeFolderFailed": "移除資料夾失敗:{message}",
|
||||||
"reorderFoldersFailed": "重新排序資料夾失敗:{message}"
|
"reorderFoldersFailed": "重新排序資料夾失敗:{message}",
|
||||||
|
"changeFolderColorFailed": "修改顏色失敗:{message}"
|
||||||
},
|
},
|
||||||
"statsLabel": "{folders} 個資料夾 · {convos} 個對話",
|
"statsLabel": "{folders} 個資料夾 · {convos} 個對話",
|
||||||
"reorderHandle": "拖拽排序",
|
"reorderHandle": "拖拽排序",
|
||||||
@@ -802,6 +803,7 @@
|
|||||||
"removeFolderConfirmDescription": "從工作區移除 \"{name}\"?相關分頁與終端機將會關閉。",
|
"removeFolderConfirmDescription": "從工作區移除 \"{name}\"?相關分頁與終端機將會關閉。",
|
||||||
"folderHeaderMenu": {
|
"folderHeaderMenu": {
|
||||||
"manageConversations": "會話管理…",
|
"manageConversations": "會話管理…",
|
||||||
|
"changeColor": "修改顏色",
|
||||||
"removeFromWorkspace": "從工作區移除"
|
"removeFromWorkspace": "從工作區移除"
|
||||||
},
|
},
|
||||||
"manageConversations": {
|
"manageConversations": {
|
||||||
|
|||||||
@@ -644,6 +644,13 @@ export async function reorderFolders(ids: number[]): Promise<void> {
|
|||||||
return getTransport().call("reorder_folders", { ids })
|
return getTransport().call("reorder_folders", { ids })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateFolderColor(
|
||||||
|
folderId: number,
|
||||||
|
color: string
|
||||||
|
): Promise<FolderDetail> {
|
||||||
|
return getTransport().call("update_folder_color", { folderId, color })
|
||||||
|
}
|
||||||
|
|
||||||
export async function importLocalConversations(
|
export async function importLocalConversations(
|
||||||
folderId: number
|
folderId: number
|
||||||
): Promise<ImportResult> {
|
): Promise<ImportResult> {
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ export interface FolderDetail {
|
|||||||
default_agent_type: AgentType | null
|
default_agent_type: AgentType | null
|
||||||
last_opened_at: string
|
last_opened_at: string
|
||||||
sort_order: number
|
sort_order: number
|
||||||
|
color: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenedTab {
|
export interface OpenedTab {
|
||||||
|
|||||||
Reference in New Issue
Block a user