Skip to content

Commit edd8fa7

Browse files
committed
Merge branch 'feat/robot-330' into develop
2 parents ff0b003 + 9e4da52 commit edd8fa7

11 files changed

Lines changed: 307 additions & 96 deletions

File tree

lint-staged.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
module.exports = {
2-
'./packages/**/**.{js,mjs,jsx,ts,mts,tsx,vue}': 'eslint',
2+
'./packages/**/**.{js,mjs,jsx,ts,mts,tsx,vue}': 'eslint --no-warn-ignored',
33
'./packages/**/**.{js,mjs,jsx,ts,mts,tsx,vue,html,json,less}': 'prettier --write'
44
}

packages/plugins/robot/src/Main.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
v-model:show="robotVisible"
2020
v-model:input="inputMessage"
2121
:status="mappedStatus"
22+
:chat-mode="robotSettingState.chatMode"
2223
:prompt-items="promptItems"
2324
:bubble-renderers="bubbleRenderers"
2425
:allowFiles="isVisualModel && robotSettingState.chatMode === ChatMode.Agent"

packages/plugins/robot/src/components/chat/RobotChat.vue

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ import {
9292
TrAttachments,
9393
UploadButton,
9494
VoiceButton,
95+
BubbleRenderers,
9596
BubbleRendererMatchPriority,
9697
type BubbleRoleConfig,
9798
type PromptProps,
@@ -112,6 +113,7 @@ const props = defineProps({
112113
type: Function
113114
},
114115
status: { type: String },
116+
chatMode: { type: String },
115117
allowFiles: {
116118
type: Boolean,
117119
default: false
@@ -160,6 +162,11 @@ const contentRendererMatches = computed<BubbleContentRendererMatch[]>(() => [
160162
find: (_message: any, content: any) => content?.type === type,
161163
renderer
162164
})),
165+
{
166+
priority: BubbleRendererMatchPriority.NORMAL,
167+
find: (message: any, content: any) => content?.type === 'tool' && message.tool_calls?.length,
168+
renderer: BubbleRenderers.Tools
169+
},
163170
{
164171
priority: BubbleRendererMatchPriority.NORMAL,
165172
find: (message: any, content: any) =>
@@ -173,6 +180,42 @@ const contentRendererMatches = computed<BubbleContentRendererMatch[]>(() => [
173180
}
174181
])
175182
183+
const isAgentMessage = (message: any) => {
184+
const hasAgentContent = message.renderContent?.some((item: any) => {
185+
return item.type === 'agent-content' || item.type === 'agent-loading'
186+
})
187+
return message.metadata?.chatMode === 'agent' || hasAgentContent
188+
}
189+
190+
const resolveAgentRenderContent = (message: any) => {
191+
if (!isAgentMessage(message) || message.role !== 'assistant') {
192+
return message.renderContent
193+
}
194+
195+
const isLastMessage = messages.value.at(-1) === message
196+
const isGenerating = Boolean(message.loading) || (isLastMessage && GeneratingStatus.includes(props.status as any))
197+
const renderContent = isGenerating
198+
? message.renderContent
199+
: message.renderContent.filter((item: any) => item.type !== 'agent-loading')
200+
const agentContents = renderContent.filter((item: any) => item.type === 'agent-content')
201+
const finalStatus = agentContents.findLast((item: any) => ['success', 'failed', 'fix'].includes(item.status))?.status
202+
203+
return renderContent.map((item: any) => {
204+
if (item.type !== 'agent-content' || isGenerating) {
205+
return item
206+
}
207+
208+
if (!item.status || item.status === 'loading') {
209+
return {
210+
...item,
211+
status: finalStatus || message.metadata?.agentStatus || 'failed'
212+
}
213+
}
214+
215+
return item
216+
})
217+
}
218+
176219
// 处理文件选择事件
177220
const handleSingleFilesSelected = (files: File[] | null, retry = false) => {
178221
if (!files?.length) return
@@ -230,7 +273,20 @@ const welcomeIcon = getSvgIcon('AI', { fontSize: '44px' })
230273
231274
const resolveMessageContent = (message: any) => {
232275
if (Array.isArray(message.renderContent) && message.renderContent.length > 0) {
233-
return message.renderContent
276+
return resolveAgentRenderContent(message)
277+
}
278+
279+
if (isAgentMessage(message) && message.role === 'assistant' && message.content) {
280+
const agentStatus = ['success', 'failed', 'fix'].includes(message.metadata?.agentStatus)
281+
? message.metadata.agentStatus
282+
: 'failed'
283+
return [
284+
{
285+
type: 'agent-content',
286+
status: agentStatus,
287+
content: message.content
288+
}
289+
]
234290
}
235291
236292
return message.content

packages/plugins/robot/src/components/renderers/AgentRenderer.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ export const resolveAgentRenderState = (props: any) => {
1616
const contentItem = Array.isArray(renderContent) ? renderContent[props.contentIndex || 0] || {} : {}
1717
1818
return {
19-
status: contentItem.status || props.status || 'loading',
20-
content: contentItem.content ?? props.content,
21-
contentType: contentItem.type || contentItem.contentType || props.contentType
19+
status: props.status || contentItem.status || 'loading',
20+
content: props.content || contentItem.content,
21+
contentType: props.type || props.contentType || contentItem.type || contentItem.contentType
2222
}
2323
}
2424
@@ -35,6 +35,9 @@ export default {
3535
contentType: {
3636
type: String
3737
},
38+
type: {
39+
type: String
40+
},
3841
message: {
3942
type: Object,
4043
default: () => ({})

packages/plugins/robot/src/composables/core/useConversation.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export interface ConversationMetadata {
3131
[key: string]: any
3232
}
3333

34+
let currentConversationMetadata: ConversationMetadata = {}
35+
3436
const createResponseProvider = (
3537
provider: Pick<OpenAICompatibleProvider, 'chatStream'>
3638
): UseMessageOptions['responseProvider'] => {
@@ -102,6 +104,7 @@ const updateMessageMetadata = (currentMessage: ChatMessage, chunk: ChatCompletio
102104
currentMessage.loading = undefined
103105
currentMessage.renderContent ||= []
104106
currentMessage.metadata ||= {}
107+
currentMessage.metadata.chatMode ||= currentConversationMetadata.chatMode
105108
currentMessage.metadata.createdAt ||= chunk.created
106109
currentMessage.metadata.updatedAt = Math.floor(Date.now() / 1000)
107110
currentMessage.metadata.id ||= chunk.id
@@ -191,6 +194,9 @@ export function useConversationAdapter(options: ConversationAdapterOptions) {
191194
...(conversation.metadata || {}),
192195
...metadata
193196
}
197+
if (conversationId === activeConversationId.value) {
198+
currentConversationMetadata = conversation.metadata
199+
}
194200
conversation.updatedAt = Date.now()
195201
void saveConversation(conversation)
196202
}
@@ -251,12 +257,14 @@ export function useConversationAdapter(options: ConversationAdapterOptions) {
251257
...metadata
252258
}
253259
}
260+
currentConversationMetadata = conversation.metadata || {}
254261
void saveConversation(conversation)
255262
return currentId
256263
}
257264
}
258265

259266
const conversation = createConversationKit({ title, metadata })
267+
currentConversationMetadata = conversation.metadata || {}
260268
return conversation.id
261269
}
262270

@@ -277,6 +285,7 @@ export function useConversationAdapter(options: ConversationAdapterOptions) {
277285
const result = await switchConversationKit(conversationId)
278286

279287
if (result && onStart) {
288+
currentConversationMetadata = conversation.metadata || {}
280289
onStart(conversationState, messageManager.messages.value, {
281290
createConversation,
282291
switchConversation,
@@ -331,8 +340,6 @@ export function useConversationAdapter(options: ConversationAdapterOptions) {
331340
conversationState,
332341
// 会话方法(包装后,覆盖原始方法)
333342
...apis,
334-
createConversation,
335-
switchConversation,
336343
autoSetTitle
337344
}
338345
}

packages/plugins/robot/src/composables/modes/useAgentMode.ts

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,50 @@ const updateToolCallRenderContent = (tool: Record<string, unknown>, renderConten
4747
}
4848
}
4949

50+
const normalizeFinishedAgentMessages = (messages: any[]) => {
51+
messages.forEach((message) => {
52+
if (message.role !== 'assistant' || message.loading || !Array.isArray(message.renderContent)) {
53+
return
54+
}
55+
56+
message.renderContent = message.renderContent.filter((item: any) => item.type !== 'agent-loading')
57+
const agentContents = message.renderContent.filter((item: any) => item.type === 'agent-content')
58+
const finalStatus = agentContents.findLast((item: any) =>
59+
['success', 'failed', 'fix'].includes(item.status)
60+
)?.status
61+
62+
message.renderContent.forEach((item: any) => {
63+
if (item.type === 'agent-content' && (!item.status || item.status === 'loading')) {
64+
item.status = finalStatus || message.metadata?.agentStatus || 'failed'
65+
}
66+
})
67+
})
68+
}
69+
70+
const markLastAgentContentFailed = (messages: any[], content: unknown) => {
71+
const lastMessage = messages.at(-1)
72+
if (!lastMessage) {
73+
return
74+
}
75+
76+
lastMessage.loading = undefined
77+
lastMessage.metadata = {
78+
...(lastMessage.metadata || {}),
79+
chatMode: ChatMode.Agent,
80+
agentStatus: 'failed'
81+
}
82+
lastMessage.renderContent ||= []
83+
lastMessage.renderContent = lastMessage.renderContent.filter((item: any) => item.type !== 'agent-loading')
84+
85+
const lastAgentContent = lastMessage.renderContent.findLast((item: any) => item.type === 'agent-content')
86+
const errorInfo = { content: content || '页面生成失败', status: 'failed' }
87+
if (lastAgentContent) {
88+
Object.assign(lastAgentContent, errorInfo)
89+
} else {
90+
lastMessage.renderContent.push({ type: 'agent-content', ...errorInfo })
91+
}
92+
}
93+
5094
/**
5195
* Agent 模式实现
5296
* 特点:
@@ -76,6 +120,8 @@ export default function useAgentMode(): ModeHooks {
76120
apis.updateMetadata(conversationState.currentId, { chatMode: ChatMode.Agent })
77121
}
78122

123+
normalizeFinishedAgentMessages(messages)
124+
79125
// Agent 模式特殊处理:标记失败的 loading
80126
messages.at(-1)?.renderContent?.forEach((item: any) => {
81127
if (item.type.includes('loading') || item.status !== 'success') {
@@ -148,12 +194,7 @@ export default function useAgentMode(): ModeHooks {
148194
) => {
149195
if (finishReason === 'aborted' || finishReason === 'error') {
150196
removeLoading(messages)
151-
const errorInfo = { content: extraData?.error || '请求失败', status: 'failed' }
152-
if (messages.at(-1).renderContent.at(-1)) {
153-
Object.assign(messages.at(-1).renderContent.at(-1), errorInfo)
154-
} else {
155-
messages.at(-1).renderContent = [{ type: getContentType(), ...errorInfo }]
156-
}
197+
markLastAgentContentFailed(messages, extraData?.error || content || '请求失败')
157198
}
158199
}
159200

@@ -193,9 +234,7 @@ export default function useAgentMode(): ModeHooks {
193234
lastMessage.renderContent.at(-1)
194235

195236
if (finishReason === 'aborted' || finishReason === 'error') {
196-
if (lastRenderContent) {
197-
lastRenderContent.status = 'failed'
198-
}
237+
markLastAgentContentFailed(messages, content || '页面生成失败')
199238
return
200239
}
201240

@@ -235,6 +274,11 @@ export default function useAgentMode(): ModeHooks {
235274
if (lastRenderContent) {
236275
lastRenderContent.status = 'failed'
237276
}
277+
lastMessage.metadata = {
278+
...(lastMessage.metadata || {}),
279+
chatMode: ChatMode.Agent,
280+
agentStatus: 'failed'
281+
}
238282
if (error instanceof Error && error.message.includes('canceled')) {
239283
messageState.status = STATUS.ABORTED
240284
} else {
@@ -255,8 +299,18 @@ export default function useAgentMode(): ModeHooks {
255299
lastRenderContent.status = 'success'
256300
lastRenderContent.schema = renderedSchema
257301
}
302+
lastMessage.metadata = {
303+
...(lastMessage.metadata || {}),
304+
chatMode: ChatMode.Agent,
305+
agentStatus: 'success'
306+
}
258307
} else if (lastRenderContent) {
259308
lastRenderContent.status = 'failed'
309+
lastMessage.metadata = {
310+
...(lastMessage.metadata || {}),
311+
chatMode: ChatMode.Agent,
312+
agentStatus: 'failed'
313+
}
260314
}
261315

262316
pageSchema = null

packages/plugins/robot/src/composables/modes/useChatMode.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ const updateToolCallState = (
3838
}
3939
}
4040

41+
const syncToolCallRenderContent = (currentMessage: any) => {
42+
if (!currentMessage.tool_calls?.length) {
43+
return
44+
}
45+
46+
currentMessage.renderContent ||= []
47+
if (!currentMessage.renderContent.some((item: any) => item.type === 'tool')) {
48+
currentMessage.renderContent.push({ type: 'tool' })
49+
}
50+
}
51+
4152
/**
4253
* Chat 模式实现
4354
* 特点:
@@ -123,10 +134,12 @@ export default function useChatMode(): ModeHooks {
123134

124135
const onStreamTools = (tools: Record<string, unknown>[], { currentMessage }: { currentMessage: any }) => {
125136
tools.forEach((tool) => updateToolCallState(tool, currentMessage))
137+
syncToolCallRenderContent(currentMessage)
126138
}
127139

128140
const onBeforeCallTool = (tool: Record<string, unknown>, { currentMessage }: { currentMessage: any }) => {
129141
updateToolCallState(tool, currentMessage)
142+
syncToolCallRenderContent(currentMessage)
130143
}
131144

132145
const onPostCallTool = (
@@ -136,6 +149,7 @@ export default function useChatMode(): ModeHooks {
136149
{ currentMessage }: { currentMessage: any }
137150
) => {
138151
updateToolCallState(tool, currentMessage, { status: toolCallStatus, result: toolCallResult })
152+
syncToolCallRenderContent(currentMessage)
139153
}
140154

141155
const onPostCallTools = (_toolsResult: Record<string, unknown>[], _context: { currentMessage: any }) => {

0 commit comments

Comments
 (0)