refactor: 移除 message thinking 字段并修复流式渲染显示
This commit is contained in:
parent
6ffbb29ec7
commit
e57b4b7d9a
|
|
@ -64,8 +64,8 @@ class ChatService:
|
|||
msg_id = str(uuid.uuid4())
|
||||
tool_calls_list = []
|
||||
|
||||
# Send thinking_start event to clear previous thinking in frontend
|
||||
yield f"event: thinking_start\ndata: {{}}\n\n"
|
||||
# Clear state for new iteration
|
||||
# (frontend resets via onProcessStep when first step arrives)
|
||||
|
||||
try:
|
||||
with app.app_context():
|
||||
|
|
@ -102,7 +102,6 @@ class ChatService:
|
|||
reasoning = delta.get("reasoning_content", "")
|
||||
if reasoning:
|
||||
full_thinking += reasoning
|
||||
yield f"event: thinking\ndata: {json.dumps({'content': reasoning}, ensure_ascii=False)}\n\n"
|
||||
|
||||
# Accumulate text content for this iteration
|
||||
text = delta.get("content", "")
|
||||
|
|
|
|||
|
|
@ -465,8 +465,6 @@ def process_tool_calls(self, tool_calls, context=None):
|
|||
|
||||
| 事件 | 说明 |
|
||||
|------|------|
|
||||
| `thinking_start` | 新一轮思考开始,前端应清空之前的思考缓冲 |
|
||||
| `thinking` | 思维链增量内容(启用时) |
|
||||
| `message` | 回复内容的增量片段 |
|
||||
| `tool_calls` | 工具调用信息 |
|
||||
| `tool_result` | 工具执行结果 |
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@
|
|||
:messages="messages"
|
||||
:streaming="streaming"
|
||||
:streaming-content="streamContent"
|
||||
:streaming-tool-calls="streamToolCalls"
|
||||
:streaming-process-steps="streamProcessSteps"
|
||||
:has-more-messages="hasMoreMessages"
|
||||
:loading-more="loadingMessages"
|
||||
|
|
@ -342,6 +341,11 @@ function createStreamCallbacks(convId, { updateConvList = true } = {}) {
|
|||
steps[step.index] = step
|
||||
return steps
|
||||
})
|
||||
// When text is finalized as a process_step, reset streaming content
|
||||
// to prevent duplication (the text is now rendered via processSteps).
|
||||
if (step.type === 'text') {
|
||||
updateStreamField(convId, 'streamContent', streamContent, () => '')
|
||||
}
|
||||
},
|
||||
async onDone(data) {
|
||||
streamStates.delete(convId)
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ async function request(url, options = {}) {
|
|||
* Shared SSE stream processor - parses SSE events and dispatches to callbacks
|
||||
* @param {string} url - API URL (without BASE prefix)
|
||||
* @param {object} body - Request body
|
||||
* @param {object} callbacks - Event handlers: { onThinkingStart, onThinking, onMessage, onToolCalls, onToolResult, onProcessStep, onDone, onError }
|
||||
* @param {object} callbacks - Event handlers: { onMessage, onToolCalls, onToolResult, onProcessStep, onDone, onError }
|
||||
* @returns {{ abort: () => void }}
|
||||
*/
|
||||
function createSSEStream(url, body, { onThinkingStart, onThinking, onMessage, onToolCalls, onToolResult, onProcessStep, onDone, onError }) {
|
||||
function createSSEStream(url, body, { onMessage, onToolCalls, onToolResult, onProcessStep, onDone, onError }) {
|
||||
const controller = new AbortController()
|
||||
|
||||
const promise = (async () => {
|
||||
|
|
@ -67,11 +67,7 @@ function createSSEStream(url, body, { onThinkingStart, onThinking, onMessage, on
|
|||
currentEvent = line.slice(7).trim()
|
||||
} else if (line.startsWith('data: ')) {
|
||||
const data = JSON.parse(line.slice(6))
|
||||
if (currentEvent === 'thinking_start' && onThinkingStart) {
|
||||
onThinkingStart()
|
||||
} else if (currentEvent === 'thinking' && onThinking) {
|
||||
onThinking(data.content)
|
||||
} else if (currentEvent === 'message' && onMessage) {
|
||||
if (currentEvent === 'message' && onMessage) {
|
||||
onMessage(data.content)
|
||||
} else if (currentEvent === 'tool_calls' && onToolCalls) {
|
||||
onToolCalls(data.calls)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@
|
|||
<div class="avatar">claw</div>
|
||||
<div class="message-body">
|
||||
<ProcessBlock
|
||||
:tool-calls="streamingToolCalls"
|
||||
:process-steps="streamingProcessSteps"
|
||||
:streaming-content="streamingContent"
|
||||
:streaming="streaming"
|
||||
|
|
@ -88,7 +87,6 @@ const props = defineProps({
|
|||
messages: { type: Array, required: true },
|
||||
streaming: { type: Boolean, default: false },
|
||||
streamingContent: { type: String, default: '' },
|
||||
streamingToolCalls: { type: Array, default: () => [] },
|
||||
streamingProcessSteps: { type: Array, default: () => [] },
|
||||
hasMoreMessages: { type: Boolean, default: false },
|
||||
loadingMore: { type: Boolean, default: false },
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
<ProcessBlock
|
||||
v-if="processSteps && processSteps.length > 0"
|
||||
:process-steps="processSteps"
|
||||
:tool-calls="toolCalls"
|
||||
/>
|
||||
<!-- Fallback path: old messages without processSteps in DB, -->
|
||||
<!-- render toolCalls via ProcessBlock and text separately -->
|
||||
|
|
|
|||
|
|
@ -1,17 +1,7 @@
|
|||
<template>
|
||||
<div ref="processRef" class="process-block" :class="{ 'is-streaming': streaming }">
|
||||
<!-- Placeholder while waiting for the first process step to arrive -->
|
||||
<div v-if="streaming && processItems.length === 0" class="streaming-placeholder">
|
||||
<div class="streaming-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="streaming-text">正在思考中<span class="dots">...</span></span>
|
||||
</div>
|
||||
|
||||
<!-- Render all steps in order: thinking, text, tool_call, tool_result interleaved -->
|
||||
<template v-else>
|
||||
<template v-if="processItems.length > 0">
|
||||
<template v-for="item in processItems" :key="item.key">
|
||||
<!-- Thinking block -->
|
||||
<div v-if="item.type === 'thinking'" class="step-item thinking">
|
||||
|
|
@ -60,14 +50,15 @@
|
|||
<div v-else-if="item.type === 'text'" class="step-item text-content md-content" v-html="item.rendered"></div>
|
||||
</template>
|
||||
|
||||
<!-- Active streaming indicator (cursor) -->
|
||||
</template>
|
||||
|
||||
<!-- Active streaming indicator — always visible during streaming, even before any content arrives -->
|
||||
<div v-if="streaming" class="streaming-indicator">
|
||||
<svg class="spinner" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
||||
</svg>
|
||||
<span>正在生成...</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -231,44 +222,6 @@ watch(() => props.streamingContent?.length, () => {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
/* Streaming placeholder while waiting for first step */
|
||||
.streaming-placeholder {
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: var(--bg-hover);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.streaming-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
background: #fef3c7;
|
||||
color: #f59e0b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.streaming-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.streaming-text .dots {
|
||||
display: inline-block;
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Step items (shared) */
|
||||
.step-item {
|
||||
margin-bottom: 8px;
|
||||
|
|
@ -278,6 +231,11 @@ watch(() => props.streamingContent?.length, () => {
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Thinking and tool call step headers */
|
||||
.thinking .step-header,
|
||||
.tool_call .step-header {
|
||||
|
|
@ -429,10 +387,14 @@ watch(() => props.streamingContent?.length, () => {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 0 0;
|
||||
border-top: 1px solid var(--border-light);
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* Add separator only when there are step items above the indicator */
|
||||
.process-block:has(.step-item) .streaming-indicator {
|
||||
margin-top: 8px;
|
||||
padding: 8px 0 0;
|
||||
border-top: 1px solid var(--border-light);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue