fix(ui): sanitize json tool leaks, optimize markdown list and typography, update ai avatar

This commit is contained in:
lidf 2026-04-12 16:22:19 +08:00
parent 4fd682ff77
commit e87b91146d
4 changed files with 49 additions and 12 deletions

View File

@ -592,6 +592,9 @@ class DeepviewSSEServer:
else:
systemPrompt += f"\n\n## 通用模式\n企业知识库目录:{orgDir}/wiki/\n平台规则参考:{platformDir}/wiki/\n"
# 强调输出纪律,从源头杜绝输出底层 JSON 结构给前端
systemPrompt += "\n\n【严格输出规范】\n严禁在最终回答中输出任何 JSON 结构、工具调用过程记录、文件读取错误等底层调试信息。你必须将系统结果和发现转化为专业、流畅的文档级 Markdown 给医生阅读!\n"
# 3. 加载上下文履历,实现真正的 Stateful Context
history = db.get_messages_as_conversation(chatId)

View File

@ -395,28 +395,44 @@
.chat-send-btn:disabled { background: #cbd5e1; color: #f1f5f9; }
/* Markdown in Chat */
.markdown-body {
font-family: inherit;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
font-size: 15px;
color: #1e293b;
letter-spacing: 0.01em;
}
.markdown-body p {
margin-bottom: 8px;
margin-bottom: 12px;
}
.markdown-body p:last-child {
margin-bottom: 0;
}
.markdown-body strong {
font-weight: 700;
color: #0f172a;
}
.markdown-body ul, .markdown-body ol {
padding-left: 20px;
margin-bottom: 8px;
padding-left: 24px;
margin-bottom: 12px;
margin-top: 4px;
}
.markdown-body ul {
list-style-type: disc;
}
.markdown-body ol {
list-style-type: decimal;
}
.markdown-body li {
margin-bottom: 4px;
margin-bottom: 6px;
display: list-item;
}
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4 {
margin-top: 12px;
margin-bottom: 8px;
margin-top: 16px;
margin-bottom: 10px;
font-weight: 700;
line-height: 1.4;
line-height: 1.3;
color: #0f172a;
}
.markdown-body h3, .markdown-body h4 {
font-size: 16px;
}

View File

@ -136,7 +136,7 @@
<!-- AI 头像:方形 -->
<div class="msg-avatar ai-avatar" *ngIf="msg.role !== 'user'">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
</div>
<div class="msg-bubble markdown-body" [class.streaming]="msg.isStreaming" [innerHTML]="msg.htmlContent || msg.rawContent"></div>

View File

@ -127,7 +127,8 @@ export class App implements OnDestroy, OnInit {
const currentStreamingMsg = this.chatMessages.find(m => m.isStreaming && m.role === 'agent');
if (currentStreamingMsg) {
currentStreamingMsg.rawContent = (currentStreamingMsg.rawContent || '') + payload.text;
currentStreamingMsg.htmlContent = marked.parse(currentStreamingMsg.rawContent) as string;
const displayStr = this.sanitizeStreamingContent(currentStreamingMsg.rawContent);
currentStreamingMsg.htmlContent = marked.parse(displayStr) as string;
}
break;
case 'agent:thinking':
@ -141,7 +142,11 @@ export class App implements OnDestroy, OnInit {
activeStreamingMsg.isStreaming = false;
if (payload.fullAnswer) {
activeStreamingMsg.rawContent = payload.fullAnswer;
activeStreamingMsg.htmlContent = marked.parse(activeStreamingMsg.rawContent || '') as string;
const finalStr = this.sanitizeStreamingContent(activeStreamingMsg.rawContent || '');
activeStreamingMsg.htmlContent = marked.parse(finalStr) as string;
} else {
const finalFallbackStr = this.sanitizeStreamingContent(activeStreamingMsg.rawContent || '');
activeStreamingMsg.htmlContent = marked.parse(finalFallbackStr) as string;
}
}
break;
@ -458,7 +463,8 @@ export class App implements OnDestroy, OnInit {
if (res && res.messages && res.messages.length > 0) {
this.chatMessages = res.messages.map((m: any) => {
const rawContent = m.content || '';
const htmlContent = marked.parse(rawContent) as string;
const displayStr = this.sanitizeStreamingContent(rawContent);
const htmlContent = marked.parse(displayStr) as string;
return {
role: m.role,
rawContent: rawContent,
@ -509,4 +515,16 @@ export class App implements OnDestroy, OnInit {
}
}
private sanitizeStreamingContent(raw: string): string {
if (!raw) return '';
let text = raw;
// Strip markdown JSON block wrappings entirely
text = text.replace(/```json\s*\{[\s\S]*?\}\s*```/g, '');
// Strip bare unescaped JSON tool reports which match specific sigs
text = text.replace(/\{"content":[\s\S]*?\}/g, '');
text = text.replace(/\{"total_count":[\s\S]*?\}/g, '');
// Strip trailing tool garbage that might be partially yielded
return text.trim();
}
}