feat: add session export with full and compressed modes (#507)

Add export functionality that allows users to download session data
as JSON or plain text, with optional LLM-based context compression
for long conversations. Includes UI controls in chat panel, session
list, and history view, plus i18n strings for all 8 locales.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-05-07 13:49:57 +08:00
committed by GitHub
parent c0ad8c907b
commit 173307ef28
18 changed files with 554 additions and 14 deletions
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { renameSession, setSessionWorkspace, batchDeleteSessions } from "@/api/hermes/sessions";
import { renameSession, setSessionWorkspace, batchDeleteSessions, exportSession } from "@/api/hermes/sessions";
import { useChatStore, type Session } from "@/stores/hermes/chat";
import { useSessionBrowserPrefsStore } from "@/stores/hermes/session-browser-prefs";
import {
@@ -303,6 +303,28 @@ const contextMenuOptions = computed(() => [
},
{ label: t("chat.rename"), key: "rename" },
{ label: t("chat.setWorkspace"), key: "workspace" },
{
label: t("chat.export"),
key: "export",
children: [
{
label: t("chat.exportFull"),
key: "export-full",
children: [
{ label: "JSON", key: "export-full-json" },
{ label: "TXT", key: "export-full-txt" },
],
},
{
label: t("chat.exportCompressed"),
key: "export-compressed",
children: [
{ label: "JSON", key: "export-compressed-json" },
{ label: "TXT", key: "export-compressed-txt" },
],
},
],
},
{ label: t("chat.copySessionId"), key: "copy-id" },
]);
@@ -318,7 +340,15 @@ const showContextMenu = ref(false);
const contextMenuX = ref(0);
const contextMenuY = ref(0);
function handleContextMenuSelect(key: string) {
function parseExportKey(key: string): { mode: 'full' | 'compressed'; ext: 'json' | 'txt' } | null {
if (key === 'export-full-json') return { mode: 'full', ext: 'json' }
if (key === 'export-full-txt') return { mode: 'full', ext: 'txt' }
if (key === 'export-compressed-json') return { mode: 'compressed', ext: 'json' }
if (key === 'export-compressed-txt') return { mode: 'compressed', ext: 'txt' }
return null
}
async function handleContextMenuSelect(key: string) {
showContextMenu.value = false;
if (!contextSessionId.value) return;
if (key === "pin") {
@@ -327,6 +357,17 @@ function handleContextMenuSelect(key: string) {
}
if (key === "copy-id") {
copySessionId(contextSessionId.value);
} else if (parseExportKey(key)) {
const { mode, ext } = parseExportKey(key)!;
const loadingMsg = mode === "compressed" ? message.loading(t("chat.exportCompressing"), { duration: 0 }) : null;
try {
await exportSession(contextSessionId.value, mode, ext);
loadingMsg?.destroy();
message.success(t("chat.exportSuccess"));
} catch {
loadingMsg?.destroy();
message.error(t("chat.exportFailed"));
}
} else if (key === "workspace") {
const session = chatStore.sessions.find(
(s) => s.id === contextSessionId.value,