From cb36570cdeb9dd2ad25cbcb32d60ca832fd09a93 Mon Sep 17 00:00:00 2001 From: wizardchen Date: Thu, 2 Apr 2026 14:59:42 +0800 Subject: [PATCH] feat(summary): enhance document summarization capabilities and UI - Added max_input_chars configuration to limit input size for summary generation. - Updated generate_summary.yaml to clarify summary generation steps and requirements. - Introduced a new summary section in the document content view to display generated summaries or loading states. - Refactored doc-content.vue to utilize computed properties for improved performance and readability. - Enhanced knowledge base hooks to include description and summary status fields for better data handling. - Updated internationalization files to include new summary-related labels in multiple languages. --- config/config.yaml | 1 + config/prompt_templates/generate_summary.yaml | 22 +- frontend/src/components/doc-content.vue | 292 ++++++++++-------- frontend/src/hooks/useKnowledgeBase.ts | 8 +- frontend/src/i18n/locales/en-US.ts | 1 + frontend/src/i18n/locales/ko-KR.ts | 1 + frontend/src/i18n/locales/ru-RU.ts | 1 + frontend/src/i18n/locales/zh-CN.ts | 1 + internal/application/service/knowledge.go | 77 ++++- internal/config/config.go | 1 + 10 files changed, 264 insertions(+), 141 deletions(-) diff --git a/config/config.yaml b/config/config.yaml index f8310957..48209531 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -23,6 +23,7 @@ conversation: generate_summary_prompt_id: "default_summary" # from prompt_templates/generate_summary.yaml generate_session_title_prompt_id: "default_session_title" # from prompt_templates/generate_session_title.yaml summary: + max_input_chars: 16384 repeat_penalty: 1.0 temperature: 0.3 max_completion_tokens: 2048 diff --git a/config/prompt_templates/generate_summary.yaml b/config/prompt_templates/generate_summary.yaml index 9e0eed5f..c3fad973 100644 --- a/config/prompt_templates/generate_summary.yaml +++ b/config/prompt_templates/generate_summary.yaml @@ -5,12 +5,20 @@ templates: description: "Generate a concise document summary" default: true content: | - You are a precise document summarization expert. Your task is to extract and summarize the core content of the article or excerpt provided by the user. + You are a precise document summarization expert. Your task is to extract and summarize the core content of the document provided by the user. + + ## Steps + 1. Identify the document type from the metadata (e.g., technical doc, meeting notes, research paper, code, etc.) + 2. Extract 3-5 key points or main topics covered in the document + 3. Write a coherent summary incorporating these key points ## Core Requirements - - Summary length should be 100-300 words, adjusted flexibly based on content complexity - - Generate the summary entirely based on the provided content, without adding any information not present in the article + - Summary length: 100-500 words, adjusted based on content complexity + - Short/simple documents: 100-200 words + - Long/complex documents: 300-500 words + - Generate the summary entirely based on the provided content, without adding any information not present in the document - Ensure the summary captures key information points and main conclusions + - If the content contains "[...content omitted...]" markers, it is a sampled excerpt from a longer document — cover ALL topics that appear across the provided sections, not just the beginning - Even for complex or specialized content, you must attempt to extract core points for summarization - Output the summary directly, without any preamble, prefix, or explanation @@ -18,13 +26,13 @@ templates: - Use an objective, neutral third-person narrative tone - Maintain logical coherence with smooth transitions between sentences - Avoid repetitive use of the same expressions or sentence structures + - For technical documents: preserve key terms, metrics, and specific details + - For meeting notes/reports: highlight decisions, action items, and conclusions ## Important Notes - NEVER output refusal phrases such as "unable to generate", "unable to summarize", or "insufficient content" - - Do not copy or reference any content from examples; ensure the summary is entirely based on the user's new article + - Do not copy or reference any content from examples; ensure the summary is entirely based on the user's document - Make every effort to extract key points and summarize for any text, regardless of length or complexity - ## Requirements: + ## Language - Use {{language}} for all outputs - - ## The following is the article information provided by the user: diff --git a/frontend/src/components/doc-content.vue b/frontend/src/components/doc-content.vue index 04e77adf..89ab3eac 100644 --- a/frontend/src/components/doc-content.vue +++ b/frontend/src/components/doc-content.vue @@ -5,7 +5,7 @@ import { marked } from "marked"; import hljs from "highlight.js"; import "highlight.js/styles/github.css"; import mermaid from "mermaid"; -import { onMounted, ref, nextTick, onUnmounted, watch } from "vue"; +import { onMounted, ref, nextTick, onUnmounted, watch, computed } from "vue"; import { downKnowledgeDetails, deleteGeneratedQuestion, getChunkByIdOnly } from "@/api/knowledge-base/index"; import { MessagePlugin, DialogPlugin } from "tdesign-vue-next"; import { sanitizeHTML, safeMarkdownToHTML, createSafeImage, isValidImageURL, hydrateProtectedFileImages } from '@/utils/security'; @@ -66,8 +66,7 @@ let url = ref('') // file 类型默认「预览」,URL / 手动创建 默认「全文」 const viewMode = ref<'chunks' | 'merged' | 'preview'>('merged'); -// 合并后的文档内容 -const mergedContent = ref(''); +// 合并后的文档内容(在下方通过 computed 定义) /** * 根据 start_at 和 end_at 字段合并有 overlap 的 chunks @@ -136,8 +135,11 @@ const mergeChunks = (chunks: any[]): string => { onMounted(() => { nextTick(() => { - doc = document.getElementsByClassName('t-drawer__body')[0] - doc.addEventListener('scroll', handleDetailsScroll); + const drawers = document.getElementsByClassName('t-drawer__body'); + if (drawers && drawers.length > 0) { + doc = drawers[0]; + doc.addEventListener('scroll', handleDetailsScroll); + } }) }) watch(() => props.details?.id, () => { @@ -162,7 +164,9 @@ watch(() => props.details?.chunkLoading, (val) => { } }); onUnmounted(() => { - doc.removeEventListener('scroll', handleDetailsScroll); + if (doc) { + doc.removeEventListener('scroll', handleDetailsScroll); + } }) const checkImage = (url) => { return new Promise((resolve) => { @@ -223,14 +227,28 @@ renderer.code = function ({text, lang}) { `; }; -// 监听 chunks 变化,自动更新合并内容 -watch(() => props.details?.md, (newChunks) => { +// 监听 chunks 变化,自动更新合并内容(已改为 computed 属性) +const mergedContent = computed(() => { + const newChunks = props.details?.md; if (newChunks && newChunks.length > 0) { - mergedContent.value = mergeChunks(newChunks); - } else { - mergedContent.value = ''; + return mergeChunks(newChunks); } -}, { immediate: true, deep: true }); + return ''; +}); + +// 计算处理后的分块数据,避免在模板中频繁调用方法和 JSON.parse +const processedChunks = computed(() => { + return (props.details?.md || []).map((item: any, index: number) => { + return { + original: item, + processedContent: processMarkdown(item.content), + questions: getGeneratedQuestions(item), + meta: getChunkMeta(item), + hasParent: hasParentChunk(item), + chunkClass: getChunkClass(index) + }; + }); +}); const previewSupportedTypes = new Set([ 'pdf', 'docx', 'pptx', 'ppt', 'xlsx', 'xls', 'csv', @@ -353,8 +371,11 @@ const bindMermaidClickEvents = () => { const processMarkdown = (markdownText) => { if (!markdownText || typeof markdownText !== 'string') return ''; + // 去除 Markdown 头部的 YAML Frontmatter(例如 --- title: xxx ---) + let processedText = markdownText.replace(/^\s*---\r?\n[\s\S]*?\r?\n---\r?\n/, ''); + // 先还原原始文本中的 HTML 实体,让它们作为普通字符参与渲染 - let processedText = markdownText + processedText = processedText .replace(/'/g, "'") .replace(/'/gi, "'") .replace(/'/g, "'") @@ -387,7 +408,7 @@ const processMarkdown = (markdownText) => { }; const handleClose = () => { emit("closeDoc", false); - doc.scrollTop = 0; + if (doc) doc.scrollTop = 0; viewMode.value = 'merged'; }; @@ -724,6 +745,19 @@ const handleDetailsScroll = () => { + +
+ {{ $t('knowledgeBase.documentSummary') }} +
{{ details.description }}
+
+
+ {{ $t('knowledgeBase.documentSummary') }} +
+ + {{ $t('knowledgeBase.generatingSummary') }} +
+
+
@@ -733,10 +767,12 @@ const handleDetailsScroll = () => {
- {{ getTimeLabel() }}:{{ details.time }} - - {{ getChannelLabel(details.channel) }} - +
+ {{ getTimeLabel() }}:{{ details.time }} + + {{ getChannelLabel(details.channel) }} + +
{
-
{{ $t('common.noData') }}
+
{{ $t('common.noData') }}
{{ $t('knowledgeBase.segment') }} {{ index + 1 }}
{ {{ $t('knowledgeBase.childChunk') }} - {{ $t('knowledgeBase.questions') }} {{ getGeneratedQuestions(item).length }} + {{ $t('knowledgeBase.questions') }} {{ chunk.questions.length }} - {{ getChunkMeta(item) }} + {{ chunk.meta }}
-
+
-
-
+
+
{{ $t('knowledgeBase.viewParentContext') }}
-
+
-
+
- {{ $t('knowledgeBase.generatedQuestions') }} ({{ getGeneratedQuestions(item).length }}) + {{ $t('knowledgeBase.generatedQuestions') }} ({{ chunk.questions.length }})
@@ -843,7 +878,7 @@ const handleDetailsScroll = () => { size="small" class="delete-question-btn" :loading="isDeleting(index, question.id)" - @click.stop="handleDeleteQuestion(item, index, question)" + @click.stop="handleDeleteQuestion(chunk.original, index, question)" >