import type { TextAnalysisAPI } from '../api/GLTR_API'; import type { AnalyzeResponse } from '../api/GLTR_API'; import { showDialog, createCombinedContent, showAlertDialog, showConfirmDialog } from '../ui/dialog'; import { createRawSnapshot } from '../utils/tokenUtils'; import { ServerStorage } from '../storage/demoStorage'; import { DemoStorageController } from './demoStorageController'; import type { IDemoStorage } from '../storage/demoStorage'; import { normalizeFolderPath, composeDemoFullPath, getDefaultDemoName, buildFolderOptions } from '../utils/demoPathUtils'; import { tr, trf } from '../lang/i18n-lite'; /** * 从保存结果中提取文件名,如果不存在则根据名称生成 */ const extractSavedFileName = (saveResult: any, defaultName: string): string => { if (saveResult && typeof saveResult.file === 'string' && saveResult.file.trim()) { return saveResult.file.trim(); } const trimmed = defaultName.trim(); return trimmed.toLowerCase().endsWith('.json') ? trimmed : `${trimmed}.json`; }; /** * 显示 demo 名称输入对话框 */ export const showDemoNameInput = ( api: TextAnalysisAPI, defaultName: string ): Promise<{ name: string, path: string } | null> => { const LAST_SAVE_PATH_KEY = 'lastSaveDemoPath'; return new Promise((resolve) => { // 先加载文件夹列表 api.list_all_folders() .then((result: { folders: string[] }) => { const folders = result.folders || []; // 获取上次保存的路径(从 localStorage) const lastSavePath = localStorage.getItem(LAST_SAVE_PATH_KEY); // 使用统一的 buildFolderOptions 函数 const { options: selectOptions, defaultPath } = buildFolderOptions(folders, lastSavePath); // 显示弹框 showDialog({ title: tr('Please enter demo name:'), content: createCombinedContent( tr('Demo name:'), defaultName, tr('Save directory:'), selectOptions, defaultPath ), onConfirm: (value: { input: string; select: string }) => { if (value && value.input) { // 保存选择的路径到 localStorage const selectedPath = value.select || '/'; localStorage.setItem(LAST_SAVE_PATH_KEY, selectedPath); resolve({ name: value.input, path: selectedPath }); } else { resolve(null); } }, onCancel: () => { resolve(null); }, cancelText: tr('Cancel') }); }) .catch((error) => { console.error('加载文件夹列表失败:', error); const errorMessage = error instanceof Error ? error.message : String(error); showAlertDialog(tr('Error'), trf('Failed to load folder list: {message}', { message: errorMessage })); resolve(null); }); }); }; export type ServerDemoSaveOptions = { api: TextAnalysisAPI; currentData: AnalyzeResponse | null; rawApiResponse: AnalyzeResponse | null; textFieldValue: string; enableDemo: boolean; demoManager?: { loadDemoByPath: (path: string) => Promise; highlightDemo: (path: string | null) => void; refresh: () => Promise; } | null; /** * 可选:直接传入已选的名称与目录,跳过名称输入弹窗 */ presetSaveInfo?: { name: string; path?: string | null; } | null; /** * 是否显示成功提示(使用 toast) * 默认为 true,设置为 false 时成功提示由调用者自行处理 * 注意:错误提示统一使用 alert,不受此参数影响 */ showSuccessToast?: boolean; /** * 可选的服务器存储实例(用于复用) * 如果不提供,将创建新的存储实例 */ serverStorage?: IDemoStorage; /** * 当前文件名(如果有,将作为默认文件名) */ currentFileName?: string | null; onSaveStart: () => void; onSaveSuccess: (name: string) => void; onSaveError: (error: Error) => void; setGlobalLoading: (loading: boolean) => void; showToast: (message: string, type: 'success' | 'error') => void; }; /** * 处理服务器 demo 保存逻辑 */ export const handleServerDemoSave = async (options: ServerDemoSaveOptions): Promise => { const { api, currentData, rawApiResponse, textFieldValue, enableDemo, demoManager, presetSaveInfo = null, showSuccessToast = true, serverStorage: providedStorage, currentFileName = null, onSaveStart, onSaveSuccess, onSaveError, setGlobalLoading, showToast } = options; if (!currentData || !rawApiResponse) { showAlertDialog(tr('Info'), tr('No data to save, please analyze text first')); return; } const LAST_SAVE_PATH_KEY = 'lastSaveDemoPath'; // 获取默认名称并显示输入对话框(或直接使用预设) let result: { name: string; path: string } | null = null; if (presetSaveInfo && presetSaveInfo.name && presetSaveInfo.name.trim()) { const normalizedPath = normalizeFolderPath(presetSaveInfo.path); result = { name: presetSaveInfo.name.trim(), path: normalizedPath }; // 记录最近路径 if (normalizedPath) { localStorage.setItem(LAST_SAVE_PATH_KEY, normalizedPath); } } else { const defaultName = getDefaultDemoName(currentData, textFieldValue, currentFileName); result = await showDemoNameInput(api, defaultName); } if (!result || !result.name || result.name.trim() === '') { return; // 用户取消或输入为空 } // 使用传入的存储实例或创建新的存储实例 const savePath = result.path || '/'; const storage = providedStorage || new ServerStorage(api); // 创建控制器(回调是动态的,所以每次都需要创建新控制器) const serverController = new DemoStorageController( storage, { onStart: onSaveStart, onSuccess: async (name, saveResult) => { // 保存成功后的处理 onSaveSuccess(name); // 注意:成功提示由 demoStorageController 统一处理(根据 showSuccessToast 参数) // 如果需要自定义提示,可以设置 showSuccessToast: false 并在 onSaveSuccess 回调中处理 // 保存成功后自动刷新demo列表并重新加载刚保存的demo if (enableDemo && demoManager && saveResult) { const savedFileName = extractSavedFileName(saveResult, name); const highlightPath = composeDemoFullPath(savePath, savedFileName); if (highlightPath) { // 保存成功后自动重新加载刚保存的demo try { const success = await demoManager.loadDemoByPath(highlightPath); if (success) { demoManager.highlightDemo(highlightPath); } } catch (err) { console.error('自动加载保存的demo失败:', err); } finally { // 无论加载成功失败,都刷新列表 await demoManager.refresh().catch(refreshErr => { console.error('刷新demo列表失败:', refreshErr); }); } } else { // 如果没有路径,只刷新列表 await demoManager.refresh().catch(err => { console.error('刷新demo列表失败:', err); }); } } }, onError: onSaveError, setLoading: setGlobalLoading, showToast, showSuccessToast } ); // 使用统一控制器保存 await serverController.save(rawApiResponse, { name: result.name.trim(), path: savePath }); };