From 25842eb33eb0342d9143a2990146353d8cc78123 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Fri, 19 Sep 2025 11:52:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(file-detail):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E8=AF=A6=E6=83=85=E7=BB=84=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0Markdown=E5=86=85=E5=AE=B9=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/FileDetailModal.vue | 126 +----- web/src/components/MarkdownContentViewer.vue | 404 +++++++++++++++++++ web/src/utils/chunkUtils.js | 134 ++++++ 3 files changed, 551 insertions(+), 113 deletions(-) create mode 100644 web/src/components/MarkdownContentViewer.vue create mode 100644 web/src/utils/chunkUtils.js diff --git a/web/src/components/FileDetailModal.vue b/web/src/components/FileDetailModal.vue index 6dbcc1b0..d9f85aa1 100644 --- a/web/src/components/FileDetailModal.vue +++ b/web/src/components/FileDetailModal.vue @@ -4,38 +4,13 @@ :title="file?.filename || '文件详情'" width="1200px" :footer="null" + wrap-class-name="file-detail" @after-open-change="afterOpenChange" + :bodyStyle="{ height: '80vh', padding: '0' }" >
-
-
- - {{ file.file_id }} -
-
- - {{ formatStandardTime(Math.round(file.created_at*1000)) }} -
-
- - - {{ getStatusText(file.status) }} - {{ file.lines?.length || 0 }} 行 - -
-
-
-

文件内容预览

-
-
- {{ index + 1 }} - {{ line.text || line }} -
-
+
@@ -71,68 +46,15 @@ const afterOpenChange = (open) => { // 导入工具函数 import { getStatusText, formatStandardTime } from '@/utils/file_utils'; +import MarkdownContentViewer from './MarkdownContentViewer.vue'; + + \ No newline at end of file diff --git a/web/src/components/MarkdownContentViewer.vue b/web/src/components/MarkdownContentViewer.vue new file mode 100644 index 00000000..8b9f3997 --- /dev/null +++ b/web/src/components/MarkdownContentViewer.vue @@ -0,0 +1,404 @@ + + + + + \ No newline at end of file diff --git a/web/src/utils/chunkUtils.js b/web/src/utils/chunkUtils.js new file mode 100644 index 00000000..850f247d --- /dev/null +++ b/web/src/utils/chunkUtils.js @@ -0,0 +1,134 @@ +/** + * Chunk合并工具函数 + * 用于合并多个chunk并处理重叠内容 + */ + +/** + * 查找两个字符串的重叠部分 + * @param {string} str1 - 第一个字符串 + * @param {string} str2 - 第二个字符串 + * @returns {string} - 重叠部分的内容 + */ +export function findOverlap(str1, str2) { + if (!str1 || !str2) return ''; + + const maxOverlap = Math.min(str1.length, str2.length); + let overlap = ''; + + // 从最长可能的重叠开始检查 + for (let i = maxOverlap; i > 0; i--) { + const endStr1 = str1.slice(-i); + const startStr2 = str2.slice(0, i); + + if (endStr1 === startStr2) { + overlap = endStr1; + break; + } + } + + return overlap; +} + +/** + * 合并chunks并处理重叠内容 + * @param {Array} chunks - chunk数组,每个chunk包含id, content, chunk_order_index + * @returns {Object} - 合并结果,包含content和chunks数组 + */ +export function mergeChunks(chunks) { + if (!chunks || chunks.length === 0) { + return { content: '', chunks: [] }; + } + + // 按order排序 + const sorted = [...chunks].sort((a, b) => a.chunk_order_index - b.chunk_order_index); + const merged = []; + let currentContent = ''; + + for (let i = 0; i < sorted.length; i++) { + const chunk = sorted[i]; + const content = chunk.content; + + if (i === 0) { + // 第一个chunk直接添加 + currentContent = content; + merged.push({ + ...chunk, + startOffset: 0, + endOffset: content.length + }); + } else { + // 查找重叠部分 + const overlap = findOverlap(currentContent, content); + const newContent = content.slice(overlap.length); + + if (newContent.length > 0) { + const startOffset = currentContent.length; + currentContent += newContent; + merged.push({ + ...chunk, + startOffset, + endOffset: currentContent.length + }); + } + } + } + + return { content: currentContent, chunks: merged }; +} + +/** + * 将文本分割成段落 + * @param {string} content - 文本内容 + * @returns {Array} - 段落数组 + */ +export function splitIntoParagraphs(content) { + if (!content) return []; + + // 按换行符分割,保留空段落 + return content.split(/\n\n+/).filter(para => para.trim() !== ''); +} + +/** + * 为每个段落找到对应的chunk + * @param {Array} paragraphs - 段落数组 + * @param {Array} mappedChunks - 映射后的chunks + * @returns {Array} - 包含chunk信息的段落 + */ +export function mapParagraphsToChunks(paragraphs, mappedChunks) { + if (!paragraphs || !mappedChunks) return []; + + let currentOffset = 0; + return paragraphs.map(paragraph => { + const paragraphLength = paragraph.length + 2; // +2 for the \n\n + + // 找到包含此位置的chunk + const chunk = mappedChunks.find(chunk => + currentOffset >= chunk.startOffset && currentOffset < chunk.endOffset + ) || mappedChunks[0]; + + const result = { + content: paragraph, + chunk, + startOffset: currentOffset, + endOffset: currentOffset + paragraphLength + }; + + currentOffset += paragraphLength; + return result; + }); +} + +/** + * 获取chunk的预览文本 + * @param {string} content - chunk内容 + * @param {number} maxLength - 最大长度 + * @returns {string} - 预览文本 + */ +export function getChunkPreview(content, maxLength = 100) { + if (!content) return ''; + + const text = content.replace(/\n+/g, ' ').trim(); + if (text.length <= maxLength) return text; + + return text.slice(0, maxLength) + '...'; +} \ No newline at end of file