diff --git a/langgraph/src/components/MessageBubble.tsx b/langgraph/src/components/MessageBubble.tsx
index 17b9e18..6d8fb65 100644
--- a/langgraph/src/components/MessageBubble.tsx
+++ b/langgraph/src/components/MessageBubble.tsx
@@ -148,15 +148,40 @@ function CodeBlock({
);
}
+const CONCLUSION_KEYWORDS = /总结|结论|建议|综上|总体|总的来说|核心|要点|小结/;
+const FILLER_PATTERN = /^(根据|针对|您好|你好|好的|感谢|当然|如您所述|以下是|下面是)/;
+
function extractSummary(text: string): string {
+ const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
+
+ // 1. 优先:含总结关键词的段落首句
+ for (const line of lines) {
+ if (CONCLUSION_KEYWORDS.test(line) && line.length > 8 && !line.startsWith("#")) {
+ return line.replace(/^[*_#>\s-]+/, "").slice(0, 120);
+ }
+ }
+
+ // 2. 次优先:Markdown 标题(## / ###)之后的第一个非空行
+ for (let i = 0; i < lines.length; i++) {
+ if (/^#{2,3}\s/.test(lines[i]) && lines[i + 1]) {
+ const next = lines[i + 1].replace(/^[*_>\s-]+/, "");
+ if (next.length > 8) return next.slice(0, 120);
+ }
+ }
+
+ // 3. Fallback:跳过开场白,取第一个有实质内容的句子
const sentences = text.split(/(?<=[.。!!??])\s+/);
+ const meaningful = sentences.find((s) => s.length > 10 && !FILLER_PATTERN.test(s.trim()));
+ if (meaningful) return meaningful.trim().slice(0, 120);
+
+ // 4. 最终 fallback
return sentences.slice(0, 2).join(" ").trim();
}
export default function MessageBubble({ content }: MessageBubbleProps) {
const [summaryExpanded, setSummaryExpanded] = useState(false);
const contentLines = content.split("\n").length;
- const isLong = contentLines > 20;
+ const isLong = contentLines > 25 || content.length > 800;
const summary = isLong ? extractSummary(content) : null;
return (
@@ -166,7 +191,7 @@ export default function MessageBubble({ content }: MessageBubbleProps) {
{summary}…