From 14fb231dcc6ca6754adbec6dce0029c46df24fd4 Mon Sep 17 00:00:00 2001 From: xintaofei Date: Wed, 22 Apr 2026 09:31:38 +0800 Subject: [PATCH] feat(sidebar): add conversation management dialog with filtering and bulk actions --- src-tauri/src/web/handlers/conversations.rs | 2 +- .../conversation-manage-dialog.tsx | 500 ++++++++++++++++++ .../sidebar-conversation-list.tsx | 40 +- src/i18n/messages/ar.json | 22 + src/i18n/messages/de.json | 22 + src/i18n/messages/en.json | 22 + src/i18n/messages/es.json | 22 + src/i18n/messages/fr.json | 22 + src/i18n/messages/ja.json | 22 + src/i18n/messages/ko.json | 22 + src/i18n/messages/pt.json | 22 + src/i18n/messages/zh-CN.json | 22 + src/i18n/messages/zh-TW.json | 22 + 13 files changed, 760 insertions(+), 2 deletions(-) create mode 100644 src/components/conversations/conversation-manage-dialog.tsx diff --git a/src-tauri/src/web/handlers/conversations.rs b/src-tauri/src/web/handlers/conversations.rs index 1000045..defee2f 100644 --- a/src-tauri/src/web/handlers/conversations.rs +++ b/src-tauri/src/web/handlers/conversations.rs @@ -251,4 +251,4 @@ pub async fn update_conversation_external_id( .await .map_err(AppCommandError::from)?; Ok(Json(())) -} +} \ No newline at end of file diff --git a/src/components/conversations/conversation-manage-dialog.tsx b/src/components/conversations/conversation-manage-dialog.tsx new file mode 100644 index 0000000..3fccb8d --- /dev/null +++ b/src/components/conversations/conversation-manage-dialog.tsx @@ -0,0 +1,500 @@ +"use client" + +import { useCallback, useEffect, useMemo, useState } from "react" +import { useTranslations } from "next-intl" +import { toast } from "sonner" +import { + CheckSquare, + ChevronDown, + ListChecks, + Loader2, + Square, + Trash2, +} from "lucide-react" +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Skeleton } from "@/components/ui/skeleton" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog" +import { AgentIcon } from "@/components/agent-icon" +import { useAppWorkspace } from "@/contexts/app-workspace-context" +import { useTabContext } from "@/contexts/tab-context" +import { + deleteConversation, + listAllConversations, + updateConversationStatus, +} from "@/lib/api" +import type { + AgentType, + ConversationStatus, + DbConversationSummary, +} from "@/lib/types" +import { + AGENT_LABELS, + ALL_AGENT_TYPES, + STATUS_COLORS, + STATUS_ORDER, +} from "@/lib/types" +import { cn } from "@/lib/utils" + +interface ConversationManageDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + folderId: number + folderName: string +} + +function parseTimestamp(value: string): number { + const ts = Date.parse(value) + return Number.isNaN(ts) ? 0 : ts +} + +function formatRelative(iso: string): string { + const ts = parseTimestamp(iso) + if (!ts) return "" + const diff = Math.max(0, Date.now() - ts) + const m = Math.floor(diff / 60000) + if (m < 1) return "now" + if (m < 60) return `${m}m` + const h = Math.floor(m / 60) + if (h < 24) return `${h}h` + const d = Math.floor(h / 24) + if (d < 30) return `${d}d` + const mo = Math.floor(d / 30) + if (mo < 12) return `${mo}mo` + const y = Math.floor(mo / 12) + return `${y}y` +} + +export function ConversationManageDialog({ + open, + onOpenChange, + folderId, + folderName, +}: ConversationManageDialogProps) { + const t = useTranslations("Folder.sidebar.manageConversations") + const tCommon = useTranslations("Folder.common") + const tStatus = useTranslations("Folder.statusLabels") + + const { refreshConversations } = useAppWorkspace() + const { closeConversationTab } = useTabContext() + + const [search, setSearch] = useState("") + const [agentFilter, setAgentFilter] = useState("all") + const [statusFilter, setStatusFilter] = useState( + "all" + ) + const [rows, setRows] = useState([]) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [selected, setSelected] = useState>(new Set()) + const [pending, setPending] = useState(false) + const [refreshKey, setRefreshKey] = useState(0) + const [confirmDelete, setConfirmDelete] = useState(false) + + // Reset state on open/close transitions + useEffect(() => { + if (!open) { + setSearch("") + setAgentFilter("all") + setStatusFilter("all") + setSelected(new Set()) + setConfirmDelete(false) + setError(null) + } + }, [open]) + + // Debounced data fetch + useEffect(() => { + if (!open) return + const timer = setTimeout(async () => { + setLoading(true) + try { + const data = await listAllConversations({ + folder_ids: [folderId], + search: search.trim() || null, + agent_type: agentFilter === "all" ? null : agentFilter, + status: statusFilter === "all" ? null : statusFilter, + }) + setRows(data) + setError(null) + } catch (e) { + setError(e instanceof Error ? e.message : String(e)) + } finally { + setLoading(false) + } + }, 300) + return () => clearTimeout(timer) + }, [open, folderId, search, agentFilter, statusFilter, refreshKey]) + + const toggleOne = useCallback((id: number) => { + setSelected((prev) => { + const next = new Set(prev) + if (next.has(id)) next.delete(id) + else next.add(id) + return next + }) + }, []) + + const allVisibleSelected = useMemo( + () => rows.length > 0 && rows.every((r) => selected.has(r.id)), + [rows, selected] + ) + + const toggleSelectAll = useCallback(() => { + if (allVisibleSelected) { + setSelected((prev) => { + const next = new Set(prev) + for (const r of rows) next.delete(r.id) + return next + }) + } else { + setSelected((prev) => { + const next = new Set(prev) + for (const r of rows) next.add(r.id) + return next + }) + } + }, [allVisibleSelected, rows]) + + const afterBulkOp = useCallback(() => { + setSelected(new Set()) + setRefreshKey((k) => k + 1) + refreshConversations() + }, [refreshConversations]) + + const selectedIds = useMemo(() => [...selected], [selected]) + const selectedCount = selected.size + + const handleBulkDelete = useCallback(async () => { + if (selectedIds.length === 0) return + setPending(true) + try { + const affected = rows.filter((r) => selected.has(r.id)) + await Promise.all(selectedIds.map((id) => deleteConversation(id))) + for (const conv of affected) { + closeConversationTab(conv.folder_id, conv.id, conv.agent_type) + } + toast.success(t("toastDeleted", { count: selectedIds.length })) + afterBulkOp() + } catch (e) { + toast.error( + t("toastOpFailed", { + message: e instanceof Error ? e.message : String(e), + }) + ) + } finally { + setPending(false) + setConfirmDelete(false) + } + }, [selectedIds, rows, selected, closeConversationTab, t, afterBulkOp]) + + const handleBulkStatus = useCallback( + async (status: ConversationStatus) => { + if (selectedIds.length === 0) return + setPending(true) + try { + await Promise.all( + selectedIds.map((id) => updateConversationStatus(id, status)) + ) + toast.success(t("toastStatusUpdated", { count: selectedIds.length })) + afterBulkOp() + } catch (e) { + toast.error( + t("toastOpFailed", { + message: e instanceof Error ? e.message : String(e), + }) + ) + } finally { + setPending(false) + } + }, + [selectedIds, t, afterBulkOp] + ) + + return ( + <> + + + + {t("title", { name: folderName })} + + + {/* Filter row */} +
+ setSearch(e.target.value)} + placeholder={t("searchPlaceholder")} + className="h-9 w-64" + /> +
+ + +
+
+ + {/* List container: select-all header + scrollable list */} +
+
+ + + {t("matchedCount", { count: rows.length })} + +
+ +
+ {loading ? ( + Array.from({ length: 6 }).map((_, i) => ( + + )) + ) : error ? ( +

+ {error} +

+ ) : rows.length === 0 ? ( +

+ {search.trim() || + agentFilter !== "all" || + statusFilter !== "all" + ? t("noMatchingConversations") + : t("noConversations")} +

+ ) : ( + rows.map((conv) => { + const checked = selected.has(conv.id) + return ( +
toggleOne(conv.id)} + className={cn( + "flex items-center gap-2 rounded-md px-2 py-1.5 cursor-pointer border border-transparent", + "hover:bg-accent/50", + checked && "bg-accent/40 border-accent/60" + )} + > + + + + {conv.title || t("untitledConversation")} + + + {t("messagesShort", { count: conv.message_count })} + + + {formatRelative(conv.updated_at)} + + +
+ ) + }) + )} +
+
+
+ + {/* Footer: bulk actions */} + + + {t("selectedCount", { count: selectedCount })} + +
+ {/* Set status */} + + + + + + {STATUS_ORDER.map((s) => ( + handleBulkStatus(s)} + > + + {tStatus(s)} + + ))} + + + + {/* Delete */} + + + +
+
+
+
+ + {/* Delete confirmation */} + !o && setConfirmDelete(false)} + > + + + + {t("confirmDeleteTitle", { count: selectedCount })} + + + {t("confirmDeleteDescription")} + + + + {tCommon("cancel")} + + {tCommon("confirm")} + + + + + + ) +} diff --git a/src/components/conversations/sidebar-conversation-list.tsx b/src/components/conversations/sidebar-conversation-list.tsx index c5cd7f3..64986b9 100644 --- a/src/components/conversations/sidebar-conversation-list.tsx +++ b/src/components/conversations/sidebar-conversation-list.tsx @@ -13,7 +13,14 @@ import { import { useTranslations } from "next-intl" import { toast } from "sonner" import { Virtualizer, type VirtualizerHandle } from "virtua" -import { ChevronRight, Download, Loader2, Plus, XCircle } from "lucide-react" +import { + ChevronRight, + Download, + ListChecks, + Loader2, + Plus, + XCircle, +} from "lucide-react" import { useActiveFolder } from "@/contexts/active-folder-context" import { useAppWorkspace } from "@/contexts/app-workspace-context" import { useTabContext } from "@/contexts/tab-context" @@ -31,6 +38,7 @@ import { saveFolderExpanded, } from "@/lib/sidebar-view-mode-storage" import { SidebarConversationCard } from "./sidebar-conversation-card" +import { ConversationManageDialog } from "./conversation-manage-dialog" import { Button } from "@/components/ui/button" import { Skeleton } from "@/components/ui/skeleton" import { ScrollArea } from "@/components/ui/scroll-area" @@ -112,6 +120,7 @@ const FolderHeader = memo(function FolderHeader({ onRemoveFromWorkspace, onNewConversation, onImport, + onManageConversations, t, }: { folderId: number @@ -123,6 +132,7 @@ const FolderHeader = memo(function FolderHeader({ onRemoveFromWorkspace: (folderId: number) => void onNewConversation: (folderId: number) => void onImport: (folderId: number) => void + onManageConversations: (folderId: number) => void t: ReturnType }) { return ( @@ -216,6 +226,11 @@ const FolderHeader = memo(function FolderHeader({ {importing ? t("importing") : t("importLocalSessions")} + onManageConversations(folderId)}> + + {t("folderHeaderMenu.manageConversations")} + + onRemoveFromWorkspace(folderId)} @@ -301,6 +316,10 @@ export function SidebarConversationList({ folderId: number folderName: string } | null>(null) + const [manageState, setManageState] = useState<{ + folderId: number + folderName: string + } | null>(null) useEffect(() => { // Hydrate from localStorage after mount to keep SSR/CSR markup consistent. @@ -483,6 +502,14 @@ export function SidebarConversationList({ [folderIndex] ) + const handleManageConversations = useCallback( + (folderId: number) => { + const name = folderIndex.get(folderId)?.name ?? String(folderId) + setManageState({ folderId, folderName: name }) + }, + [folderIndex] + ) + const handleRemoveFolderConfirm = useCallback(async () => { if (!removeConfirm) return const { folderId, folderName } = removeConfirm @@ -705,6 +732,7 @@ export function SidebarConversationList({ onRemoveFromWorkspace={handleRemoveFolder} onNewConversation={handleNewConversationForFolder} onImport={handleImportForFolder} + onManageConversations={handleManageConversations} t={t} /> @@ -735,6 +763,7 @@ export function SidebarConversationList({ onRemoveFromWorkspace={handleRemoveFolder} onNewConversation={handleNewConversationForFolder} onImport={handleImportForFolder} + onManageConversations={handleManageConversations} t={t} /> ) @@ -795,6 +824,15 @@ export function SidebarConversationList({ + + {manageState && ( + !o && setManageState(null)} + folderId={manageState.folderId} + folderName={manageState.folderName} + /> + )} ) } diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index b5ec1bb..a841e21 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -796,7 +796,29 @@ "removeFolderConfirmTitle": "إزالة المجلد من مساحة العمل؟", "removeFolderConfirmDescription": "إزالة \"{name}\" من مساحة العمل؟ سيتم إغلاق علامات التبويب والمحطات المرتبطة.", "folderHeaderMenu": { + "manageConversations": "إدارة المحادثات…", "removeFromWorkspace": "إزالة من مساحة العمل" + }, + "manageConversations": { + "title": "إدارة المحادثات — {name}", + "searchPlaceholder": "البحث بالعنوان…", + "agentFilterAll": "جميع الوكلاء", + "statusFilterAll": "جميع الحالات", + "selectAllVisible": "تحديد الكل", + "deselectAll": "إلغاء التحديد", + "selectedCount": "{count} محدد", + "matchedCount": "{count} مطابق", + "messagesShort": "{count} رسالة", + "untitledConversation": "محادثة بدون عنوان", + "setStatus": "تعيين الحالة…", + "deleteSelected": "حذف", + "noConversations": "لا توجد محادثات في هذا المجلد.", + "noMatchingConversations": "لا توجد محادثات مطابقة للفلاتر.", + "confirmDeleteTitle": "حذف {count} محادثة؟", + "confirmDeleteDescription": "لا يمكن التراجع عن هذا الإجراء.", + "toastDeleted": "تم حذف {count} محادثة", + "toastStatusUpdated": "تم تحديث حالة {count} محادثة", + "toastOpFailed": "فشلت العملية: {message}" } }, "conversation": { diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 953b01e..a257dd9 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -796,7 +796,29 @@ "removeFolderConfirmTitle": "Ordner aus Arbeitsbereich entfernen?", "removeFolderConfirmDescription": "\"{name}\" aus dem Arbeitsbereich entfernen? Zugehörige Tabs und Terminals werden geschlossen.", "folderHeaderMenu": { + "manageConversations": "Konversationen verwalten…", "removeFromWorkspace": "Aus Arbeitsbereich entfernen" + }, + "manageConversations": { + "title": "Konversationen verwalten — {name}", + "searchPlaceholder": "Nach Titel suchen…", + "agentFilterAll": "Alle Agenten", + "statusFilterAll": "Alle Status", + "selectAllVisible": "Alle auswählen", + "deselectAll": "Auswahl aufheben", + "selectedCount": "{count} ausgewählt", + "matchedCount": "{count} Treffer", + "messagesShort": "{count} Nachr.", + "untitledConversation": "Unbenannte Konversation", + "setStatus": "Status setzen…", + "deleteSelected": "Löschen", + "noConversations": "Keine Konversationen in diesem Ordner.", + "noMatchingConversations": "Keine Konversationen entsprechen den Filtern.", + "confirmDeleteTitle": "{count} Konversation(en) löschen?", + "confirmDeleteDescription": "Diese Aktion kann nicht rückgängig gemacht werden.", + "toastDeleted": "{count} Konversation(en) gelöscht", + "toastStatusUpdated": "Status von {count} Konversation(en) aktualisiert", + "toastOpFailed": "Aktion fehlgeschlagen: {message}" } }, "conversation": { diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 1074236..412652e 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -796,7 +796,29 @@ "removeFolderConfirmTitle": "Remove folder from workspace?", "removeFolderConfirmDescription": "Remove \"{name}\" from the workspace? Its tabs and terminals will close.", "folderHeaderMenu": { + "manageConversations": "Manage conversations…", "removeFromWorkspace": "Remove from workspace" + }, + "manageConversations": { + "title": "Manage conversations — {name}", + "searchPlaceholder": "Search by title…", + "agentFilterAll": "All agents", + "statusFilterAll": "All statuses", + "selectAllVisible": "Select all", + "deselectAll": "Deselect all", + "selectedCount": "{count} selected", + "matchedCount": "{count} matched", + "messagesShort": "{count} msg", + "untitledConversation": "Untitled conversation", + "setStatus": "Set status…", + "deleteSelected": "Delete", + "noConversations": "No conversations in this folder.", + "noMatchingConversations": "No conversations match the filters.", + "confirmDeleteTitle": "Delete {count} conversation(s)?", + "confirmDeleteDescription": "This action cannot be undone.", + "toastDeleted": "Deleted {count} conversation(s)", + "toastStatusUpdated": "Updated status for {count} conversation(s)", + "toastOpFailed": "Operation failed: {message}" } }, "conversation": { diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index cf536bb..fb9b046 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -796,7 +796,29 @@ "removeFolderConfirmTitle": "¿Eliminar carpeta del espacio de trabajo?", "removeFolderConfirmDescription": "¿Eliminar \"{name}\" del espacio de trabajo? Sus pestañas y terminales se cerrarán.", "folderHeaderMenu": { + "manageConversations": "Gestionar conversaciones…", "removeFromWorkspace": "Quitar del espacio de trabajo" + }, + "manageConversations": { + "title": "Gestionar conversaciones — {name}", + "searchPlaceholder": "Buscar por título…", + "agentFilterAll": "Todos los agentes", + "statusFilterAll": "Todos los estados", + "selectAllVisible": "Seleccionar todo", + "deselectAll": "Deseleccionar todo", + "selectedCount": "{count} seleccionada(s)", + "matchedCount": "{count} coincidencia(s)", + "messagesShort": "{count} msg", + "untitledConversation": "Conversación sin título", + "setStatus": "Cambiar estado…", + "deleteSelected": "Eliminar", + "noConversations": "No hay conversaciones en esta carpeta.", + "noMatchingConversations": "Ninguna conversación coincide con los filtros.", + "confirmDeleteTitle": "¿Eliminar {count} conversación(es)?", + "confirmDeleteDescription": "Esta acción no se puede deshacer.", + "toastDeleted": "Se eliminaron {count} conversación(es)", + "toastStatusUpdated": "Estado actualizado para {count} conversación(es)", + "toastOpFailed": "Error en la operación: {message}" } }, "conversation": { diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 3f7b224..52a801f 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -796,7 +796,29 @@ "removeFolderConfirmTitle": "Retirer le dossier de l'espace de travail ?", "removeFolderConfirmDescription": "Retirer \"{name}\" de l'espace de travail ? Les onglets et terminaux associés seront fermés.", "folderHeaderMenu": { + "manageConversations": "Gérer les conversations…", "removeFromWorkspace": "Retirer de l'espace de travail" + }, + "manageConversations": { + "title": "Gérer les conversations — {name}", + "searchPlaceholder": "Rechercher par titre…", + "agentFilterAll": "Tous les agents", + "statusFilterAll": "Tous les statuts", + "selectAllVisible": "Tout sélectionner", + "deselectAll": "Tout désélectionner", + "selectedCount": "{count} sélectionnée(s)", + "matchedCount": "{count} correspondance(s)", + "messagesShort": "{count} msg", + "untitledConversation": "Conversation sans titre", + "setStatus": "Définir le statut…", + "deleteSelected": "Supprimer", + "noConversations": "Aucune conversation dans ce dossier.", + "noMatchingConversations": "Aucune conversation ne correspond aux filtres.", + "confirmDeleteTitle": "Supprimer {count} conversation(s) ?", + "confirmDeleteDescription": "Cette action est irréversible.", + "toastDeleted": "{count} conversation(s) supprimée(s)", + "toastStatusUpdated": "Statut mis à jour pour {count} conversation(s)", + "toastOpFailed": "Échec de l'opération : {message}" } }, "conversation": { diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index fabba55..108d6a2 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -796,7 +796,29 @@ "removeFolderConfirmTitle": "このフォルダをワークスペースから削除しますか?", "removeFolderConfirmDescription": "\"{name}\" をワークスペースから削除しますか?関連するタブとターミナルが閉じられます。", "folderHeaderMenu": { + "manageConversations": "会話の管理…", "removeFromWorkspace": "ワークスペースから削除" + }, + "manageConversations": { + "title": "会話の管理 — {name}", + "searchPlaceholder": "タイトルで検索…", + "agentFilterAll": "すべてのエージェント", + "statusFilterAll": "すべてのステータス", + "selectAllVisible": "すべて選択", + "deselectAll": "選択を解除", + "selectedCount": "{count} 件選択中", + "matchedCount": "{count} 件該当", + "messagesShort": "{count} 件", + "untitledConversation": "無題の会話", + "setStatus": "ステータスを変更…", + "deleteSelected": "削除", + "noConversations": "このフォルダには会話がありません。", + "noMatchingConversations": "条件に一致する会話がありません。", + "confirmDeleteTitle": "{count} 件の会話を削除しますか?", + "confirmDeleteDescription": "この操作は元に戻せません。", + "toastDeleted": "{count} 件の会話を削除しました", + "toastStatusUpdated": "{count} 件の会話のステータスを更新しました", + "toastOpFailed": "操作に失敗しました: {message}" } }, "conversation": { diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 049d792..b461305 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -796,7 +796,29 @@ "removeFolderConfirmTitle": "이 폴더를 워크스페이스에서 제거하시겠습니까?", "removeFolderConfirmDescription": "워크스페이스에서 \"{name}\"을(를) 제거하시겠습니까? 관련 탭과 터미널이 닫힙니다.", "folderHeaderMenu": { + "manageConversations": "대화 관리…", "removeFromWorkspace": "워크스페이스에서 제거" + }, + "manageConversations": { + "title": "대화 관리 — {name}", + "searchPlaceholder": "제목으로 검색…", + "agentFilterAll": "모든 에이전트", + "statusFilterAll": "모든 상태", + "selectAllVisible": "전체 선택", + "deselectAll": "선택 해제", + "selectedCount": "{count}개 선택됨", + "matchedCount": "{count}개 일치", + "messagesShort": "{count}개", + "untitledConversation": "제목 없는 대화", + "setStatus": "상태 변경…", + "deleteSelected": "삭제", + "noConversations": "이 폴더에 대화가 없습니다.", + "noMatchingConversations": "필터에 일치하는 대화가 없습니다.", + "confirmDeleteTitle": "{count}개 대화를 삭제하시겠습니까?", + "confirmDeleteDescription": "이 작업은 되돌릴 수 없습니다.", + "toastDeleted": "{count}개 대화를 삭제했습니다", + "toastStatusUpdated": "{count}개 대화의 상태를 업데이트했습니다", + "toastOpFailed": "작업 실패: {message}" } }, "conversation": { diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 95d951d..03479d9 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -796,7 +796,29 @@ "removeFolderConfirmTitle": "Remover pasta do espaço de trabalho?", "removeFolderConfirmDescription": "Remover \"{name}\" do espaço de trabalho? As abas e terminais relacionados serão fechados.", "folderHeaderMenu": { + "manageConversations": "Gerenciar conversas…", "removeFromWorkspace": "Remover do espaço de trabalho" + }, + "manageConversations": { + "title": "Gerenciar conversas — {name}", + "searchPlaceholder": "Pesquisar por título…", + "agentFilterAll": "Todos os agentes", + "statusFilterAll": "Todos os status", + "selectAllVisible": "Selecionar tudo", + "deselectAll": "Desmarcar tudo", + "selectedCount": "{count} selecionada(s)", + "matchedCount": "{count} correspondência(s)", + "messagesShort": "{count} msg", + "untitledConversation": "Conversa sem título", + "setStatus": "Definir status…", + "deleteSelected": "Excluir", + "noConversations": "Sem conversas nesta pasta.", + "noMatchingConversations": "Nenhuma conversa corresponde aos filtros.", + "confirmDeleteTitle": "Excluir {count} conversa(s)?", + "confirmDeleteDescription": "Esta ação não pode ser desfeita.", + "toastDeleted": "{count} conversa(s) excluída(s)", + "toastStatusUpdated": "Status atualizado para {count} conversa(s)", + "toastOpFailed": "Falha na operação: {message}" } }, "conversation": { diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 8805294..5a79ae4 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -796,7 +796,29 @@ "removeFolderConfirmTitle": "从工作区移除该文件夹?", "removeFolderConfirmDescription": "从工作区移除 \"{name}\"?其相关 Tab 与终端将会关闭。", "folderHeaderMenu": { + "manageConversations": "会话管理…", "removeFromWorkspace": "从工作区移除" + }, + "manageConversations": { + "title": "会话管理 — {name}", + "searchPlaceholder": "按标题搜索…", + "agentFilterAll": "全部智能体", + "statusFilterAll": "全部状态", + "selectAllVisible": "全选", + "deselectAll": "取消全选", + "selectedCount": "已选 {count} 条", + "matchedCount": "匹配 {count} 条", + "messagesShort": "{count} 条", + "untitledConversation": "未命名会话", + "setStatus": "设为状态…", + "deleteSelected": "删除", + "noConversations": "此文件夹暂无会话。", + "noMatchingConversations": "没有匹配过滤条件的会话。", + "confirmDeleteTitle": "删除 {count} 个会话?", + "confirmDeleteDescription": "此操作不可撤销。", + "toastDeleted": "已删除 {count} 个会话", + "toastStatusUpdated": "已更新 {count} 个会话的状态", + "toastOpFailed": "操作失败:{message}" } }, "conversation": { diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index e2905c0..7008b32 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -796,7 +796,29 @@ "removeFolderConfirmTitle": "從工作區移除此資料夾?", "removeFolderConfirmDescription": "從工作區移除 \"{name}\"?相關分頁與終端機將會關閉。", "folderHeaderMenu": { + "manageConversations": "會話管理…", "removeFromWorkspace": "從工作區移除" + }, + "manageConversations": { + "title": "會話管理 — {name}", + "searchPlaceholder": "依標題搜尋…", + "agentFilterAll": "全部智能體", + "statusFilterAll": "全部狀態", + "selectAllVisible": "全選", + "deselectAll": "取消全選", + "selectedCount": "已選 {count} 筆", + "matchedCount": "符合 {count} 筆", + "messagesShort": "{count} 則", + "untitledConversation": "未命名會話", + "setStatus": "設為狀態…", + "deleteSelected": "刪除", + "noConversations": "此資料夾暫無會話。", + "noMatchingConversations": "沒有符合過濾條件的會話。", + "confirmDeleteTitle": "刪除 {count} 個會話?", + "confirmDeleteDescription": "此操作無法復原。", + "toastDeleted": "已刪除 {count} 個會話", + "toastStatusUpdated": "已更新 {count} 個會話的狀態", + "toastOpFailed": "操作失敗:{message}" } }, "conversation": {