diff --git a/src-tauri/src/commands/folders.rs b/src-tauri/src/commands/folders.rs index 48e1ccf..a68fd2d 100644 --- a/src-tauri/src/commands/folders.rs +++ b/src-tauri/src/commands/folders.rs @@ -567,6 +567,19 @@ pub async fn reorder_folders( .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 { + 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)] pub async fn create_folder_directory(path: String) -> Result<(), AppCommandError> { std::fs::create_dir_all(&path).map_err(AppCommandError::io) diff --git a/src-tauri/src/db/entities/folder.rs b/src-tauri/src/db/entities/folder.rs index bc798c6..0fb08c5 100644 --- a/src-tauri/src/db/entities/folder.rs +++ b/src-tauri/src/db/entities/folder.rs @@ -16,6 +16,7 @@ pub struct Model { pub deleted_at: Option, pub is_open: bool, pub sort_order: i32, + pub color: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/src-tauri/src/db/migration/m20260424_000001_folder_color.rs b/src-tauri/src/db/migration/m20260424_000001_folder_color.rs new file mode 100644 index 0000000..56f813a --- /dev/null +++ b/src-tauri/src/db/migration/m20260424_000001_folder_color.rs @@ -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, +} diff --git a/src-tauri/src/db/migration/mod.rs b/src-tauri/src/db/migration/mod.rs index 74aece2..0007e5e 100644 --- a/src-tauri/src/db/migration/mod.rs +++ b/src-tauri/src/db/migration/mod.rs @@ -12,6 +12,7 @@ mod m20260406_000001_agent_setting_model_provider; mod m20260420_000001_opened_tabs; mod m20260422_000001_folder_sort_order; mod m20260423_000001_drop_folder_parent_branch; +mod m20260424_000001_folder_color; pub struct Migrator; #[async_trait::async_trait] @@ -30,6 +31,7 @@ impl MigratorTrait for Migrator { Box::new(m20260420_000001_opened_tabs::Migration), Box::new(m20260422_000001_folder_sort_order::Migration), Box::new(m20260423_000001_drop_folder_parent_branch::Migration), + Box::new(m20260424_000001_folder_color::Migration), ] } } diff --git a/src-tauri/src/db/service/folder_service.rs b/src-tauri/src/db/service/folder_service.rs index 6d9fa56..f7acc48 100644 --- a/src-tauri/src/db/service/folder_service.rs +++ b/src-tauri/src/db/service/folder_service.rs @@ -10,6 +10,35 @@ use crate::db::error::DbError; use crate::models::agent::AgentType; 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 { FolderHistoryEntry { id: m.id, @@ -34,6 +63,7 @@ fn to_detail(m: folder::Model) -> FolderDetail { default_agent_type, last_opened_at: m.last_opened_at, sort_order: m.sort_order, + color: m.color, } } @@ -81,7 +111,7 @@ pub async fn add_folder( .unwrap_or(0); let active = folder::ActiveModel { id: NotSet, - name: Set(name), + name: Set(name.clone()), path: Set(path.to_string()), git_branch: Set(None), default_agent_type: Set(None), @@ -91,13 +121,42 @@ pub async fn add_folder( deleted_at: Set(None), is_open: Set(true), 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)) } +pub async fn update_folder_color( + conn: &DatabaseConnection, + folder_id: i32, + color: &str, +) -> Result, 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, DbError> { let rows = folder::Entity::find() .filter(folder::Column::DeletedAt.is_null()) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3fda95f..9f4b4c6 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -221,6 +221,7 @@ mod tauri_app { folders::open_folder_by_id, folders::remove_folder_from_workspace, folders::reorder_folders, + folders::update_folder_color, folders::add_folder_to_history, folders::remove_folder_from_history, folders::create_folder_directory, diff --git a/src-tauri/src/models/folder.rs b/src-tauri/src/models/folder.rs index 5910beb..eaa1701 100644 --- a/src-tauri/src/models/folder.rs +++ b/src-tauri/src/models/folder.rs @@ -20,6 +20,7 @@ pub struct FolderDetail { pub default_agent_type: Option, pub last_opened_at: DateTime, pub sort_order: i32, + pub color: String, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src-tauri/src/web/handlers/folders.rs b/src-tauri/src/web/handlers/folders.rs index f8c4195..d615c6b 100644 --- a/src-tauri/src/web/handlers/folders.rs +++ b/src-tauri/src/web/handlers/folders.rs @@ -140,6 +140,25 @@ pub async fn reorder_folders( 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>, + Json(params): Json, +) -> Result, 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)] #[serde(rename_all = "camelCase")] pub struct PathParams { diff --git a/src-tauri/src/web/router.rs b/src-tauri/src/web/router.rs index 5cd1841..f7717c6 100644 --- a/src-tauri/src/web/router.rs +++ b/src-tauri/src/web/router.rs @@ -107,6 +107,10 @@ pub fn build_router(state: Arc, token: String, static_dir: std::path:: post(handlers::folders::remove_folder_from_workspace), ) .route("/reorder_folders", post(handlers::folders::reorder_folders)) + .route( + "/update_folder_color", + post(handlers::folders::update_folder_color), + ) .route( "/add_folder_to_history", post(handlers::folders::add_folder_to_history), diff --git a/src/components/conversations/sidebar-conversation-card.tsx b/src/components/conversations/sidebar-conversation-card.tsx index 0e1f95e..3da738b 100644 --- a/src/components/conversations/sidebar-conversation-card.tsx +++ b/src/components/conversations/sidebar-conversation-card.tsx @@ -125,10 +125,7 @@ export const SidebarConversationCard = memo(function SidebarConversationCard({ void onRemoveFromWorkspace: (folderId: number) => void onNewConversation: (folderId: number) => void onImport: (folderId: number) => void onManageConversations: (folderId: number) => void + onChangeColor: (folderId: number, color: string) => void isDragging?: boolean t: ReturnType }) { @@ -156,7 +190,7 @@ const FolderHeader = memo(function FolderHeader({
)} -
+
+ {folderName} @@ -208,10 +245,7 @@ const FolderHeader = memo(function FolderHeader({ "inline-flex shrink-0 items-center justify-center", "h-[0.9375rem] min-w-[1rem] rounded-[0.3125rem] px-[0.25rem]", "text-[0.625rem] font-semibold leading-none tabular-nums", - "transition-colors duration-150", - expanded - ? "bg-sidebar-primary/20 text-sidebar-primary" - : "bg-[color-mix(in_oklab,var(--sidebar-accent),var(--sidebar-foreground)_6%)] text-muted-foreground/80" + "bg-[color-mix(in_oklab,var(--sidebar-accent),var(--sidebar-foreground)_6%)] text-muted-foreground/80" )} > {count} @@ -229,10 +263,8 @@ const FolderHeader = memo(function FolderHeader({ className={cn( "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", - "transition-colors duration-150", - expanded - ? "hover:text-sidebar-primary" - : "hover:text-sidebar-foreground" + "opacity-0 group-hover:opacity-100 focus-visible:opacity-100", + "transition-opacity duration-150 hover:text-sidebar-foreground" )} > @@ -257,6 +289,35 @@ const FolderHeader = memo(function FolderHeader({ {t("folderHeaderMenu.manageConversations")} + + + + {t("folderHeaderMenu.changeColor")} + + +
+ {FOLDER_SWATCH_PALETTE.map((swatch) => { + const active = swatch.toLowerCase() === color.toLowerCase() + return ( +
+
+
+ color: string onToggle: (folderId: number) => void onRemoveFromWorkspace: (folderId: number) => void onNewConversationForFolder: (folderId: number) => void onImport: (folderId: number) => void onManageConversations: (folderId: number) => void + onChangeColor: (folderId: number, color: string) => void onSelect: (id: number, agentType: string) => void onDoubleClick: (id: number, agentType: string) => void onRename: (id: number, newTitle: string) => Promise @@ -311,11 +374,13 @@ function FolderGroupItem({ sortMode, selectedConversation, openTabConversationKeys, + color, onToggle, onRemoveFromWorkspace, onNewConversationForFolder, onImport, onManageConversations, + onChangeColor, onSelect, onDoubleClick, onRename, @@ -379,11 +444,13 @@ function FolderGroupItem({ count={conversations.length} expanded={expanded} importing={importing} + color={color} onToggle={handleToggle} onRemoveFromWorkspace={onRemoveFromWorkspace} onNewConversation={onNewConversationForFolder} onImport={onImport} onManageConversations={onManageConversations} + onChangeColor={onChangeColor} isDragging={dragging} t={t} /> @@ -449,6 +516,7 @@ export function SidebarConversationList({ removeFolderFromWorkspace, reorderFolders, openFolder, + refreshFolder, } = useAppWorkspace() const refreshing = loading const { activeFolder } = useActiveFolder() @@ -464,8 +532,9 @@ export function SidebarConversationList({ const { addTask, updateTask } = useTaskContext() const folderIndex = useMemo(() => { - const map = new Map() - for (const f of allFolders) map.set(f.id, { name: f.name, path: f.path }) + const map = new Map() + for (const f of allFolders) + map.set(f.id, { name: f.name, path: f.path, color: f.color }) return map }, [allFolders]) @@ -513,6 +582,19 @@ export function SidebarConversationList({ 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(null) const scrollToActiveRef = useRef<() => void>(() => {}) const pendingScrollRef = useRef(false) @@ -961,6 +1043,7 @@ export function SidebarConversationList({ sortMode={sortMode} selectedConversation={selectedConversation} openTabConversationKeys={openTabConversationKeys} + color={folderIndex.get(folderId)?.color ?? "#22c55e"} onToggle={toggleFolder} onRemoveFromWorkspace={handleRemoveFolder} onNewConversationForFolder={ @@ -968,6 +1051,7 @@ export function SidebarConversationList({ } onImport={handleImportForFolder} onManageConversations={handleManageConversations} + onChangeColor={handleChangeFolderColor} onSelect={handleSelect} onDoubleClick={handleDoubleClick} onRename={handleRename} diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 98fa935..d41e596 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -782,7 +782,8 @@ "folderRemoved": "تمت إزالة المجلد {name}", "openFolderFailed": "فشل فتح المجلد", "removeFolderFailed": "فشل إزالة المجلد: {message}", - "reorderFoldersFailed": "فشل إعادة ترتيب المجلدات: {message}" + "reorderFoldersFailed": "فشل إعادة ترتيب المجلدات: {message}", + "changeFolderColorFailed": "فشل تغيير اللون: {message}" }, "statsLabel": "{folders} مجلدات · {convos} محادثة", "reorderHandle": "اسحب لإعادة الترتيب", @@ -802,6 +803,7 @@ "removeFolderConfirmDescription": "إزالة \"{name}\" من مساحة العمل؟ سيتم إغلاق علامات التبويب والمحطات المرتبطة.", "folderHeaderMenu": { "manageConversations": "إدارة المحادثات…", + "changeColor": "تغيير اللون", "removeFromWorkspace": "إزالة من مساحة العمل" }, "manageConversations": { diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 00cae8d..a31c27d 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -782,7 +782,8 @@ "folderRemoved": "Ordner {name} entfernt", "openFolderFailed": "Ordner konnte nicht geöffnet werden", "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", "reorderHandle": "Zum Neuordnen ziehen", @@ -802,6 +803,7 @@ "removeFolderConfirmDescription": "\"{name}\" aus dem Arbeitsbereich entfernen? Zugehörige Tabs und Terminals werden geschlossen.", "folderHeaderMenu": { "manageConversations": "Konversationen verwalten…", + "changeColor": "Farbe ändern", "removeFromWorkspace": "Aus Arbeitsbereich entfernen" }, "manageConversations": { diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 255476c..1a46578 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -782,7 +782,8 @@ "folderRemoved": "Removed folder {name}", "openFolderFailed": "Failed to open folder", "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", "reorderHandle": "Drag to reorder", @@ -802,6 +803,7 @@ "removeFolderConfirmDescription": "Remove \"{name}\" from the workspace? Its tabs and terminals will close.", "folderHeaderMenu": { "manageConversations": "Manage conversations…", + "changeColor": "Change color", "removeFromWorkspace": "Remove from workspace" }, "manageConversations": { diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index dd60036..45608e7 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -782,7 +782,8 @@ "folderRemoved": "Carpeta {name} eliminada", "openFolderFailed": "Error al abrir carpeta", "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", "reorderHandle": "Arrastrar para reordenar", @@ -802,6 +803,7 @@ "removeFolderConfirmDescription": "¿Eliminar \"{name}\" del espacio de trabajo? Sus pestañas y terminales se cerrarán.", "folderHeaderMenu": { "manageConversations": "Gestionar conversaciones…", + "changeColor": "Cambiar color", "removeFromWorkspace": "Quitar del espacio de trabajo" }, "manageConversations": { diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 5994934..3faefda 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -782,7 +782,8 @@ "folderRemoved": "Dossier {name} retiré", "openFolderFailed": "Échec de l'ouverture du dossier", "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", "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.", "folderHeaderMenu": { "manageConversations": "Gérer les conversations…", + "changeColor": "Changer la couleur", "removeFromWorkspace": "Retirer de l'espace de travail" }, "manageConversations": { diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 7e6c1db..0fc3f17 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -782,7 +782,8 @@ "folderRemoved": "フォルダ {name} を削除しました", "openFolderFailed": "フォルダを開けませんでした", "removeFolderFailed": "フォルダの削除に失敗しました: {message}", - "reorderFoldersFailed": "フォルダの並べ替えに失敗しました: {message}" + "reorderFoldersFailed": "フォルダの並べ替えに失敗しました: {message}", + "changeFolderColorFailed": "色の変更に失敗しました: {message}" }, "statsLabel": "{folders} フォルダ · {convos} 会話", "reorderHandle": "ドラッグして並べ替え", @@ -802,6 +803,7 @@ "removeFolderConfirmDescription": "\"{name}\" をワークスペースから削除しますか?関連するタブとターミナルが閉じられます。", "folderHeaderMenu": { "manageConversations": "会話の管理…", + "changeColor": "色を変更", "removeFromWorkspace": "ワークスペースから削除" }, "manageConversations": { diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 1b2f4d9..d83a033 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -782,7 +782,8 @@ "folderRemoved": "폴더 {name}을(를) 제거했습니다", "openFolderFailed": "폴더를 열 수 없습니다", "removeFolderFailed": "폴더 제거 실패: {message}", - "reorderFoldersFailed": "폴더 순서 변경 실패: {message}" + "reorderFoldersFailed": "폴더 순서 변경 실패: {message}", + "changeFolderColorFailed": "색상 변경 실패: {message}" }, "statsLabel": "{folders}개 폴더 · {convos}개 대화", "reorderHandle": "드래그하여 순서 변경", @@ -802,6 +803,7 @@ "removeFolderConfirmDescription": "워크스페이스에서 \"{name}\"을(를) 제거하시겠습니까? 관련 탭과 터미널이 닫힙니다.", "folderHeaderMenu": { "manageConversations": "대화 관리…", + "changeColor": "색상 변경", "removeFromWorkspace": "워크스페이스에서 제거" }, "manageConversations": { diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 380adc4..c51e656 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -782,7 +782,8 @@ "folderRemoved": "Pasta {name} removida", "openFolderFailed": "Falha ao abrir pasta", "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", "reorderHandle": "Arraste para reordenar", @@ -802,6 +803,7 @@ "removeFolderConfirmDescription": "Remover \"{name}\" do espaço de trabalho? As abas e terminais relacionados serão fechados.", "folderHeaderMenu": { "manageConversations": "Gerenciar conversas…", + "changeColor": "Alterar cor", "removeFromWorkspace": "Remover do espaço de trabalho" }, "manageConversations": { diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 9f859a9..bad35cf 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -782,7 +782,8 @@ "folderRemoved": "已移除文件夹 {name}", "openFolderFailed": "打开文件夹失败", "removeFolderFailed": "移除文件夹失败:{message}", - "reorderFoldersFailed": "重新排序文件夹失败:{message}" + "reorderFoldersFailed": "重新排序文件夹失败:{message}", + "changeFolderColorFailed": "修改颜色失败:{message}" }, "statsLabel": "{folders} 个文件夹 · {convos} 个会话", "reorderHandle": "拖拽排序", @@ -802,6 +803,7 @@ "removeFolderConfirmDescription": "从工作区移除 \"{name}\"?其相关 Tab 与终端将会关闭。", "folderHeaderMenu": { "manageConversations": "会话管理…", + "changeColor": "修改颜色", "removeFromWorkspace": "从工作区移除" }, "manageConversations": { diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 01d423a..5af5244 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -782,7 +782,8 @@ "folderRemoved": "已移除資料夾 {name}", "openFolderFailed": "開啟資料夾失敗", "removeFolderFailed": "移除資料夾失敗:{message}", - "reorderFoldersFailed": "重新排序資料夾失敗:{message}" + "reorderFoldersFailed": "重新排序資料夾失敗:{message}", + "changeFolderColorFailed": "修改顏色失敗:{message}" }, "statsLabel": "{folders} 個資料夾 · {convos} 個對話", "reorderHandle": "拖拽排序", @@ -802,6 +803,7 @@ "removeFolderConfirmDescription": "從工作區移除 \"{name}\"?相關分頁與終端機將會關閉。", "folderHeaderMenu": { "manageConversations": "會話管理…", + "changeColor": "修改顏色", "removeFromWorkspace": "從工作區移除" }, "manageConversations": { diff --git a/src/lib/api.ts b/src/lib/api.ts index f982e27..0fa1d22 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -644,6 +644,13 @@ export async function reorderFolders(ids: number[]): Promise { return getTransport().call("reorder_folders", { ids }) } +export async function updateFolderColor( + folderId: number, + color: string +): Promise { + return getTransport().call("update_folder_color", { folderId, color }) +} + export async function importLocalConversations( folderId: number ): Promise { diff --git a/src/lib/types.ts b/src/lib/types.ts index 749f67a..0be23ce 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -163,6 +163,7 @@ export interface FolderDetail { default_agent_type: AgentType | null last_opened_at: string sort_order: number + color: string } export interface OpenedTab {