From 51310683c7670683aad993e73f053befd5022993 Mon Sep 17 00:00:00 2001 From: jqb Date: Wed, 24 Jun 2026 11:35:39 +0800 Subject: [PATCH] refactor(sidebar): rewrite summary display to support structured summary parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构了侧边栏的每日总结组件,新增了结构化解析能力,可以识别核心诉求、处理结果、待办等区块和标签,优化了卡片样式和展示细节,提升了总结内容的可读性和美观度。 --- frontend/sidebar/src/SummaryApp.tsx | 377 ++++++++++++++++++++++++++-- 1 file changed, 355 insertions(+), 22 deletions(-) diff --git a/frontend/sidebar/src/SummaryApp.tsx b/frontend/sidebar/src/SummaryApp.tsx index e3edf9a..32ef826 100644 --- a/frontend/sidebar/src/SummaryApp.tsx +++ b/frontend/sidebar/src/SummaryApp.tsx @@ -19,6 +19,17 @@ interface SummaryItem { staffName?: string } +interface SummarySection { + title: string + items: string[] +} + +interface ParsedSummary { + sections: SummarySection[] + tags: string[] + raw: string +} + function SummaryApp() { const [loading, setLoading] = useState(true) const [list, setList] = useState([]) @@ -281,11 +292,11 @@ function SummaryApp() {
-
+
📋 每日总结
@@ -294,30 +305,352 @@ function SummaryApp() {
{list.map((item, index) => ( -
-
-
- {item.msgTime || formatDate(item.createdAt)} -
-
- {item.staffName || item.staffId || '未知老师'} -
-
-
- {item.summary || item.messageContext || '无内容'} -
-
+ ))}
) } +function SummaryCard({ item }: { item: SummaryItem }) { + const parsed = parseSummary(item.summary) + const hasStructure = parsed.sections.length > 0 || parsed.tags.length > 0 + + return ( +
+ {/* 头部 */} +
+
+ + + {item.msgTime || formatDate(item.createdAt)} + +
+
+ + + {item.staffName || item.staffId || '未知老师'} + +
+
+ + {hasStructure ? ( + <> + {parsed.sections.map((section, idx) => ( + + ))} + {parsed.tags.length > 0 && } + + ) : ( +
+ {item.summary || item.messageContext || '无内容'} +
+ )} +
+ ) +} + +function SectionBlock({ section }: { section: SummarySection }) { + const config = getSectionConfig(section.title) + const showTitle = displaySectionTitle(section.title) + + return ( +
+
+ {config.icon} + {showTitle} +
+
+ {section.items.map((text, idx) => ( +
+
+ {section.title.includes('核心诉求') ? ( + + ) : section.title.includes('处理结果') ? ( + + ) : section.title.includes('待办') ? ( + + ) : ( + + )} +
+
+ {text} +
+
+ ))} +
+
+ ) +} + +function TagList({ tags }: { tags: string[] }) { + return ( +
+
+ 🏷️ + 关键标签 +
+
+ {tags.map((tag, idx) => ( + + #{tag} + + ))} +
+
+ ) +} + +function getSectionConfig(title: string) { + if (title.includes('核心诉求')) { + return { icon: '📝', titleColor: '#1677ff' } + } + if (title.includes('处理结果')) { + return { icon: '✅', titleColor: '#52c41a' } + } + if (title.includes('待办') || title.includes('后续')) { + return { icon: '⏰', titleColor: '#fa8c16' } + } + return { icon: '•', titleColor: '#666' } +} + +function displaySectionTitle(title: string) { + if (title.includes('核心诉求')) return '学员咨询' + if (title.includes('处理结果')) return '老师解答' + if (title.includes('待办')) return '跟进建议' + return title +} + +function NumberBadge({ n }: { n: number }) { + return ( +
+ {n} +
+ ) +} + +function GreenCheck() { + return ( +
+ ✓ +
+ ) +} + +function OrangeClock() { + return ( +
+ 🕐 +
+ ) +} + +function Dot({ color }: { color: string }) { + return ( +
+ ) +} + +function CalendarIcon() { + return ( + + + + + + + ) +} + +function UserIcon() { + return ( + + + + + ) +} + +function parseSummary(text?: string): ParsedSummary { + if (!text) return { sections: [], tags: [], raw: '' } + + const result: ParsedSummary = { sections: [], tags: [], raw: text } + let content = text + + // 提取关键标签:支持 "**关键标签**:#a#b" 或 "关键标签:#a#b" 或单独行 + const tagPatterns = [ + /(?:\*\*)?关键标签(?:\*\*)?[::]\s*(.+?)(?=\n\s*\*\*|\n\s*$|$)/s, + /(?:\*\*)?标签(?:\*\*)?[::]\s*(.+?)(?=\n\s*\*\*|\n\s*$|$)/s + ] + for (const pattern of tagPatterns) { + const tagMatch = content.match(pattern) + if (tagMatch) { + result.tags = parseTags(tagMatch[1]) + content = content.replace(tagMatch[0], '') + break + } + } + + // 按 **标题** 分隔区块,标题可能有空格 + const sectionRegex = /\*\*\s*(核心诉求|处理结果|待办事项|待处理|后续跟进|关键标签)\s*\*\*/g + const parts: { title: string; text: string }[] = [] + let lastIndex = 0 + let lastTitle = '' + let match + + while ((match = sectionRegex.exec(content)) !== null) { + if (lastTitle) { + parts.push({ title: lastTitle, text: content.slice(lastIndex, match.index) }) + } + lastTitle = match[1] + lastIndex = match.index + match[0].length + } + if (lastTitle) { + parts.push({ title: lastTitle, text: content.slice(lastIndex) }) + } + + for (const part of parts) { + if (part.title === '关键标签') { + const tags = parseTags(part.text) + if (tags.length) result.tags = tags + continue + } + const items = parseItems(part.text) + if (items.length > 0) { + result.sections.push({ title: part.title, items }) + } + } + + // 没有任何结构化区块时,尝试整段按行解析列表 + if (result.sections.length === 0 && result.tags.length === 0) { + const items = parseItems(content) + if (items.length > 1) { + result.sections.push({ title: '总结', items }) + } + } + + return result +} + +function parseItems(text: string): string[] { + const lines = text.split('\n').map(l => l.trim()).filter(l => l.length > 0) + const items: string[] = [] + + for (const line of lines) { + // 匹配 1. / 1. / - / * / • 开头的列表项 + const match = line.match(/^(?:\d+[..]\s*|[\-*•]\s+)(.+)$/) + if (match) { + items.push(cleanText(match[1])) + } else if (items.length > 0 && !line.match(/^\*\*/)) { + // 续行(非标题) + items[items.length - 1] += ' ' + cleanText(line) + } + } + + // 如果整段没有列表项,把段落本身作为一条内容(兼容纯文本区块) + if (items.length === 0 && text.trim().length > 0) { + items.push(cleanText(text.trim())) + } + + return items +} + +function parseTags(text: string): string[] { + const s = text.trim() + if (!s) return [] + // 支持 #标签1#标签2 或 标签1#标签2 或 #标签1 #标签2 + return s.split(/[#\s]+/) + .map(t => t.trim()) + .filter(t => t.length > 0 && t !== '、' && t !== ',') +} + +function cleanText(s: string): string { + return s.replace(/\*\*/g, '').trim() +} + function formatDate(timestamp?: number) { if (!timestamp) return '-' const date = new Date(timestamp)