mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
feat(ai): render analysis plan blocks
This commit is contained in:
1 parent
b61974136e
commit
2d413f4202
11 files changed
+262
-33
No files matched your search
@@ -9,6 +9,7 @@ import Database from 'better-sqlite3'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import type { ChartPayload } from '@openchatlab/core'
|
||||
import type { PlanContentBlock } from './agent'
|
||||
|
||||
const DEFAULT_GENERAL_ID = 'general_cn'
|
||||
|
||||
@@ -28,6 +29,7 @@ export type ContentBlock =
|
||||
| { type: 'text'; text: string }
|
||||
| { type: 'think'; tag: string; text: string; durationMs?: number }
|
||||
| { type: 'chart'; chart: ChartPayload }
|
||||
| PlanContentBlock
|
||||
| {
|
||||
type: 'tool'
|
||||
tool: {
|
||||
@@ -154,9 +156,9 @@ export class AIChatManager {
|
||||
}
|
||||
|
||||
private tableExists(db: Database.Database, tableName: string): boolean {
|
||||
const row = db
|
||||
.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?")
|
||||
.get(tableName) as { 1: number } | undefined
|
||||
const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(tableName) as
|
||||
| { 1: number }
|
||||
| undefined
|
||||
return !!row
|
||||
}
|
||||
|
||||
@@ -369,9 +371,9 @@ export class AIChatManager {
|
||||
|
||||
private getActiveMessageId(aiChatId: string): string | null {
|
||||
const db = this.getDb()
|
||||
const row = db
|
||||
.prepare('SELECT active_message_id as activeMessageId FROM ai_chat WHERE id = ?')
|
||||
.get(aiChatId) as { activeMessageId: string | null } | undefined
|
||||
const row = db.prepare('SELECT active_message_id as activeMessageId FROM ai_chat WHERE id = ?').get(aiChatId) as
|
||||
| { activeMessageId: string | null }
|
||||
| undefined
|
||||
if (row?.activeMessageId) {
|
||||
const activeExists = db.prepare('SELECT 1 FROM ai_message WHERE id = ?').get(row.activeMessageId)
|
||||
if (activeExists) return row.activeMessageId
|
||||
@@ -516,9 +518,10 @@ export class AIChatManager {
|
||||
const result = new Map<string, number>()
|
||||
try {
|
||||
const db = this.getDb()
|
||||
const rows = db
|
||||
.prepare('SELECT session_id, COUNT(*) as count FROM ai_chat GROUP BY session_id')
|
||||
.all() as Array<{ session_id: string; count: number }>
|
||||
const rows = db.prepare('SELECT session_id, COUNT(*) as count FROM ai_chat GROUP BY session_id').all() as Array<{
|
||||
session_id: string
|
||||
count: number
|
||||
}>
|
||||
for (const row of rows) {
|
||||
result.set(row.session_id, row.count)
|
||||
}
|
||||
@@ -556,9 +559,7 @@ export class AIChatManager {
|
||||
updateAIChatTitle(aiChatId: string, title: string): boolean {
|
||||
const db = this.getDb()
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const result = db
|
||||
.prepare('UPDATE ai_chat SET title = ?, updated_at = ? WHERE id = ?')
|
||||
.run(title, now, aiChatId)
|
||||
const result = db.prepare('UPDATE ai_chat SET title = ?, updated_at = ? WHERE id = ?').run(title, now, aiChatId)
|
||||
return result.changes > 0
|
||||
}
|
||||
|
||||
@@ -614,11 +615,7 @@ export class AIChatManager {
|
||||
branchIndex
|
||||
)
|
||||
|
||||
db.prepare('UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?').run(
|
||||
id,
|
||||
now,
|
||||
aiChatId
|
||||
)
|
||||
db.prepare('UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?').run(id, now, aiChatId)
|
||||
|
||||
return {
|
||||
id,
|
||||
@@ -663,11 +660,7 @@ export class AIChatManager {
|
||||
|
||||
const newLeafId = targetIndex > 0 ? activePath[targetIndex - 1]!.id : null
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
db.prepare('UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?').run(
|
||||
newLeafId,
|
||||
now,
|
||||
aiChatId
|
||||
)
|
||||
db.prepare('UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?').run(newLeafId, now, aiChatId)
|
||||
}
|
||||
|
||||
forkAIChat(sourceAIChatId: string, upToMessageId: string, title?: string): AIChat {
|
||||
@@ -814,11 +807,7 @@ export class AIChatManager {
|
||||
db.prepare('UPDATE ai_message SET parent_id = ? WHERE id = ?').run(id, childRow.id)
|
||||
db.prepare('UPDATE ai_chat SET updated_at = ? WHERE id = ?').run(now, aiChatId)
|
||||
} else {
|
||||
db.prepare('UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?').run(
|
||||
id,
|
||||
now,
|
||||
aiChatId
|
||||
)
|
||||
db.prepare('UPDATE ai_chat SET active_message_id = ?, updated_at = ? WHERE id = ?').run(id, now, aiChatId)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -954,9 +943,7 @@ export class AIChatManager {
|
||||
.map((row) => ({ role: row.role as AIMessageRole, content: row.content, timestamp: row.timestamp }))
|
||||
}
|
||||
|
||||
getAllUserAssistantMessages(
|
||||
aiChatId: string
|
||||
): Array<{ role: AIMessageRole; content: string; timestamp: number }> {
|
||||
getAllUserAssistantMessages(aiChatId: string): Array<{ role: AIMessageRole; content: string; timestamp: number }> {
|
||||
return this.getActivePathRows(aiChatId)
|
||||
.filter((row) => row.role === 'user' || row.role === 'assistant')
|
||||
.map((row) => ({ role: row.role as AIMessageRole, content: row.content, timestamp: row.timestamp }))
|
||||
@@ -965,8 +952,7 @@ export class AIChatManager {
|
||||
getMessageCountAfterSummary(aiChatId: string): number {
|
||||
const summary = this.getLatestSummary(aiChatId)
|
||||
if (!summary) {
|
||||
return this.getActivePathRows(aiChatId).filter((row) => row.role === 'user' || row.role === 'assistant')
|
||||
.length
|
||||
return this.getActivePathRows(aiChatId).filter((row) => row.role === 'user' || row.role === 'assistant').length
|
||||
}
|
||||
|
||||
const metaBlock = summary.contentBlocks?.find(
|
||||
|
||||
@@ -161,6 +161,15 @@ function formatToolStatusForCopy(status: ToolBlockContent['status']): string {
|
||||
return 'error'
|
||||
}
|
||||
|
||||
function getPlanStatusLabel(status: 'created' | 'executing' | 'done' | 'skipped'): string {
|
||||
return t(`ai.chat.message.plan.status.${status}`)
|
||||
}
|
||||
|
||||
function formatPlanTools(tools: string[]): string {
|
||||
if (tools.length === 0) return t('ai.chat.message.plan.noTools')
|
||||
return tools.join(', ')
|
||||
}
|
||||
|
||||
// 格式化时间参数显示
|
||||
function formatTimeParams(params: Record<string, unknown>): string {
|
||||
// 优先使用 start_time/end_time
|
||||
@@ -335,6 +344,17 @@ const copyMarkdownText = computed(() => {
|
||||
return `> Chart: ${block.chart.spec.title}`
|
||||
}
|
||||
|
||||
if (block.type === 'plan') {
|
||||
const steps = block.plan.steps
|
||||
.map(
|
||||
(step, index) =>
|
||||
`${index + 1}. ${step.goal}\n - ${t('ai.chat.message.plan.evidenceNeeded')}: ${step.evidenceNeeded}\n - ${t('ai.chat.message.plan.suggestedTools')}: ${formatPlanTools(step.suggestedTools)}`
|
||||
)
|
||||
.join('\n')
|
||||
const criteria = block.plan.successCriteria.map((item) => `- ${item}`).join('\n')
|
||||
return `> ${t('ai.chat.message.plan.label')}: ${block.plan.title} (${getPlanStatusLabel(block.status)})\n\n${steps}\n\n${t('ai.chat.message.plan.successCriteria')}:\n${criteria}`
|
||||
}
|
||||
|
||||
if (block.type === 'tool') {
|
||||
const toolName = getToolDisplayName(block.tool)
|
||||
const toolParams = formatToolParams(block.tool)
|
||||
@@ -489,6 +509,47 @@ async function handleCopyMarkdown() {
|
||||
<span>{{ t('ai.skill.active.label', { name: block.skillName }) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 计划块 -->
|
||||
<details
|
||||
v-else-if="block.type === 'plan'"
|
||||
class="mb-2 rounded-lg border border-blue-100 bg-blue-50/60 text-sm text-gray-700 dark:border-blue-900/40 dark:bg-blue-950/20 dark:text-gray-300"
|
||||
>
|
||||
<summary
|
||||
class="flex cursor-pointer select-none items-center gap-2 px-3 py-2 text-xs font-medium text-blue-700 transition-colors hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200"
|
||||
>
|
||||
<UIcon name="i-heroicons-clipboard-document-list" class="h-3.5 w-3.5 shrink-0" />
|
||||
<span class="min-w-0 truncate">{{ t('ai.chat.message.plan.label') }} · {{ block.plan.title }}</span>
|
||||
<span
|
||||
class="ml-auto shrink-0 rounded-full bg-white/70 px-2 py-0.5 text-[11px] text-blue-600 dark:bg-blue-950/60 dark:text-blue-300"
|
||||
>
|
||||
{{ getPlanStatusLabel(block.status) }}
|
||||
</span>
|
||||
</summary>
|
||||
<div class="border-t border-blue-100/70 px-3 py-2.5 dark:border-blue-900/40">
|
||||
<ol class="space-y-2">
|
||||
<li v-for="(step, stepIndex) in block.plan.steps" :key="stepIndex" class="text-xs leading-relaxed">
|
||||
<div class="font-medium text-gray-800 dark:text-gray-200">{{ stepIndex + 1 }}. {{ step.goal }}</div>
|
||||
<div class="mt-1 text-gray-500 dark:text-gray-400">
|
||||
{{ t('ai.chat.message.plan.evidenceNeeded') }}: {{ step.evidenceNeeded }}
|
||||
</div>
|
||||
<div class="mt-0.5 text-gray-500 dark:text-gray-400">
|
||||
{{ t('ai.chat.message.plan.suggestedTools') }}: {{ formatPlanTools(step.suggestedTools) }}
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<div class="mt-2 border-t border-blue-100/70 pt-2 dark:border-blue-900/40">
|
||||
<div class="text-[11px] font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('ai.chat.message.plan.successCriteria') }}
|
||||
</div>
|
||||
<ul class="mt-1 list-disc space-y-0.5 pl-4 text-xs leading-relaxed text-gray-600 dark:text-gray-300">
|
||||
<li v-for="(criterion, criterionIndex) in block.plan.successCriteria" :key="criterionIndex">
|
||||
{{ criterion }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- 图表块 -->
|
||||
<ChartBlockRenderer v-else-if="block.type === 'chart'" :chart="block.chart" />
|
||||
|
||||
|
||||
@@ -108,6 +108,19 @@
|
||||
"collapse": "Collapse summary",
|
||||
"info": "Previous conversation has been compressed into a summary to save context space"
|
||||
},
|
||||
"plan": {
|
||||
"label": "Analysis Plan",
|
||||
"evidenceNeeded": "Evidence needed",
|
||||
"suggestedTools": "Suggested tools",
|
||||
"successCriteria": "Success criteria",
|
||||
"noTools": "None",
|
||||
"status": {
|
||||
"created": "Created",
|
||||
"executing": "Executing",
|
||||
"done": "Done",
|
||||
"skipped": "Skipped"
|
||||
}
|
||||
},
|
||||
"think": {
|
||||
"labels": {
|
||||
"think": "Thinking",
|
||||
|
||||
@@ -108,6 +108,19 @@
|
||||
"collapse": "要約を折りたたむ",
|
||||
"info": "以前の会話がコンテキスト節約のため要約に圧縮されました"
|
||||
},
|
||||
"plan": {
|
||||
"label": "分析計画",
|
||||
"evidenceNeeded": "必要な証拠",
|
||||
"suggestedTools": "推奨ツール",
|
||||
"successCriteria": "成功基準",
|
||||
"noTools": "なし",
|
||||
"status": {
|
||||
"created": "作成済み",
|
||||
"executing": "実行中",
|
||||
"done": "完了",
|
||||
"skipped": "スキップ"
|
||||
}
|
||||
},
|
||||
"think": {
|
||||
"labels": {
|
||||
"think": "思考",
|
||||
|
||||
@@ -108,6 +108,19 @@
|
||||
"collapse": "收起摘要",
|
||||
"info": "以上对话已被压缩为摘要以节省上下文空间"
|
||||
},
|
||||
"plan": {
|
||||
"label": "分析计划",
|
||||
"evidenceNeeded": "所需证据",
|
||||
"suggestedTools": "建议工具",
|
||||
"successCriteria": "完成标准",
|
||||
"noTools": "无",
|
||||
"status": {
|
||||
"created": "已创建",
|
||||
"executing": "执行中",
|
||||
"done": "已完成",
|
||||
"skipped": "已跳过"
|
||||
}
|
||||
},
|
||||
"think": {
|
||||
"labels": {
|
||||
"think": "已思考",
|
||||
|
||||
@@ -108,6 +108,19 @@
|
||||
"collapse": "收起摘要",
|
||||
"info": "以上對話已被壓縮為摘要以節省上下文空間"
|
||||
},
|
||||
"plan": {
|
||||
"label": "分析計畫",
|
||||
"evidenceNeeded": "所需證據",
|
||||
"suggestedTools": "建議工具",
|
||||
"successCriteria": "完成標準",
|
||||
"noTools": "無",
|
||||
"status": {
|
||||
"created": "已建立",
|
||||
"executing": "執行中",
|
||||
"done": "已完成",
|
||||
"skipped": "已略過"
|
||||
}
|
||||
},
|
||||
"think": {
|
||||
"labels": {
|
||||
"think": "已思考",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { PlanContentBlock } from '@/services/ai/planBlocks'
|
||||
|
||||
export interface LlmStreamChunk {
|
||||
content: string
|
||||
isFinished: boolean
|
||||
@@ -8,7 +10,7 @@ export interface LlmStreamChunk {
|
||||
}
|
||||
|
||||
export interface AgentStreamChunk {
|
||||
type: 'content' | 'think' | 'tool_start' | 'tool_result' | 'status' | 'compression_done' | 'done' | 'error'
|
||||
type: 'content' | 'think' | 'tool_start' | 'tool_result' | 'status' | 'compression_done' | 'plan' | 'done' | 'error'
|
||||
content?: string
|
||||
thinkTag?: string
|
||||
thinkDurationMs?: number
|
||||
@@ -19,6 +21,7 @@ export interface AgentStreamChunk {
|
||||
isFinished?: boolean
|
||||
usage?: TokenUsage
|
||||
status?: AgentRuntimeStatus
|
||||
plan?: PlanContentBlock
|
||||
compressionResult?: {
|
||||
summaryContent: string
|
||||
tokensBefore: number
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { toPlanContentBlock, updateLastPlanBlockStatus, type PlanContentBlock } from './planBlocks'
|
||||
|
||||
const planBlock: PlanContentBlock = {
|
||||
type: 'plan',
|
||||
version: 1,
|
||||
status: 'created',
|
||||
plan: {
|
||||
version: 1,
|
||||
title: '年度话题趋势分析',
|
||||
route: 'planned_execution',
|
||||
intent: 'trend',
|
||||
steps: [{ goal: '按季度检索', suggestedTools: ['search_messages'], evidenceNeeded: '季度证据' }],
|
||||
successCriteria: ['覆盖全年'],
|
||||
},
|
||||
}
|
||||
|
||||
describe('planBlocks', () => {
|
||||
it('creates a serializable copy of plan blocks', () => {
|
||||
const copy = toPlanContentBlock(planBlock)
|
||||
|
||||
assert.deepEqual(copy, planBlock)
|
||||
assert.notEqual(copy, planBlock)
|
||||
assert.notEqual(copy.plan, planBlock.plan)
|
||||
})
|
||||
|
||||
it('updates the last plan block status without mutating the original array', () => {
|
||||
const blocks = [planBlock]
|
||||
const updated = updateLastPlanBlockStatus(blocks, 'done')
|
||||
|
||||
assert.equal(planBlock.status, 'created')
|
||||
assert.equal(updated[0]?.status, 'done')
|
||||
assert.notEqual(updated, blocks)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
export type PlanIntent = 'summary' | 'trend' | 'relationship' | 'search' | 'comparison' | 'mixed'
|
||||
export type PlanBlockStatus = 'created' | 'executing' | 'done' | 'skipped'
|
||||
|
||||
export interface PlanStep {
|
||||
goal: string
|
||||
suggestedTools: string[]
|
||||
evidenceNeeded: string
|
||||
}
|
||||
|
||||
export interface PlanSummary {
|
||||
version: 1
|
||||
title: string
|
||||
route: 'planned_execution'
|
||||
intent: PlanIntent
|
||||
steps: PlanStep[]
|
||||
successCriteria: string[]
|
||||
}
|
||||
|
||||
export interface PlanContentBlock {
|
||||
type: 'plan'
|
||||
version: 1
|
||||
status: PlanBlockStatus
|
||||
plan: PlanSummary
|
||||
}
|
||||
|
||||
export function toPlanContentBlock(block: PlanContentBlock): PlanContentBlock {
|
||||
return JSON.parse(JSON.stringify(block)) as PlanContentBlock
|
||||
}
|
||||
|
||||
export function updateLastPlanBlockStatus(
|
||||
blocks: readonly PlanContentBlock[],
|
||||
status: PlanBlockStatus
|
||||
): PlanContentBlock[] {
|
||||
const nextBlocks = blocks.map(toPlanContentBlock)
|
||||
for (let index = nextBlocks.length - 1; index >= 0; index--) {
|
||||
if (nextBlocks[index].type === 'plan') {
|
||||
nextBlocks[index].status = status
|
||||
break
|
||||
}
|
||||
}
|
||||
return nextBlocks
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TimeFilter } from '@/types/base'
|
||||
import type { ChartPayload } from '@openchatlab/core'
|
||||
import type { PlanContentBlock } from './planBlocks'
|
||||
|
||||
export interface AIChat {
|
||||
id: string
|
||||
@@ -15,6 +16,7 @@ export type ContentBlock =
|
||||
| { type: 'text'; text: string }
|
||||
| { type: 'think'; tag: string; text: string; durationMs?: number }
|
||||
| { type: 'chart'; chart: ChartPayload }
|
||||
| PlanContentBlock
|
||||
| {
|
||||
type: 'tool'
|
||||
tool: {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
toChartContentBlocks,
|
||||
toRenderOnlyToolErrorBlock,
|
||||
} from './aiChatChartBlocks'
|
||||
import { toPlanContentBlock, type PlanBlockStatus, type PlanContentBlock } from '@/services/ai/planBlocks'
|
||||
|
||||
// 工具调用记录
|
||||
export interface ToolCallRecord {
|
||||
@@ -57,6 +58,7 @@ export type ContentBlock =
|
||||
| { type: 'text'; text: string }
|
||||
| { type: 'think'; tag: string; text: string; durationMs?: number }
|
||||
| { type: 'chart'; chart: ChartPayload }
|
||||
| PlanContentBlock
|
||||
| {
|
||||
type: 'tool'
|
||||
tool: ToolBlockContent
|
||||
@@ -605,6 +607,8 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
appendTextToBlocks: (text: string) => void
|
||||
appendThinkToBlocks: (text: string, tag?: string, durationMs?: number) => void
|
||||
appendChartsToBlocks: (charts: ChartPayload[]) => void
|
||||
appendPlanToBlocks: (plan: PlanContentBlock) => void
|
||||
updatePlanBlockStatus: (status: PlanBlockStatus) => void
|
||||
appendErrorToBlocks: (error: SerializedErrorInfo) => void
|
||||
addToolBlock: (toolName: string, params?: Record<string, unknown>) => void
|
||||
updateToolBlockStatus: (toolName: string, status: 'done' | 'error') => void
|
||||
@@ -665,6 +669,26 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
updateAIMessage({ contentBlocks: [...blocks] })
|
||||
}
|
||||
|
||||
const appendPlanToBlocks = (plan: PlanContentBlock) => {
|
||||
const idx = getAiMessageIndex()
|
||||
const blocks = targetBuffer.messages[idx].contentBlocks || []
|
||||
blocks.push(toPlanContentBlock(plan))
|
||||
updateAIMessage({ contentBlocks: [...blocks] })
|
||||
}
|
||||
|
||||
const updatePlanBlockStatus = (status: PlanBlockStatus) => {
|
||||
const idx = getAiMessageIndex()
|
||||
const blocks = targetBuffer.messages[idx].contentBlocks || []
|
||||
for (let index = blocks.length - 1; index >= 0; index--) {
|
||||
const block = blocks[index]
|
||||
if (block.type === 'plan') {
|
||||
block.status = status
|
||||
break
|
||||
}
|
||||
}
|
||||
updateAIMessage({ contentBlocks: [...blocks] })
|
||||
}
|
||||
|
||||
const appendErrorToBlocks = (error: SerializedErrorInfo) => {
|
||||
const idx = getAiMessageIndex()
|
||||
const blocks = targetBuffer.messages[idx].contentBlocks || []
|
||||
@@ -697,6 +721,8 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
appendTextToBlocks,
|
||||
appendThinkToBlocks,
|
||||
appendChartsToBlocks,
|
||||
appendPlanToBlocks,
|
||||
updatePlanBlockStatus,
|
||||
appendErrorToBlocks,
|
||||
addToolBlock,
|
||||
updateToolBlockStatus,
|
||||
@@ -813,6 +839,8 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
appendTextToBlocks,
|
||||
appendThinkToBlocks,
|
||||
appendChartsToBlocks,
|
||||
appendPlanToBlocks,
|
||||
updatePlanBlockStatus,
|
||||
appendErrorToBlocks,
|
||||
addToolBlock,
|
||||
updateToolBlockStatus,
|
||||
@@ -938,6 +966,13 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
state.isLoadingSource = false
|
||||
break
|
||||
|
||||
case 'plan':
|
||||
if (chunk.plan) {
|
||||
appendPlanToBlocks(chunk.plan)
|
||||
updatePlanBlockStatus('executing')
|
||||
}
|
||||
break
|
||||
|
||||
case 'status':
|
||||
if (chunk.status && (!state.agentStatus || chunk.status.updatedAt >= state.agentStatus.updatedAt)) {
|
||||
state.agentStatus = chunk.status
|
||||
@@ -960,6 +995,7 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
|
||||
case 'done':
|
||||
state.currentToolStatus = null
|
||||
updatePlanBlockStatus('done')
|
||||
if (chunk.usage) {
|
||||
lastDoneUsage = { ...chunk.usage }
|
||||
state.sessionTokenUsage = {
|
||||
@@ -972,6 +1008,7 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
break
|
||||
|
||||
case 'error':
|
||||
updatePlanBlockStatus('skipped')
|
||||
if (state.currentToolStatus) {
|
||||
state.currentToolStatus = {
|
||||
...state.currentToolStatus,
|
||||
@@ -1362,6 +1399,8 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
appendTextToBlocks,
|
||||
appendThinkToBlocks,
|
||||
appendChartsToBlocks,
|
||||
appendPlanToBlocks,
|
||||
updatePlanBlockStatus,
|
||||
appendErrorToBlocks,
|
||||
addToolBlock,
|
||||
updateToolBlockStatus,
|
||||
@@ -1467,6 +1506,12 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
state.currentToolStatus = null
|
||||
state.isLoadingSource = false
|
||||
break
|
||||
case 'plan':
|
||||
if (chunk.plan) {
|
||||
appendPlanToBlocks(chunk.plan)
|
||||
updatePlanBlockStatus('executing')
|
||||
}
|
||||
break
|
||||
case 'status':
|
||||
if (chunk.status && (!state.agentStatus || chunk.status.updatedAt >= state.agentStatus.updatedAt)) {
|
||||
state.agentStatus = chunk.status
|
||||
@@ -1474,10 +1519,12 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
break
|
||||
case 'done':
|
||||
state.currentToolStatus = null
|
||||
updatePlanBlockStatus('done')
|
||||
if (chunk.usage) lastDoneUsage = { ...chunk.usage }
|
||||
setAgentPhase(state, 'completed', chunk.usage ? { totalUsage: chunk.usage } : undefined)
|
||||
break
|
||||
case 'error': {
|
||||
updatePlanBlockStatus('skipped')
|
||||
hasStreamError = true
|
||||
if (state.currentToolStatus) updateToolBlockStatus(state.currentToolStatus.name, 'error')
|
||||
const blocks = targetBuffer.messages[aiMessageIndex].contentBlocks || []
|
||||
|
||||
Reference in new issue
Block a user