From 9bbc28c14a3f287aba86519f9f7bc5d9c0ed9d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Thu, 23 Jul 2026 20:10:57 +0800 Subject: [PATCH] Add files via upload --- web/static/i18n/en-US.json | 2 + web/static/i18n/zh-CN.json | 2 + web/static/js/chat-files.js | 162 +++++++++++++++++++++++++++++------- web/templates/index.html | 1 + 4 files changed, 139 insertions(+), 28 deletions(-) diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 8645688e..cfc20d0f 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -2369,6 +2369,7 @@ "sourceAll": "All", "sourceUpload": "Chat uploads", "sourceReduction": "Tool outputs", + "sourceWorkspace": "Workspace files", "sourceConversationArtifact": "Conversation artifacts", "groupBy": "Group by", "groupNone": "None (flat list)", @@ -2385,6 +2386,7 @@ "browseRoot": "Files", "treeUploadsRoot": "Chat uploads", "treeReductionRoot": "Tool outputs", + "treeWorkspaceRoot": "Workspace files", "treeArtifactsRoot": "Conversation artifacts", "browseUp": "Up", "enterFolderTitle": "Open folder", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index 8c4e7ea9..7db25db0 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -2357,6 +2357,7 @@ "sourceAll": "全部", "sourceUpload": "对话附件", "sourceReduction": "工具输出", + "sourceWorkspace": "工作目录", "sourceConversationArtifact": "会话产物", "groupBy": "分组方式", "groupNone": "不分组(平铺)", @@ -2373,6 +2374,7 @@ "browseRoot": "文件", "treeUploadsRoot": "对话附件", "treeReductionRoot": "工具输出", + "treeWorkspaceRoot": "工作目录", "treeArtifactsRoot": "会话产物", "browseUp": "上级", "enterFolderTitle": "进入此文件夹", diff --git a/web/static/js/chat-files.js b/web/static/js/chat-files.js index 03084dab..c5fe3858 100644 --- a/web/static/js/chat-files.js +++ b/web/static/js/chat-files.js @@ -3,6 +3,8 @@ let chatFilesCache = []; /** 后端 GET /api/chat-uploads 返回的目录相对路径(含空文件夹),与 files 合并成树 */ let chatFilesFoldersCache = []; +const chatFilesProjectNameById = {}; +const chatFilesConversationTitleById = {}; let chatFilesDisplayed = []; let chatFilesEditRelativePath = ''; let chatFilesRenameRelativePath = ''; @@ -16,6 +18,7 @@ const CHAT_FILES_BROWSE_PATH_KEY = 'csai_chat_files_browse_path'; const CHAT_FILES_PAGE_SIZE_STORAGE_KEY = 'csai_chat_files_page_size'; const CHAT_FILES_TREE_UPLOAD_ROOT = 'uploads'; const CHAT_FILES_TREE_REDUCTION_ROOT = 'tool_outputs'; +const CHAT_FILES_TREE_WORKSPACE_ROOT = 'workspace'; const CHAT_FILES_TREE_ARTIFACT_ROOT = 'conversation_artifacts'; /** 按文件夹浏览模式下的当前路径(虚拟根段数组),如 ['uploads','2024-03-21','uuid'] */ @@ -39,6 +42,19 @@ function closeAllChatFilesFilterSelects() { }); } +function chatFilesRememberDisplayNames(files) { + Object.keys(chatFilesProjectNameById).forEach(function (k) { delete chatFilesProjectNameById[k]; }); + Object.keys(chatFilesConversationTitleById).forEach(function (k) { delete chatFilesConversationTitleById[k]; }); + (Array.isArray(files) ? files : []).forEach(function (f) { + const pid = String((f && f.projectId) || '').trim(); + const pname = String((f && f.projectName) || '').trim(); + if (pid && pname) chatFilesProjectNameById[pid] = pname; + const cid = String((f && f.conversationId) || '').trim(); + const ctitle = String((f && f.conversationTitle) || '').trim(); + if (cid && ctitle) chatFilesConversationTitleById[cid] = ctitle; + }); +} + function syncChatFilesFilterSelect(selectId) { const reg = chatFilesFilterSelectMap[selectId]; if (!reg) return; @@ -389,6 +405,7 @@ async function loadChatFilesPage() { } const data = await res.json(); chatFilesCache = Array.isArray(data.files) ? data.files : []; + chatFilesRememberDisplayNames(chatFilesCache); chatFilesFoldersCache = Array.isArray(data.folders) ? data.folders : []; chatFilesTotal = Number.isFinite(Number(data.total)) ? Number(data.total) : chatFilesCache.length; chatFilesPage = Number.isFinite(Number(data.page)) ? Math.max(1, Number(data.page)) : chatFilesPage; @@ -708,6 +725,10 @@ function chatFilesTreePathForFile(f) { rp = rp.replace(/^__reduction__\//, ''); return CHAT_FILES_TREE_REDUCTION_ROOT + '/' + rp; } + if (source === 'workspace') { + rp = rp.replace(/^__workspace__\//, ''); + return CHAT_FILES_TREE_WORKSPACE_ROOT + '/' + rp; + } if (source === 'conversation_artifact') { rp = rp.replace(/^__conversation_artifact__\//, ''); return CHAT_FILES_TREE_ARTIFACT_ROOT + '/' + rp; @@ -768,6 +789,7 @@ function chatFilesEnsureSourceRoots(root) { const roots = []; if (source === 'all' || source === 'upload') roots.push(CHAT_FILES_TREE_UPLOAD_ROOT); if (source === 'all' || source === 'reduction') roots.push(CHAT_FILES_TREE_REDUCTION_ROOT); + if (source === 'all' || source === 'workspace') roots.push(CHAT_FILES_TREE_WORKSPACE_ROOT); if (source === 'all' || source === 'conversation_artifact') roots.push(CHAT_FILES_TREE_ARTIFACT_ROOT); roots.forEach(function (name) { if (!root.dirs[name]) root.dirs[name] = chatFilesTreeMakeNode(); @@ -776,13 +798,16 @@ function chatFilesEnsureSourceRoots(root) { function chatFilesIsInternalSource(f) { const source = f && f.source; - return source === 'reduction' || source === 'conversation_artifact'; + return source === 'reduction' || source === 'workspace' || source === 'conversation_artifact'; } function chatFilesSourceLabel(source) { if (source === 'reduction') { return (typeof window.t === 'function') ? window.t('chatFilesPage.sourceReduction') : '工具输出'; } + if (source === 'workspace') { + return (typeof window.t === 'function') ? window.t('chatFilesPage.sourceWorkspace') : '工作目录'; + } if (source === 'conversation_artifact') { return (typeof window.t === 'function') ? window.t('chatFilesPage.sourceConversationArtifact') : '会话产物'; } @@ -808,12 +833,66 @@ function chatFilesTreeDisplayName(name) { if (name === CHAT_FILES_TREE_REDUCTION_ROOT) { return (typeof window.t === 'function') ? window.t('chatFilesPage.treeReductionRoot') : '工具输出'; } + if (name === CHAT_FILES_TREE_WORKSPACE_ROOT) { + return (typeof window.t === 'function') ? window.t('chatFilesPage.treeWorkspaceRoot') : '工作目录'; + } if (name === CHAT_FILES_TREE_ARTIFACT_ROOT) { return (typeof window.t === 'function') ? window.t('chatFilesPage.treeArtifactsRoot') : '会话产物'; } return name; } +function chatFilesIDDisplay(id, labelMap, emptyLabel) { + const raw = id == null ? '' : String(id); + if (!raw || raw === '—') { + return { text: emptyLabel || '—', title: '' }; + } + const label = String((labelMap && labelMap[raw]) || '').trim(); + if (label) { + return { text: label, title: label + ' (' + raw + ')' }; + } + if (raw.length > 36) { + return { text: raw.slice(0, 8) + '…' + raw.slice(-6), title: raw }; + } + return { text: raw, title: raw }; +} + +function chatFilesConversationDisplay(id) { + const c = id == null ? '' : String(id); + if (typeof window.t === 'function') { + if (c === '_manual') { + return { text: window.t('chatFilesPage.convManual'), title: '_manual' }; + } + if (c === '_new') { + return { text: window.t('chatFilesPage.convNew'), title: '_new' }; + } + } + return chatFilesIDDisplay(c, chatFilesConversationTitleById); +} + +function chatFilesProjectDisplay(id) { + const empty = (typeof window.t === 'function') ? window.t('chatFilesPage.projectUnbound') : '未绑定项目'; + return chatFilesIDDisplay(id, chatFilesProjectNameById, empty); +} + +function chatFilesTreePathDisplayName(pathParts) { + const parts = Array.isArray(pathParts) ? pathParts : []; + const name = parts.length ? parts[parts.length - 1] : ''; + const root = parts[0] || ''; + if ((root === CHAT_FILES_TREE_WORKSPACE_ROOT || root === CHAT_FILES_TREE_REDUCTION_ROOT) && parts.length === 3) { + if (parts[1] === 'projects') return chatFilesProjectDisplay(name); + if (parts[1] === 'conversations') return chatFilesConversationDisplay(name); + } + if (root === CHAT_FILES_TREE_ARTIFACT_ROOT && parts.length === 2) { + return chatFilesConversationDisplay(name); + } + if (root === CHAT_FILES_TREE_UPLOAD_ROOT && parts.length === 3) { + return chatFilesConversationDisplay(name); + } + const text = chatFilesTreeDisplayName(name); + return { text: text, title: String(name || '') }; +} + function chatFilesUploadRelativeDirFromBrowsePath(path) { const parts = Array.isArray(path) ? path.slice() : []; if (parts[0] === CHAT_FILES_TREE_UPLOAD_ROOT) { @@ -893,32 +972,15 @@ function chatFilesBuildGroups(files, mode) { /** 分组标题:长 ID 缩短展示,完整值放在 title */ function chatFilesGroupHeadingID(key, emptyLabel) { - const c = key == null ? '' : String(key); - if (c === '' || c === '—') { - return { text: emptyLabel || '—', title: '' }; - } - if (c.length > 36) { - return { text: c.slice(0, 8) + '…' + c.slice(-6), title: c }; - } - return { text: c, title: c }; + return chatFilesIDDisplay(key, null, emptyLabel); } function chatFilesGroupHeadingConversation(key) { - const c = key == null ? '' : String(key); - if (typeof window.t === 'function') { - if (c === '_manual') { - return { text: window.t('chatFilesPage.convManual'), title: '_manual' }; - } - if (c === '_new') { - return { text: window.t('chatFilesPage.convNew'), title: '_new' }; - } - } - return chatFilesGroupHeadingID(key); + return chatFilesConversationDisplay(key); } function chatFilesGroupHeadingProject(key) { - const empty = (typeof window.t === 'function') ? window.t('chatFilesPage.projectUnbound') : '未绑定项目'; - return chatFilesGroupHeadingID(key, empty); + return chatFilesProjectDisplay(key); } function renderChatFilesTable() { @@ -965,7 +1027,9 @@ function renderChatFilesTable() { const isInternal = chatFilesIsInternalSource(f); const sourceBadge = isInternal ? '' + escapeHtml(chatFilesSourceLabel(f.source)) + '' : ''; const conv = f.conversationId || ''; - const convEsc = escapeHtml(conv); + const convDisplay = chatFilesConversationDisplay(conv); + const convEsc = escapeHtml(convDisplay.text || conv); + const convTitleEsc = escapeHtml(convDisplay.title || conv); const dt = f.modifiedUnix ? new Date(f.modifiedUnix * 1000).toLocaleString() : '—'; const canOpenChat = conv && conv !== '_manual' && conv !== '_new'; @@ -1011,7 +1075,7 @@ function renderChatFilesTable() { return ` ${escapeHtml(f.date || '—')} - ${convEsc} + ${convEsc} ${subCellInner} ${nameEsc}${sourceBadge} ${formatChatFileBytes(f.size || 0)} @@ -1072,11 +1136,14 @@ function renderChatFilesTable() { for (bi = 0; bi < chatFilesBrowsePath.length; bi++) { const seg = chatFilesBrowsePath[bi]; const isLast = bi === chatFilesBrowsePath.length - 1; + const display = chatFilesTreePathDisplayName(chatFilesBrowsePath.slice(0, bi + 1)); + const displayText = escapeHtml(display.text); + const displayTitle = display.title ? ' title="' + escapeHtml(display.title) + '"' : ''; breadcrumbHtml += '/'; if (isLast) { - breadcrumbHtml += '' + escapeHtml(chatFilesTreeDisplayName(seg)) + ''; + breadcrumbHtml += '' + displayText + ''; } else { - breadcrumbHtml += ''; + breadcrumbHtml += ''; } } breadcrumbHtml += ''; @@ -1098,6 +1165,8 @@ function renderChatFilesTable() { function rowHtmlBrowseFolder(name) { const nameAttr = encodeURIComponent(String(name)); const folderPath = chatFilesBrowsePath.concat([name]); + const folderDisplay = chatFilesTreePathDisplayName(folderPath); + const folderTitle = folderDisplay.title || tEnter; const relToFolder = folderPath.join('/'); const uploadDirAttr = encodeURIComponent(relToFolder); const canUploadFolder = chatFilesBrowseCanUploadToPath(folderPath); @@ -1109,8 +1178,8 @@ function renderChatFilesTable() { ? `` : ''; return ` - - ${svgFolder}${escapeHtml(chatFilesTreeDisplayName(name))} + + ${svgFolder}${escapeHtml(folderDisplay.text)} — — @@ -1351,7 +1420,25 @@ function chatFilesDeleteFolderFromBtn(ev, btn) { async function copyChatFolderPathFromBrowse(folderName) { const segs = chatFilesBrowsePath.concat([folderName]); - const text = chatFilesClipboardFolderPath(segs); + const relativePath = chatFilesRelativePathFromTreeSegments(segs); + let text = ''; + if (relativePath) { + try { + const res = await apiFetch('/api/chat-uploads/path?kind=directory&path=' + encodeURIComponent(relativePath)); + if (!res.ok) { + const raw = await res.text(); + throw new Error(raw || String(res.status)); + } + const data = await res.json(); + text = String((data && data.absolutePath) || '').trim(); + } catch (e) { + alert((e && e.message) ? e.message : String(e)); + return; + } + } + if (!text) { + text = chatFilesClipboardFolderPath(segs); + } const ok = await chatFilesCopyText(text); if (ok) { const msg = (typeof window.t === 'function') ? window.t('chatFilesPage.folderPathCopied') : '目录路径已复制'; @@ -1362,6 +1449,25 @@ async function copyChatFolderPathFromBrowse(folderName) { } } +function chatFilesRelativePathFromTreeSegments(segs) { + const parts = Array.isArray(segs) ? segs.slice() : []; + if (!parts.length) return ''; + const root = parts.shift(); + if (root === CHAT_FILES_TREE_UPLOAD_ROOT) { + return parts.length ? parts.join('/') : '.'; + } + if (root === CHAT_FILES_TREE_REDUCTION_ROOT) { + return '__reduction__/' + parts.join('/'); + } + if (root === CHAT_FILES_TREE_WORKSPACE_ROOT) { + return '__workspace__/' + parts.join('/'); + } + if (root === CHAT_FILES_TREE_ARTIFACT_ROOT) { + return '__conversation_artifact__/' + parts.join('/'); + } + return [root].concat(parts).join('/'); +} + function chatFilesClipboardFolderPath(segs) { const parts = Array.isArray(segs) ? segs.slice() : []; if (!parts.length) return ''; diff --git a/web/templates/index.html b/web/templates/index.html index 5cab5501..16b5bd3a 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -2727,6 +2727,7 @@ +