fix: 优化思考流稳定性与 agent 区块交互样式

This commit is contained in:
XianYuDaXian committed 2026-03-10 06:55:24 +08:00
1 parent 5c0bb6fb4e
commit 84f365dba3
3 files changed
+139 -53

No files matched your search

+29 -9
View File
@@ -419,6 +419,9 @@ class StreamProcessor(proc_base.BaseProcessor):
self.rollout_id: str = ""
self.fingerprint: str = ""
self.think_opened: bool = False
self.seen_think_once: bool = False
self.image_think_active: bool = False
self.answer_buffer: list[str] = []
self.role_sent: bool = False
self.filter_tags = get_config("app.filter_tags")
self.tool_usage_enabled = (
@@ -566,12 +569,11 @@ class StreamProcessor(proc_base.BaseProcessor):
if img := resp.get("streamingImageGenerationResponse"):
if not self.show_think:
continue
if is_thinking and not self.think_opened:
self.image_think_active = True
if not self.think_opened:
yield self._sse("<think>\n")
self.think_opened = True
if (not is_thinking) and self.think_opened:
yield self._sse("\n</think>\n")
self.think_opened = False
self.seen_think_once = True
idx = img.get("imageIndex", 0) + 1
progress = img.get("progress", 0)
yield self._sse(
@@ -580,6 +582,7 @@ class StreamProcessor(proc_base.BaseProcessor):
continue
if mr := resp.get("modelResponse"):
self.image_think_active = False
for url in proc_base._collect_images(mr):
parts = url.split("/")
img_id = parts[-2] if len(parts) >= 2 else "image"
@@ -622,20 +625,37 @@ class StreamProcessor(proc_base.BaseProcessor):
filtered = self._filter_token(token)
if not filtered:
continue
if is_thinking:
# 避免上游字面 <think> 标签与前端思考分段逻辑冲突,导致多段思考块
filtered = re.sub(r"</?think>", "", filtered, flags=re.IGNORECASE)
if not filtered:
continue
in_think = (
is_thinking
or self.image_think_active
)
if in_think:
if not self.show_think:
continue
if not self.think_opened:
yield self._sse("<think>\n")
self.think_opened = True
else:
if self.think_opened:
yield self._sse("\n</think>\n")
self.think_opened = False
self.seen_think_once = True
yield self._sse(filtered)
continue
# 一旦进入思考模式,先缓存正文,等思考结束后再统一输出
if self.show_think and self.seen_think_once:
self.answer_buffer.append(filtered)
continue
yield self._sse(filtered)
if self.think_opened:
yield self._sse("</think>\n")
self.think_opened = False
if self.answer_buffer:
yield self._sse("".join(self.answer_buffer))
self.answer_buffer = []
yield self._sse(finish="stop")
yield "data: [DONE]\n\n"
except asyncio.CancelledError:
+58 -7
View File
@@ -484,7 +484,10 @@ body {
}
.think-agent {
border: 1px solid var(--chat-border);
background: var(--chat-surface);
border-radius: 12px;
padding: 8px 10px 10px;
width: 100%;
}
@@ -499,6 +502,9 @@ body {
gap: 8px;
padding: 4px 8px;
border-radius: 999px;
background: transparent;
width: auto;
max-width: 100%;
}
.think-agent summary::before {
@@ -518,7 +524,7 @@ body {
border-right: 2px solid var(--accents-4);
border-bottom: 2px solid var(--accents-4);
transform: rotate(45deg);
margin-left: 4px;
margin-left: 2px;
}
.think-agent[open] summary::after {
@@ -531,15 +537,15 @@ body {
@keyframes thinkGlow {
0% {
box-shadow: 0 0 0 rgba(255, 255, 255, 0);
box-shadow: 0 0 0 rgba(17, 17, 17, 0);
background: transparent;
}
50% {
box-shadow: 0 0 12px rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.14);
background: rgba(14, 165, 233, 0.08);
}
100% {
box-shadow: 0 0 0 rgba(255, 255, 255, 0);
box-shadow: 0 0 0 rgba(17, 17, 17, 0);
background: transparent;
}
}
@@ -551,7 +557,8 @@ body {
.think-agent-items {
margin-top: 10px;
margin-left: 0;
padding-left: 0;
padding-left: 2px;
padding-right: 2px;
border-left: none;
display: flex;
flex-direction: column;
@@ -623,7 +630,10 @@ body {
.think-item-row {
border-left: none;
padding-left: 0;
padding: 8px 10px;
border: 1px solid var(--chat-border);
border-radius: 10px;
background: rgba(17, 17, 17, 0.02);
display: flex;
flex-direction: column;
gap: 6px;
@@ -668,6 +678,47 @@ body {
line-height: 1.6;
}
html[data-theme='dark'] .think-agent {
background: #101924;
border-color: #273142;
}
html[data-theme='dark'] .think-agent summary {
background: transparent;
}
html[data-theme='dark'] .think-item-row {
background: #0f1824;
border-color: #273142;
}
html[data-theme='dark'] .think-item-type {
color: #9fb2c9;
}
html[data-theme='dark'] .think-item-body {
color: #d5e0ee;
}
@keyframes thinkGlowDark {
0% {
box-shadow: 0 0 0 rgba(96, 165, 250, 0);
background: transparent;
}
50% {
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.2);
background: rgba(96, 165, 250, 0.08);
}
100% {
box-shadow: 0 0 0 rgba(96, 165, 250, 0);
background: transparent;
}
}
html[data-theme='dark'] .think-block[data-thinking="true"] .think-agent summary {
animation: thinkGlowDark 1.6s ease-in-out infinite;
}
.composer-shell {
position: sticky;
bottom: 8px;
+52 -37
View File
@@ -799,49 +799,37 @@
const ids = Array.from(new Set(list.map((b) => String(b.id || '').trim()).filter(Boolean)));
const nonGeneralIds = ids.filter((id) => !/^general$/i.test(id));
// 优先按 rollout id 分组,能更稳定地映射多 agent。
if (nonGeneralIds.length >= 2) {
const groups = [];
const map = new Map();
for (const block of list) {
const key = String(block.id || 'General');
let group = map.get(key);
if (!group) {
group = { key, blocks: [] };
map.set(key, group);
groups.push(group);
}
group.blocks.push(block);
}
return groups.map((group, idx) => ({
title: idx === 0 ? 'Grok Leader' : `Agent ${idx}`,
blocks: group.blocks
}));
if (nonGeneralIds.length <= 1) {
return [];
}
// 兜底:按 AgentThink 作为阶段收束点切分。
const sections = [];
let current = [];
const groups = [];
const map = new Map();
for (const block of list) {
current.push(block);
if (/^agentthink$/i.test(String(block.type || '').trim())) {
sections.push(current);
current = [];
const key = String(block.id || 'General');
let group = map.get(key);
if (!group) {
group = { key, blocks: [] };
map.set(key, group);
groups.push(group);
}
group.blocks.push(block);
}
if (current.length) {
sections.push(current);
}
if (sections.length <= 1) {
return [{ title: 'Grok Leader', blocks: list }];
}
return sections.map((part, idx) => ({
return groups.map((group, idx) => ({
title: idx === 0 ? 'Grok Leader' : `Agent ${idx}`,
blocks: part
blocks: group.blocks
}));
}
function renderFlatBlocks(blocks) {
return (Array.isArray(blocks) ? blocks : []).map((item) => {
const body = renderBasicMarkdown((item.lines || []).join('\n').trim());
const typeText = escapeHtml(item.type);
const typeKey = String(item.type || '').trim().toLowerCase().replace(/\s+/g, '');
const typeAttr = escapeHtml(typeKey);
return `<div class="think-item-row"><div class="think-item-type" data-type="${typeAttr}">${typeText}</div><div class="think-item-body">${body || '<em>(空)</em>'}</div></div>`;
}).join('');
}
function renderThinkContent(text, openAll) {
const sections = parseAgentSections(text);
if (!sections.length) {
@@ -878,9 +866,9 @@
const blocks = parseRolloutBlocks(section.lines.join('\n'), section.title || 'General');
if (!section.title && blocks.length) {
const synthetic = splitBlocksIntoSyntheticAgents(blocks);
if (synthetic.length > 1) {
if (synthetic.length) {
return synthetic.map((agent, agentIdx) => {
const inner = renderGroups(agent.blocks, openAll);
const inner = renderFlatBlocks(agent.blocks);
const title = escapeHtml(agent.title);
const openAttr = openAll ? ' open' : (idx === 0 && agentIdx === 0 ? ' open' : '');
return `<details class="think-agent"${openAttr}><summary>${title}</summary><div class="think-agent-items">${inner}</div></details>`;
@@ -1126,6 +1114,27 @@
if (!entry) return;
entry.raw = content || '';
if (!entry.contentNode) return;
const captureOpenState = (root, selector) => {
if (!root || !root.querySelectorAll) return null;
const nodes = Array.from(root.querySelectorAll(selector));
if (!nodes.length) return null;
return nodes.map((node) => node.hasAttribute('open'));
};
const restoreOpenState = (root, selector, states) => {
if (!root || !root.querySelectorAll || !Array.isArray(states) || !states.length) return;
const nodes = Array.from(root.querySelectorAll(selector));
const max = Math.min(nodes.length, states.length);
for (let i = 0; i < max; i += 1) {
if (states[i]) {
nodes[i].setAttribute('open', '');
} else {
nodes[i].removeAttribute('open');
}
}
};
const savedThinkBlockState = captureOpenState(entry.contentNode, '.think-block');
const savedThinkAgentState = captureOpenState(entry.contentNode, '.think-agent');
const savedRolloutState = captureOpenState(entry.contentNode, '.think-rollout-group');
if (!entry.hasThink && entry.raw.includes('<think>')) {
entry.hasThink = true;
}
@@ -1139,7 +1148,13 @@
entry.contentNode.textContent = entry.raw;
}
}
restoreOpenState(entry.contentNode, '.think-block', savedThinkBlockState);
restoreOpenState(entry.contentNode, '.think-agent', savedThinkAgentState);
restoreOpenState(entry.contentNode, '.think-rollout-group', savedRolloutState);
if (entry.hasThink) {
if (finalize && (entry.thinkElapsed === null || typeof entry.thinkElapsed === 'undefined')) {
entry.thinkElapsed = 0;
}
updateThinkSummary(entry, entry.thinkElapsed);
}
if (entry.role === 'assistant' || entry.role === 'user') {