Studio: bulk export and import in Settings Chat Data, MCP pill off switch (#6141)
* Studio: bulk export and import in Settings Chat Data, MCP pill off switch - Settings -> Chat -> Data gains Export Recents and Projects (Recents or Recents + Projects, Raw JSONL / CSV / ShareGPT, combined or per chat) and Import chats, reusing the sidebar Recents menu actions - Extract bulkExportConversationsByScope so the sidebar and Settings share one implementation; expose the export and import helpers via the chat feature index - MCP composer pill icon now swaps to an X on hover like Search, Code and RAG; clicking it turns MCP off without opening the server menu - en and zh-CN locale strings added (parity check passes) * Reveal the pill X on hover for off-switch icons regardless of active look The X was gated on data-active, so an MCP pill with no servers enabled (or a RAG pill without a model) closed on icon click but never showed the affordance. Off-switch pills only render while their feature is on, so hover now always reveals the X. * Replace the Recents hamburger menu with an Export all chats link to Settings Bulk export and import now live in Settings -> Chat -> Data, so the sidebar Recents header menu is gone. Each chat's Export submenu gains Export all chats, which opens Settings on the Chat tab.
This commit is contained in:
parent
62191c4765
commit
dfb49fd2cb
8 changed files with 233 additions and 115 deletions
|
|
@ -13,15 +13,32 @@ import {
|
|||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
EXPORT_FORMATS_LIST,
|
||||
bulkExportConversationsByScope,
|
||||
clearAllChats,
|
||||
countAllChats,
|
||||
downloadChatExport,
|
||||
importConversationsFromFile,
|
||||
useChatRuntimeStore,
|
||||
} from "@/features/chat";
|
||||
import { useT } from "@/i18n";
|
||||
import { Delete02Icon, Download01Icon } from "@hugeicons/core-free-icons";
|
||||
import {
|
||||
Delete02Icon,
|
||||
Download01Icon,
|
||||
Upload01Icon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { SettingsRow } from "../components/settings-row";
|
||||
import { SettingsSection } from "../components/settings-section";
|
||||
|
||||
|
|
@ -61,6 +78,25 @@ export function ChatTab() {
|
|||
}
|
||||
};
|
||||
|
||||
const importInputRef = useRef<HTMLInputElement>(null);
|
||||
const handleImport = async (file: File) => {
|
||||
try {
|
||||
const imported = await importConversationsFromFile(file, null);
|
||||
if (imported === 0) {
|
||||
toast.info(t("settings.chat.importNoConversations"));
|
||||
} else {
|
||||
toast.success(
|
||||
imported === 1
|
||||
? t("settings.chat.importedOneChat")
|
||||
: t("settings.chat.importedChatCount", { count: imported }),
|
||||
);
|
||||
setCount(await countAllChats().catch(() => count));
|
||||
}
|
||||
} catch {
|
||||
toast.error(t("settings.chat.importFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleClear = async () => {
|
||||
setClearing(true);
|
||||
try {
|
||||
|
|
@ -172,6 +208,89 @@ export function ChatTab() {
|
|||
</Button>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
label={t("settings.chat.exportConversations")}
|
||||
description={t("settings.chat.exportConversationsDescription")}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild={true}>
|
||||
<Button variant="outline" size="sm" disabled={count === 0}>
|
||||
<HugeiconsIcon
|
||||
icon={Download01Icon}
|
||||
className="size-3.5 mr-1.5"
|
||||
/>
|
||||
{t("settings.chat.exportConversationsAction")}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
{(
|
||||
[
|
||||
{ scope: "recents", label: "exportScopeRecents" },
|
||||
{ scope: "all", label: "exportScopeAll" },
|
||||
] as const
|
||||
).map(({ scope, label }) => (
|
||||
<DropdownMenuSub key={scope}>
|
||||
<DropdownMenuSubTrigger>
|
||||
<HugeiconsIcon
|
||||
icon={Download01Icon}
|
||||
className="size-3.5 mr-1"
|
||||
/>
|
||||
{t(`settings.chat.${label}`)}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="w-56">
|
||||
{EXPORT_FORMATS_LIST.map(({ fmt, label: fmtLabel }) => (
|
||||
<DropdownMenuItem
|
||||
key={`${scope}-m-${fmt}`}
|
||||
onSelect={() =>
|
||||
void bulkExportConversationsByScope(scope, fmt, true)
|
||||
}
|
||||
>
|
||||
{fmtLabel} {t("settings.chat.exportCombinedSuffix")}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
{EXPORT_FORMATS_LIST.map(({ fmt, label: fmtLabel }) => (
|
||||
<DropdownMenuItem
|
||||
key={`${scope}-s-${fmt}`}
|
||||
onSelect={() =>
|
||||
void bulkExportConversationsByScope(scope, fmt, false)
|
||||
}
|
||||
>
|
||||
{fmtLabel} {t("settings.chat.exportPerChatSuffix")}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
label={t("settings.chat.importChats")}
|
||||
description={t("settings.chat.importChatsDescription")}
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => importInputRef.current?.click()}
|
||||
>
|
||||
<HugeiconsIcon icon={Upload01Icon} className="size-3.5 mr-1.5" />
|
||||
{t("settings.chat.importChatsAction")}
|
||||
</Button>
|
||||
<input
|
||||
ref={importInputRef}
|
||||
type="file"
|
||||
accept=".jsonl,.ndjson,.csv"
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
e.target.value = "";
|
||||
if (file) void handleImport(file);
|
||||
}}
|
||||
/>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
destructive
|
||||
label={t("settings.chat.clearAllChats")}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue