fix: 修复工具调用和样式渲染的问题
This commit is contained in:
parent
c1788f1ba3
commit
805f8c86da
|
|
@ -1,11 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div ref="processRef" class="process-block" :class="{ 'is-streaming': streaming }">
|
<div ref="processRef" class="process-block" :class="{ 'is-streaming': streaming }">
|
||||||
<!-- Thinking Steps -->
|
<!-- Single loop: render all steps in index order for proper alternation -->
|
||||||
<div
|
<template v-for="item in orderedItems">
|
||||||
v-for="item in thinkingItems"
|
<!-- Thinking Step -->
|
||||||
:key="item.key"
|
<div v-if="item.type === 'thinking'" :key="`thinking-${item.key}`" class="step-item thinking">
|
||||||
class="step-item thinking"
|
|
||||||
>
|
|
||||||
<div class="step-header" @click="toggleExpand(item.key)">
|
<div class="step-header" @click="toggleExpand(item.key)">
|
||||||
<span v-html="brainIcon"></span>
|
<span v-html="brainIcon"></span>
|
||||||
<span class="step-label">思考中</span>
|
<span class="step-label">思考中</span>
|
||||||
|
|
@ -18,13 +16,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tool Call Steps -->
|
<!-- Tool Call Step -->
|
||||||
<div
|
<div v-else-if="item.type === 'tool_call'" :key="`tool-${item.key}`" class="step-item tool_call" :class="{ loading: item.loading }">
|
||||||
v-for="item in toolCallItems"
|
|
||||||
:key="item.key"
|
|
||||||
class="step-item tool_call"
|
|
||||||
:class="{ loading: item.loading }"
|
|
||||||
>
|
|
||||||
<div class="step-header" @click="toggleExpand(item.key)">
|
<div class="step-header" @click="toggleExpand(item.key)">
|
||||||
<span v-html="toolIcon"></span>
|
<span v-html="toolIcon"></span>
|
||||||
<span class="step-label">{{ item.name || '工具调用' }}</span>
|
<span class="step-label">{{ item.name || '工具调用' }}</span>
|
||||||
|
|
@ -46,13 +39,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Text Steps -->
|
<!-- Text Step -->
|
||||||
<div
|
<div v-else-if="item.type === 'text'" :key="`text-${item.key}`" class="text-content md-content" v-html="renderMarkdown(item.content)"></div>
|
||||||
v-for="item in textItems"
|
</template>
|
||||||
:key="item.key"
|
|
||||||
class="text-content"
|
|
||||||
v-html="renderMarkdown(item.content)"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<!-- Streaming indicator -->
|
<!-- Streaming indicator -->
|
||||||
<div v-if="streaming && !hasContent" class="streaming-indicator">
|
<div v-if="streaming && !hasContent" class="streaming-indicator">
|
||||||
|
|
@ -85,6 +74,7 @@ const allItems = computed(() => {
|
||||||
items.push({
|
items.push({
|
||||||
key: step.id || `thinking-${step.index}`,
|
key: step.id || `thinking-${step.index}`,
|
||||||
type: 'thinking',
|
type: 'thinking',
|
||||||
|
index: step.index,
|
||||||
content: step.content || '',
|
content: step.content || '',
|
||||||
brief: step.content ? step.content.slice(0, 50) + (step.content.length > 50 ? '...' : '') : '',
|
brief: step.content ? step.content.slice(0, 50) + (step.content.length > 50 ? '...' : '') : '',
|
||||||
})
|
})
|
||||||
|
|
@ -92,9 +82,10 @@ const allItems = computed(() => {
|
||||||
items.push({
|
items.push({
|
||||||
key: step.id || `tool-${step.index}`,
|
key: step.id || `tool-${step.index}`,
|
||||||
type: 'tool_call',
|
type: 'tool_call',
|
||||||
|
index: step.index,
|
||||||
id: step.id,
|
id: step.id,
|
||||||
name: step.name,
|
name: step.name,
|
||||||
args: step.args,
|
args: step.arguments || step.args || '{}', // 后端发送 arguments 字段
|
||||||
brief: step.name || '',
|
brief: step.name || '',
|
||||||
loading: step.loading,
|
loading: step.loading,
|
||||||
isSuccess: step.isSuccess,
|
isSuccess: step.isSuccess,
|
||||||
|
|
@ -115,9 +106,10 @@ const allItems = computed(() => {
|
||||||
items.push({
|
items.push({
|
||||||
key: `result-${step.id || step.index}`,
|
key: `result-${step.id || step.index}`,
|
||||||
type: 'tool_call',
|
type: 'tool_call',
|
||||||
|
index: step.index,
|
||||||
id: step.id_ref || step.id,
|
id: step.id_ref || step.id,
|
||||||
name: step.name || '工具结果',
|
name: step.name || '工具结果',
|
||||||
args: '{}',
|
args: '{}', // 占位符默认空参数
|
||||||
brief: step.name || '工具结果',
|
brief: step.name || '工具结果',
|
||||||
loading: false,
|
loading: false,
|
||||||
isSuccess: true,
|
isSuccess: true,
|
||||||
|
|
@ -129,6 +121,7 @@ const allItems = computed(() => {
|
||||||
items.push({
|
items.push({
|
||||||
key: step.id || `text-${step.index}`,
|
key: step.id || `text-${step.index}`,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
index: step.index,
|
||||||
content: step.content || '',
|
content: step.content || '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -150,14 +143,33 @@ const allItems = computed(() => {
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|
||||||
const thinkingItems = computed(() => allItems.value.filter(i => i.type === 'thinking'))
|
// Ordered by index for proper step alternation
|
||||||
const toolCallItems = computed(() => allItems.value.filter(i => i.type === 'tool_call'))
|
const orderedItems = computed(() =>
|
||||||
const textItems = computed(() => allItems.value.filter(i => i.type === 'text'))
|
[...allItems.value].sort((a, b) => (a.index || 0) - (b.index || 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderedThinkingItems = computed(() =>
|
||||||
|
allItems.value
|
||||||
|
.filter(i => i.type === 'thinking')
|
||||||
|
.sort((a, b) => (a.index || 0) - (b.index || 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderedToolCallItems = computed(() =>
|
||||||
|
allItems.value
|
||||||
|
.filter(i => i.type === 'tool_call')
|
||||||
|
.sort((a, b) => (a.index || 0) - (b.index || 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderedTextItems = computed(() =>
|
||||||
|
allItems.value
|
||||||
|
.filter(i => i.type === 'text')
|
||||||
|
.sort((a, b) => (a.index || 0) - (b.index || 0))
|
||||||
|
)
|
||||||
|
|
||||||
const hasContent = computed(() => allItems.value.length > 0)
|
const hasContent = computed(() => allItems.value.length > 0)
|
||||||
const lastThinkingKey = computed(() => {
|
const lastThinkingKey = computed(() => {
|
||||||
const thinkingItems = allItems.value.filter(i => i.type === 'thinking')
|
const items = allItems.value.filter(i => i.type === 'thinking')
|
||||||
return thinkingItems.length > 0 ? thinkingItems[thinkingItems.length - 1].key : null
|
return items.length > 0 ? items[items.length - 1].key : null
|
||||||
})
|
})
|
||||||
|
|
||||||
function toggleExpand(key) {
|
function toggleExpand(key) {
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ export function createSSEStream(url, body, { onProcessStep, onDone, onError }) {
|
||||||
} else if (line.startsWith('data: ')) {
|
} else if (line.startsWith('data: ')) {
|
||||||
const data = JSON.parse(line.slice(6))
|
const data = JSON.parse(line.slice(6))
|
||||||
if (currentEvent === 'process_step' && onProcessStep) {
|
if (currentEvent === 'process_step' && onProcessStep) {
|
||||||
onProcessStep(data)
|
onProcessStep(data.step)
|
||||||
} else if (currentEvent === 'done' && onDone) {
|
} else if (currentEvent === 'done' && onDone) {
|
||||||
completed = true
|
completed = true
|
||||||
onDone(data)
|
onDone(data)
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,15 @@ onMounted(fetchData)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.tools { padding: 0; }
|
.tools {
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.tools > * { flex-shrink: 0; }
|
||||||
|
.grid { flex: 1; min-height: 0; }
|
||||||
.tools h1 { font-size: 2rem; margin: 0 0 1.5rem; color: var(--text-h); }
|
.tools h1 { font-size: 2rem; margin: 0 0 1.5rem; color: var(--text-h); }
|
||||||
.stats { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }
|
.stats { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }
|
||||||
.stat { font-size: 1.1rem; color: var(--text); }
|
.stat { font-size: 1.1rem; color: var(--text); }
|
||||||
|
|
|
||||||
|
|
@ -135,9 +135,11 @@ class ChatService:
|
||||||
full_thinking = ""
|
full_thinking = ""
|
||||||
tool_calls_list = []
|
tool_calls_list = []
|
||||||
|
|
||||||
# Generate new step IDs for each iteration to track multiple thoughts/tools
|
# Step tracking - use unified step-{index} format
|
||||||
iteration_thinking_step_id = f"thinking-{iteration}"
|
thinking_step_id = None
|
||||||
iteration_text_step_id = f"text-{iteration}"
|
thinking_step_idx = None
|
||||||
|
text_step_id = None
|
||||||
|
text_step_idx = None
|
||||||
|
|
||||||
async for sse_line in llm.stream_call(
|
async for sse_line in llm.stream_call(
|
||||||
model=model,
|
model=model,
|
||||||
|
|
@ -185,31 +187,37 @@ class ChatService:
|
||||||
# Handle reasoning (thinking)
|
# Handle reasoning (thinking)
|
||||||
reasoning = delta.get("reasoning_content", "")
|
reasoning = delta.get("reasoning_content", "")
|
||||||
if reasoning:
|
if reasoning:
|
||||||
|
prev_thinking_len = len(full_thinking)
|
||||||
full_thinking += reasoning
|
full_thinking += reasoning
|
||||||
if thinking_step_id is None:
|
if prev_thinking_len == 0: # New thinking stream started
|
||||||
thinking_step_id = iteration_thinking_step_id
|
|
||||||
thinking_step_idx = step_index
|
thinking_step_idx = step_index
|
||||||
|
thinking_step_id = f"step-{step_index}"
|
||||||
step_index += 1
|
step_index += 1
|
||||||
yield _sse_event("process_step", {
|
yield _sse_event("process_step", {
|
||||||
|
"step": {
|
||||||
"id": thinking_step_id,
|
"id": thinking_step_id,
|
||||||
"index": thinking_step_idx,
|
"index": thinking_step_idx,
|
||||||
"type": "thinking",
|
"type": "thinking",
|
||||||
"content": full_thinking
|
"content": full_thinking
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
# Handle content
|
# Handle content
|
||||||
content = delta.get("content", "")
|
content = delta.get("content", "")
|
||||||
if content:
|
if content:
|
||||||
|
prev_content_len = len(full_content)
|
||||||
full_content += content
|
full_content += content
|
||||||
if text_step_id is None:
|
if prev_content_len == 0: # New text stream started
|
||||||
text_step_idx = step_index
|
text_step_idx = step_index
|
||||||
text_step_id = iteration_text_step_id
|
text_step_id = f"step-{step_index}"
|
||||||
step_index += 1
|
step_index += 1
|
||||||
yield _sse_event("process_step", {
|
yield _sse_event("process_step", {
|
||||||
|
"step": {
|
||||||
"id": text_step_id,
|
"id": text_step_id,
|
||||||
"index": text_step_idx,
|
"index": text_step_idx,
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"content": full_content
|
"content": full_content
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
# Accumulate tool calls
|
# Accumulate tool calls
|
||||||
|
|
@ -250,42 +258,45 @@ class ChatService:
|
||||||
if tool_calls_list:
|
if tool_calls_list:
|
||||||
all_tool_calls.extend(tool_calls_list)
|
all_tool_calls.extend(tool_calls_list)
|
||||||
|
|
||||||
# Yield tool_call steps
|
# Yield tool_call steps - use unified step-{index} format
|
||||||
tool_call_step_ids = [] # Track step IDs for tool calls
|
tool_call_step_ids = [] # Track step IDs for tool calls
|
||||||
for tc in tool_calls_list:
|
for tc in tool_calls_list:
|
||||||
call_step_id = f"tool-{iteration}-{tc.get('function', {}).get('name', 'unknown')}"
|
call_step_idx = step_index
|
||||||
|
call_step_id = f"step-{step_index}"
|
||||||
tool_call_step_ids.append(call_step_id)
|
tool_call_step_ids.append(call_step_id)
|
||||||
|
step_index += 1
|
||||||
call_step = {
|
call_step = {
|
||||||
"id": call_step_id,
|
"id": call_step_id,
|
||||||
"index": step_index,
|
"index": call_step_idx,
|
||||||
"type": "tool_call",
|
"type": "tool_call",
|
||||||
"id_ref": tc.get("id", ""),
|
"id_ref": tc.get("id", ""),
|
||||||
"name": tc["function"]["name"],
|
"name": tc["function"]["name"],
|
||||||
"arguments": tc["function"]["arguments"]
|
"arguments": tc["function"]["arguments"]
|
||||||
}
|
}
|
||||||
all_steps.append(call_step)
|
all_steps.append(call_step)
|
||||||
yield _sse_event("process_step", call_step)
|
yield _sse_event("process_step", {"step": call_step})
|
||||||
step_index += 1
|
|
||||||
|
|
||||||
# Execute tools
|
# Execute tools
|
||||||
tool_results = self.tool_executor.process_tool_calls_parallel(
|
tool_results = self.tool_executor.process_tool_calls_parallel(
|
||||||
tool_calls_list, {}
|
tool_calls_list, {}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Yield tool_result steps
|
# Yield tool_result steps - use unified step-{index} format
|
||||||
for i, tr in enumerate(tool_results):
|
for i, tr in enumerate(tool_results):
|
||||||
tool_call_step_id = tool_call_step_ids[i] if i < len(tool_call_step_ids) else f"tool-{i}"
|
tool_call_step_id = tool_call_step_ids[i] if i < len(tool_call_step_ids) else f"step-{i}"
|
||||||
|
result_step_idx = step_index
|
||||||
|
result_step_id = f"step-{step_index}"
|
||||||
|
step_index += 1
|
||||||
result_step = {
|
result_step = {
|
||||||
"id": f"result-{iteration}-{tr.get('name', 'unknown')}",
|
"id": result_step_id,
|
||||||
"index": step_index,
|
"index": result_step_idx,
|
||||||
"type": "tool_result",
|
"type": "tool_result",
|
||||||
"id_ref": tool_call_step_id, # Reference to the tool_call step
|
"id_ref": tool_call_step_id, # Reference to the tool_call step
|
||||||
"name": tr.get("name", ""),
|
"name": tr.get("name", ""),
|
||||||
"content": tr.get("content", "")
|
"content": tr.get("content", "")
|
||||||
}
|
}
|
||||||
all_steps.append(result_step)
|
all_steps.append(result_step)
|
||||||
yield _sse_event("process_step", result_step)
|
yield _sse_event("process_step", {"step": result_step})
|
||||||
step_index += 1
|
|
||||||
|
|
||||||
all_tool_results.append({
|
all_tool_results.append({
|
||||||
"role": "tool",
|
"role": "tool",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue