Skip to content

Commit 6d31656

Browse files
committed
fix:fix review
1 parent 3b65f7d commit 6d31656

7 files changed

Lines changed: 323 additions & 112 deletions

File tree

packages/plugins/robot/src/Main.vue

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ import { AgentRenderer } from './components/renderers'
9393
import useChat from './composables/useChat'
9494
import useModelConfig from './composables/core/useConfig'
9595
import { ChatMode } from './types/mode.types'
96+
import { STATUS } from './constants/status'
97+
import {
98+
AgentMessageStatus,
99+
RobotMessageContentType,
100+
RobotMessageRole,
101+
isAgentFinalStatus,
102+
type MessageResolverContext,
103+
type RobotMessage,
104+
type RobotRenderContentItem
105+
} from './types'
96106
import apiService from './services/api'
97107
98108
const props = defineProps({
@@ -224,9 +234,9 @@ const promptClickHandler = (item: PromptProps & { mode?: 'chat' | 'agent' }) =>
224234
changeChatMode(item.mode)
225235
}
226236
messages.value.push({
227-
role: 'user',
237+
role: RobotMessageRole.User,
228238
content: item.description || '',
229-
renderContent: [{ type: 'text', content: item.description }]
239+
renderContent: [{ type: RobotMessageContentType.Text, content: item.description }]
230240
})
231241
sendUserMessage()
232242
}
@@ -245,51 +255,61 @@ const openAIRobot = () => {
245255
useLayout().closeSetting(true)
246256
}
247257
248-
// 当前Robot的bubbleRenderers无法做到响应式更新,因此Agent模式的type要与Chat模式不同
249-
const bubbleRenderers = { 'agent-content': AgentRenderer, 'agent-loading': AgentRenderer }
258+
// 当前 Robot 的 bubbleRenderers 无法做到响应式更新,因此 Agent 模式需要独立的内容类型。
259+
// `agent-content` 表示 Agent 的最终内容片段;`agent-loading` 表示 Agent 处理中间态占位片段。
260+
const bubbleRenderers = {
261+
[RobotMessageContentType.AgentContent]: AgentRenderer,
262+
[RobotMessageContentType.AgentLoading]: AgentRenderer
263+
}
250264
251-
const resolveChatMessageContent = (message: any, context: { messages: any[]; status: string }) => {
252-
const hasAgentContent = message.renderContent?.some((item: any) => {
253-
return item.type === 'agent-content' || item.type === 'agent-loading'
265+
const resolveChatMessageContent = (message: RobotMessage, context: MessageResolverContext) => {
266+
const renderContent = Array.isArray(message.renderContent) ? message.renderContent : []
267+
const hasAgentContent = renderContent.some((item) => {
268+
return item.type === RobotMessageContentType.AgentContent || item.type === RobotMessageContentType.AgentLoading
254269
})
255-
const isAgentMessage = message.metadata?.chatMode === 'agent' || hasAgentContent
270+
const isAgentMessage = message.metadata?.chatMode === ChatMode.Agent || hasAgentContent
256271
257-
if (!isAgentMessage || message.role !== 'assistant') {
272+
if (!isAgentMessage || message.role !== RobotMessageRole.Assistant) {
258273
return Array.isArray(message.renderContent) && message.renderContent.length > 0
259274
? message.renderContent
260275
: message.content
261276
}
262277
278+
// context.status 是当前整轮对话请求的运行状态,不是单条渲染片段状态。
263279
const isLastMessage = context.messages.at(-1) === message
264-
const isGenerating = Boolean(message.loading) || (isLastMessage && context.status !== 'finished')
265-
const renderContent = isGenerating
266-
? message.renderContent || []
267-
: (message.renderContent || []).filter((item: any) => item.type !== 'agent-loading')
268-
const agentContents = renderContent.filter((item: any) => item.type === 'agent-content')
269-
const finalStatus = agentContents.findLast((item: any) => ['success', 'failed', 'fix'].includes(item.status))?.status
280+
const isGenerating = Boolean(message.loading) || (isLastMessage && context.status !== STATUS.FINISHED)
281+
const resolvedRenderContent = isGenerating
282+
? renderContent
283+
: renderContent.filter((item) => item.type !== RobotMessageContentType.AgentLoading)
284+
const agentContents = resolvedRenderContent.filter(
285+
(item): item is RobotRenderContentItem =>
286+
item.type === RobotMessageContentType.AgentContent || item.type === RobotMessageContentType.AgentLoading
287+
)
288+
// item.status 是单个 agent 片段的业务结果,例如 success/failed/fix/loading。
289+
const finalStatus = agentContents.findLast((item) => isAgentFinalStatus(item.status))?.status
270290
271291
if (!Array.isArray(message.renderContent) || message.renderContent.length === 0) {
272-
const agentStatus = ['success', 'failed', 'fix'].includes(message.metadata?.agentStatus)
292+
const agentStatus = isAgentFinalStatus(message.metadata?.agentStatus)
273293
? message.metadata.agentStatus
274-
: 'failed'
294+
: AgentMessageStatus.Failed
275295
return [
276296
{
277-
type: 'agent-content',
297+
type: RobotMessageContentType.AgentContent,
278298
status: agentStatus,
279299
content: message.content
280300
}
281301
]
282302
}
283303
284-
return renderContent.map((item: any) => {
285-
if (item.type !== 'agent-content' || isGenerating) {
304+
return resolvedRenderContent.map((item) => {
305+
if (item.type !== RobotMessageContentType.AgentContent || isGenerating) {
286306
return item
287307
}
288308
289-
if (!item.status || item.status === 'loading') {
309+
if (!item.status || item.status === AgentMessageStatus.Loading) {
290310
return {
291311
...item,
292-
status: finalStatus || message.metadata?.agentStatus || 'failed'
312+
status: finalStatus || message.metadata?.agentStatus || AgentMessageStatus.Failed
293313
}
294314
}
295315

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

Lines changed: 38 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</template>
1212

1313
<div class="robot-chat-container-content" ref="chatContainerRef">
14-
<div v-if="messages.filter((item) => item.role !== 'system').length === 0">
14+
<div v-if="messages.filter((item) => item.role !== RobotMessageRole.System).length === 0">
1515
<tr-welcome title="AI助手" description="您好,我是您的开发小助手" :icon="welcomeIcon" class="robot-welcome">
1616
</tr-welcome>
1717
<tr-prompts
@@ -99,10 +99,18 @@ import {
9999
type RawFileAttachment,
100100
type BubbleContentRendererMatch
101101
} from '@opentiny/tiny-robot'
102-
import { type ChatMessage } from '@opentiny/tiny-robot-kit'
103102
import { GeneratingStatus } from '../../constants/status'
104103
import { LoadingRenderer, MarkdownRenderer, ImgRenderer } from '../renderers'
105104
import { useNotify } from '@opentiny/tiny-engine-meta-register'
105+
import {
106+
RobotMessageContentType,
107+
RobotMessageRole,
108+
type MessageContentResolver,
109+
type RobotInputContentPart,
110+
type RobotMessage,
111+
type RobotRenderContentItem
112+
} from '../../types'
113+
import { extractMessageText } from '../../utils'
106114
107115
const props = defineProps({
108116
promptItems: {
@@ -114,7 +122,7 @@ const props = defineProps({
114122
},
115123
status: { type: String },
116124
messageContentResolver: {
117-
type: Function
125+
type: Function as PropType<MessageContentResolver>
118126
},
119127
allowFiles: {
120128
type: Boolean,
@@ -141,7 +149,7 @@ const selectedAttachments = ref([])
141149
const robotVisible = defineModel<boolean>('show', { required: true })
142150
const fullscreen = defineModel<boolean>('fullscreen')
143151
const inputMessage = defineModel<string>('input', { required: true })
144-
const messages = defineModel<ChatMessage[]>('messages', { required: true })
152+
const messages = defineModel<RobotMessage[]>('messages', { required: true })
145153
const senderRef = ref<InstanceType<typeof TrSender> | null>(null)
146154
147155
watch(
@@ -161,47 +169,29 @@ const contentRendererMatches = computed<BubbleContentRendererMatch[]>(() => [
161169
},
162170
...Object.entries(props.bubbleRenderers).map(([type, renderer]) => ({
163171
priority: BubbleRendererMatchPriority.NORMAL,
164-
find: (_message: any, content: any) => content?.type === type,
172+
find: (_message: RobotMessage, content: RobotRenderContentItem) => content?.type === type,
165173
renderer
166174
})),
167175
{
168176
priority: BubbleRendererMatchPriority.NORMAL,
169-
find: (message: any, content: any) => content?.type === 'tool' && message.tool_calls?.length,
177+
find: (message: RobotMessage, content: RobotRenderContentItem) =>
178+
content?.type === RobotMessageContentType.Tool && Boolean(message.tool_calls?.length),
170179
renderer: BubbleRenderers.Tools
171180
},
172181
{
173182
priority: BubbleRendererMatchPriority.NORMAL,
174-
find: (_message: any, content: any) => !content?.type || ['markdown', 'text'].includes(content.type),
183+
find: (_message: RobotMessage, content: RobotRenderContentItem) =>
184+
!content?.type || [RobotMessageContentType.Markdown, RobotMessageContentType.Text].includes(content.type as any),
175185
renderer: MarkdownRenderer
176186
},
177187
{
178188
priority: BubbleRendererMatchPriority.NORMAL,
179-
find: (_message: any, content: any) => ['img', 'image'].includes(content?.type),
189+
find: (_message: RobotMessage, content: RobotRenderContentItem) =>
190+
[RobotMessageContentType.Img, RobotMessageContentType.Image].includes(content?.type as any),
180191
renderer: ImgRenderer
181192
}
182193
])
183194
184-
const getTextContent = (content: any) => {
185-
if (typeof content === 'string') {
186-
return content
187-
}
188-
if (Array.isArray(content)) {
189-
return content
190-
.map((item) => {
191-
if (typeof item === 'string') {
192-
return item
193-
}
194-
if (item?.type === 'text') {
195-
return item.text ?? item.content ?? ''
196-
}
197-
return ''
198-
})
199-
.filter(Boolean)
200-
.join('\n')
201-
}
202-
return ''
203-
}
204-
205195
// 处理文件选择事件
206196
const handleSingleFilesSelected = (files: File[] | null, retry = false) => {
207197
if (!files?.length) return
@@ -257,7 +247,7 @@ const getSvgIcon = (name: string, style?: CSSProperties) => {
257247
const aiAvatar = getSvgIcon('AI')
258248
const welcomeIcon = getSvgIcon('AI', { fontSize: '44px' })
259249
260-
const resolveMessageContent = (message: any) => {
250+
const resolveMessageContent = (message: RobotMessage) => {
261251
if (props.messageContentResolver) {
262252
return props.messageContentResolver(message, {
263253
messages: messages.value,
@@ -266,16 +256,16 @@ const resolveMessageContent = (message: any) => {
266256
}
267257
268258
if (Array.isArray(message.renderContent) && message.renderContent.length > 0) {
269-
return message.renderContent.map((item: any) => {
270-
if (item?.type === 'img' || item?.type === 'image') {
259+
return message.renderContent.map((item) => {
260+
if (item?.type === RobotMessageContentType.Img || item?.type === RobotMessageContentType.Image) {
271261
return {
272-
type: 'img',
262+
type: RobotMessageContentType.Img,
273263
content: item.content || item.url || item.image_url?.url || ''
274264
}
275265
}
276-
if (item?.type === 'text') {
266+
if (item?.type === RobotMessageContentType.Text) {
277267
return {
278-
type: 'text',
268+
type: RobotMessageContentType.Text,
279269
content: item.content ?? item.text ?? ''
280270
}
281271
}
@@ -284,15 +274,13 @@ const resolveMessageContent = (message: any) => {
284274
}
285275
286276
if (Array.isArray(message.content) && message.content.length > 0) {
287-
const textContent = getTextContent(
288-
message.content.map((item: any) => item?.text ?? item?.content ?? item?.image_url?.url ?? '')
289-
)
277+
const textContent = extractMessageText(message.content)
290278
if (textContent) {
291279
return textContent
292280
}
293281
}
294282
295-
const textContent = getTextContent(message.content)
283+
const textContent = extractMessageText(message.content)
296284
if (textContent) {
297285
return textContent
298286
}
@@ -301,14 +289,14 @@ const resolveMessageContent = (message: any) => {
301289
}
302290
303291
const roleConfigs: Record<string, BubbleRoleConfig> = {
304-
assistant: {
292+
[RobotMessageRole.Assistant]: {
305293
placement: 'start',
306294
avatar: aiAvatar
307295
},
308-
user: {
296+
[RobotMessageRole.User]: {
309297
placement: 'end'
310298
},
311-
system: {
299+
[RobotMessageRole.System]: {
312300
hidden: true
313301
}
314302
}
@@ -328,38 +316,38 @@ const handleSendMessage = async (content: string) => {
328316
return
329317
}
330318
331-
const userMessage: ChatMessage = {
332-
role: 'user',
319+
const userMessage: RobotMessage = {
320+
role: RobotMessageRole.User,
333321
content: messageContent
334322
}
335323
const files = selectedAttachments.value.filter((item) => item.status === 'success')
336324
if (files.length > 0) {
337325
userMessage.content = [
338326
{
339-
type: 'text',
327+
type: RobotMessageContentType.Text,
340328
text: messageContent
341329
},
342330
...files.map((item) => ({
343-
type: 'image_url',
331+
type: RobotMessageContentType.ImageUrl,
344332
image_url: {
345333
url: item.url
346334
}
347335
}))
348-
] as any
336+
] as RobotInputContentPart[]
349337
userMessage.renderContent = [
350338
{
351-
type: 'text',
339+
type: RobotMessageContentType.Text,
352340
content: messageContent
353341
},
354342
...files.map((item) => ({
355-
type: 'img',
343+
type: RobotMessageContentType.Img,
356344
content: item.url
357345
}))
358346
]
359347
} else {
360348
userMessage.renderContent = [
361349
{
362-
type: 'text',
350+
type: RobotMessageContentType.Text,
363351
content: messageContent
364352
}
365353
]

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import DOMPurify from 'dompurify'
88
import MarkdownIt from 'markdown-it'
99
import type { Options } from 'markdown-it'
1010
import hljs from 'highlight.js/lib/core'
11+
import { extractMessageText } from '../../utils'
1112
import 'highlight.js/styles/github.css'
1213
1314
// 按需加载语言
@@ -29,7 +30,7 @@ hljs.registerLanguage('xml', xml)
2930
hljs.registerLanguage('shell', shell)
3031
3132
interface MarkdownMessage {
32-
content: string | string[] | Record<string, unknown>[]
33+
content: unknown
3334
}
3435
3536
const props = defineProps({
@@ -71,14 +72,14 @@ const markdownIt = new MarkdownIt({
7172
})
7273
7374
const renderContent = computed(() => {
74-
const content = Array.isArray(props.message.content)
75-
? props.message.content
76-
.map((item: any) => item?.text ?? item?.content ?? '')
77-
.filter(Boolean)
78-
.join('\n')
79-
: typeof props.message.content === 'string'
80-
? props.message.content
81-
: ''
75+
let content = ''
76+
77+
if (typeof props.message.content === 'string') {
78+
content = props.message.content
79+
} else if (Array.isArray(props.message.content)) {
80+
content = extractMessageText(props.message.content)
81+
}
82+
8283
return DOMPurify.sanitize(markdownIt.render(content))
8384
})
8485
</script>

0 commit comments

Comments
 (0)