diff --git a/src/api/settings/workflow/session.ts b/src/api/settings/workflow/session.ts index 3e09b56..6d3913b 100644 --- a/src/api/settings/workflow/session.ts +++ b/src/api/settings/workflow/session.ts @@ -68,16 +68,27 @@ export function deleteSessionRecord(ids: { id: string; type: string }[], request // ===== 工作空间结果列表(/ai-agent/session/resultList)===== // 与内容创作 execution/list 同结构:data.tree(按 createDate 分组)+ data.imgAddressPrefix export interface ExecutionItem { - id: string; - timestamp: string; + // 兼容旧平铺结构;resultList 新结构下 items 无独立 id,删除按流程记录 flow.Id + id?: string; + timestamp?: string; content: string; label: string; type?: string; } +// resultList 新两级结构:createDate → flows(流程分组)→ items(文件) +export interface ExecutionFlow { + flowName: string; + Id: string; // 流程结果记录 id(雪花) + sessionId: string; + items: ExecutionItem[]; +} + export interface ExecutionTreeItem { createDate: string; - items: ExecutionItem[]; + // 新结构:日期下按流程分组;兼容旧平铺结构(无 flows 时回落 items) + flows?: ExecutionFlow[]; + items?: ExecutionItem[]; } export interface ExecutionListData { @@ -100,3 +111,13 @@ export function getWorkspaceResultList(params?: Record, requestOpti requestOptions, }) as Promise; } + +/** 删除工作空间结果(单个文件)——工作流域独立接口,不依赖内容创作域 */ +export function deleteWorkspaceResult(data: { id: string; content: string }, requestOptions?: RequestOptions) { + return request({ + url: '/ai-agent/session/resultDelete', + method: 'delete', + data, + requestOptions, + }); +} diff --git a/src/views/home/components/Sidebar.vue b/src/views/home/components/Sidebar.vue index 0dd2a59..7f5b888 100644 --- a/src/views/home/components/Sidebar.vue +++ b/src/views/home/components/Sidebar.vue @@ -47,12 +47,20 @@ +
+ 加载失败 + +
暂无会话记录
- +
+ 加载失败 + +
+
{{ dateNode.label }}
@@ -98,6 +106,11 @@ interface Props { activeMenu: string; activeHistoryId: string | null; historyList: HistoryItem[]; + // 会话记录 / 工作空间加载失败标记:失败时显示「加载失败 + 重试」错误态 + historyFailed?: boolean; + workspaceFailed?: boolean; + // 工作空间是否已成功加载:懒加载成功后才不再触发;失败保持 false → 切回 tab 自动重试 + workspaceLoaded?: boolean; treeNodes: TreeNode[]; treeLoading: boolean; } @@ -111,18 +124,23 @@ interface Emits { (e: 'download-node', data: TreeNode): void; (e: 'delete-node', data: TreeNode): void; (e: 'load-workspace'): void; + (e: 'retry-history'): void; + (e: 'retry-workspace'): void; } -const props = defineProps(); +const props = withDefaults(defineProps(), { + historyFailed: false, + workspaceFailed: false, + workspaceLoaded: false, +}); const emit = defineEmits(); const activeTab = ref<'history' | 'workspace'>('history'); -// 工作空间懒加载标记:首次切换到「工作空间」tab 时才通知父组件加载结果树,避免进入页面即拉取 -const workspaceLoaded = ref(false); const handleTabChange = (tab: 'history' | 'workspace') => { activeTab.value = tab; - if (tab === 'workspace' && !workspaceLoaded.value) { - workspaceLoaded.value = true; + // 懒加载:仅在「工作空间」tab 且尚未成功加载过时通知父组件拉取结果树,避免进入页面即请求; + // 失败时父组件不置 workspaceLoaded,切回 tab 自动重试 + if (tab === 'workspace' && !props.workspaceLoaded) { emit('load-workspace'); } }; @@ -282,6 +300,34 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data); padding: 32px 0; } +/* 加载失败错误态:可区分「失败」与「空数据」,并提供重试入口 */ +.panel-error { + padding: 32px 0; + text-align: center; +} + +.panel-error-text { + font-size: 12px; + color: #94a3b8; + margin-right: 8px; +} + +.panel-retry-btn { + border: 1px solid #dbeafe; + background: #eff6ff; + color: #2563eb; + font-size: 12px; + padding: 4px 14px; + border-radius: 8px; + cursor: pointer; + transition: background 0.15s; + outline: none; + + &:hover { + background: #dbeafe; + } +} + /* 历史记录条目 */ .history-item { display: flex; diff --git a/src/views/home/index.vue b/src/views/home/index.vue index a9951fd..3c03e62 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -4,8 +4,11 @@ :active-menu="activeMenu" :active-history-id="activeHistoryId" :history-list="historyList" + :history-failed="sessionHistoryFailed" :tree-nodes="treeNodes" :tree-loading="treeLoading" + :workspace-failed="workspaceTreeFailed" + :workspace-loaded="workspaceTreeLoaded" @menu-change="handleMenuChange" @new-chat="handleCreateHistory" @select-history="handleSelectHistory" @@ -14,6 +17,8 @@ @download-node="downloadNode" @delete-node="handleDeleteNode" @load-workspace="loadWorkspaceTree" + @retry-history="loadSessionHistory" + @retry-workspace="loadWorkspaceTree" />
(null); const treeLoading = ref(false); const treeNodes = ref([]); +// 会话记录 / 工作空间结果树加载失败标记:失败态可区分、提供重试入口 +const sessionHistoryFailed = ref(false); +const workspaceTreeFailed = ref(false); +// 工作空间已成功加载标记:懒加载成功后才不再触发;失败保持 false → 切回 tab 自动重试 +const workspaceTreeLoaded = ref(false); const imgAddressPrefix = ref(''); const previewDialogVisible = ref(false); const previewUrl = ref(''); @@ -176,14 +186,32 @@ const buildTreeNodes = (tree: ExecutionTreeItem[]): TreeNode[] => id: 'date-' + di, label: d.createDate, nodeType: 'date', - children: (d.items || []).map((item, ii) => ({ - id: 'item-' + di + '-' + ii, - label: item.label || '作品' + (ii + 1), - nodeType: 'title', - fileUrl: item.content, - fileType: item.type, - backendId: item.id, - })), + children: (() => { + // 新结构(createDate → flows → items):flows 下所有文件平铺到日期分组; + // 兼容旧平铺结构(createDate → items) + const flows = Array.isArray(d.flows) ? d.flows : []; + if (flows.length > 0) { + return flows.flatMap((flow, fi) => + (flow.items || []).map((item, ii) => ({ + id: 'item-' + di + '-' + fi + '-' + ii, + label: item.label || '作品' + (ii + 1), + nodeType: 'title', + fileUrl: item.content, + fileType: item.type, + // 新结构 items 无独立 id,删除按流程结果记录 id(flow.Id) + backendId: flow.Id ?? item.id, + })), + ); + } + return (d.items || []).map((item, ii) => ({ + id: 'item-' + di + '-' + ii, + label: item.label || '作品' + (ii + 1), + nodeType: 'title', + fileUrl: item.content, + fileType: item.type, + backendId: item.id, + })); + })(), })); const getPreviewMode = (fileType?: string): 'image' | 'video' | 'audio' | 'iframe' => { @@ -245,10 +273,10 @@ const handleDeleteNode = async (data: TreeNode) => { } catch { return; } - // 调后端接口删除 + // 调后端接口删除(工作空间结果:走 session 域独立接口) if (data.backendId && data.fileUrl) { try { - await deleteExecutionResult({ id: data.backendId, content: data.fileUrl }); + await deleteWorkspaceResult({ id: data.backendId, content: data.fileUrl }); } catch { // 后端已提示错误,继续本地删除 } @@ -288,9 +316,12 @@ const loadWorkspaceTree = async () => { const execRes = await getWorkspaceResultList(); imgAddressPrefix.value = execRes.data?.imgAddressPrefix || ''; treeNodes.value = buildTreeNodes(execRes.data?.tree || []); + workspaceTreeFailed.value = false; + // 成功后才标记已加载:失败保持 false,切回「工作空间」tab 时自动重试 + workspaceTreeLoaded.value = true; } catch { - treeNodes.value = []; - imgAddressPrefix.value = ''; + // 失败保留已有树数据(首次为空则显示错误态);不置 workspaceTreeLoaded 以便下次切 tab 重试 + workspaceTreeFailed.value = true; } finally { treeLoading.value = false; } @@ -325,6 +356,7 @@ const loadSessionHistory = async () => { }; }); historyList.value = sessions; + sessionHistoryFailed.value = false; // 为每个后端会话初始化消息存储 sessions.forEach((s) => { if (!sessionMessages.value.has(s.id)) { @@ -332,7 +364,8 @@ const loadSessionHistory = async () => { } }); } catch { - historyList.value = []; + // 失败保留已有会话数据(首次为空则显示错误态),标记失败供 UI 提供重试入口 + sessionHistoryFailed.value = true; } };