Skip to content

Commit 87ac1e0

Browse files
authored
feat:Migration of tiny robot version to 0.4.0 (opentiny#1804)
1 parent 4769de9 commit 87ac1e0

23 files changed

Lines changed: 1451 additions & 258 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/package.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"access": "public"
66
},
77
"scripts": {
8-
"build": "vite build"
8+
"build": "vite build",
9+
"test": "vitest run",
10+
"test:watch": "vitest"
911
},
1012
"type": "module",
1113
"main": "dist/index.js",
@@ -29,9 +31,9 @@
2931
"@opentiny/tiny-engine-common": "workspace:*",
3032
"@opentiny/tiny-engine-meta-register": "workspace:*",
3133
"@opentiny/tiny-engine-utils": "workspace:*",
32-
"@opentiny/tiny-robot": "0.3.1",
33-
"@opentiny/tiny-robot-kit": "0.3.1",
34-
"@opentiny/tiny-robot-svgs": "0.3.1",
34+
"@opentiny/tiny-robot": "0.4.0",
35+
"@opentiny/tiny-robot-kit": "0.4.0",
36+
"@opentiny/tiny-robot-svgs": "0.4.0",
3537
"@opentiny/tiny-schema-renderer": "1.0.0-beta.6",
3638
"@vueuse/core": "^9.13.0",
3739
"dompurify": "^3.0.1",
@@ -45,7 +47,8 @@
4547
"@types/markdown-it": "^14.1.2",
4648
"@vitejs/plugin-vue": "^5.1.2",
4749
"@vitejs/plugin-vue-jsx": "^4.0.1",
48-
"vite": "^5.4.2"
50+
"vite": "^5.4.2",
51+
"vitest": "^1.6.1"
4952
},
5053
"peerDependencies": {
5154
"@opentiny/vue": "^3.20.0",

packages/plugins/robot/src/Main.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
v-model:fullscreen="fullscreen"
1919
v-model:show="robotVisible"
2020
v-model:input="inputMessage"
21-
:status="chatStatus"
21+
:status="mappedStatus"
22+
:chat-mode="robotSettingState.chatMode"
2223
:prompt-items="promptItems"
2324
:bubble-renderers="bubbleRenderers"
2425
:allowFiles="isVisualModel && robotSettingState.chatMode === ChatMode.Agent"
26+
:show-aborted="robotSettingState.chatMode !== ChatMode.Agent"
2527
:beforeSubmit="checkApiKey"
2628
:promptClickHandler="promptClickHandler"
2729
@fileSelected="handleFileSelected"
@@ -147,7 +149,7 @@ const showTeleport = ref(false)
147149
const showSetting = ref(false)
148150
149151
const {
150-
chatStatus,
152+
mappedStatus,
151153
inputMessage,
152154
messages,
153155
changeChatMode,

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

Lines changed: 133 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,18 @@
2222
@item-click="handlePromptItemClick"
2323
></tr-prompts>
2424
</div>
25-
<tr-bubble-provider v-else :content-renderers="contentRenderers">
26-
<tr-bubble-list :items="messages" :roles="roles" auto-scroll class="robot-bubble-list"> </tr-bubble-list>
25+
<tr-bubble-provider v-else :content-renderer-matches="contentRendererMatches">
26+
<tr-bubble-list
27+
:messages="messages"
28+
:role-configs="roleConfigs"
29+
:content-resolver="resolveMessageContent"
30+
auto-scroll
31+
class="robot-bubble-list"
32+
>
33+
<template #content-footer="{ messages }">
34+
<div v-if="showAborted && messages[0]?.aborted" class="aborted">已中止</div>
35+
</template>
36+
</tr-bubble-list>
2737
</tr-bubble-provider>
2838
</div>
2939

@@ -39,9 +49,6 @@
3949
:showWordLimit="false"
4050
@submit="handleSendMessage"
4151
@cancel="handleAbortRequest"
42-
:allowFiles="selectedAttachments.length < 1 && props.allowFiles"
43-
uploadTooltip="支持上传1张图片"
44-
@files-selected="handleSingleFilesSelected"
4552
>
4653
<template #header v-if="selectedAttachments.length > 0">
4754
<div>
@@ -55,9 +62,18 @@
5562
</tr-attachments>
5663
</div>
5764
</template>
58-
<template #footer-left>
65+
<template #footer>
5966
<slot name="footer-left"></slot>
6067
</template>
68+
<template #footer-right>
69+
<VoiceButton :speech-config="{ lang: 'zh-CN', continuous: false }" />
70+
<UploadButton
71+
v-if="selectedAttachments.length < 1 && props.allowFiles"
72+
accept="image/*"
73+
:multiple="false"
74+
@select="handleSingleFilesSelected"
75+
/>
76+
</template>
6177
</tr-sender>
6278
</div>
6379
</template>
@@ -74,11 +90,17 @@ import {
7490
TrSender,
7591
TrWelcome,
7692
TrAttachments,
93+
UploadButton,
94+
VoiceButton,
95+
BubbleRenderers,
96+
BubbleRendererMatchPriority,
7797
type BubbleRoleConfig,
7898
type PromptProps,
79-
type RawFileAttachment
99+
type RawFileAttachment,
100+
type BubbleContentRendererMatch
80101
} from '@opentiny/tiny-robot'
81-
import { type ChatMessage, GeneratingStatus } from '@opentiny/tiny-robot-kit'
102+
import { type ChatMessage } from '@opentiny/tiny-robot-kit'
103+
import { GeneratingStatus } from '../../constants/status'
82104
import { LoadingRenderer, MarkdownRenderer, ImgRenderer } from '../renderers'
83105
import { useNotify } from '@opentiny/tiny-engine-meta-register'
84106
@@ -91,10 +113,15 @@ const props = defineProps({
91113
type: Function
92114
},
93115
status: { type: String },
116+
chatMode: { type: String },
94117
allowFiles: {
95118
type: Boolean,
96119
default: false
97120
},
121+
showAborted: {
122+
type: Boolean,
123+
default: true
124+
},
98125
bubbleRenderers: {
99126
type: Object as PropType<Record<string, Component>>,
100127
default: () => ({})
@@ -113,6 +140,7 @@ const robotVisible = defineModel<boolean>('show', { required: true })
113140
const fullscreen = defineModel<boolean>('fullscreen')
114141
const inputMessage = defineModel<string>('input', { required: true })
115142
const messages = defineModel<ChatMessage[]>('messages', { required: true })
143+
const senderRef = ref<InstanceType<typeof TrSender> | null>(null)
116144
117145
watch(
118146
() => props.allowFiles,
@@ -123,6 +151,71 @@ watch(
123151
}
124152
)
125153
154+
const contentRendererMatches = computed<BubbleContentRendererMatch[]>(() => [
155+
{
156+
priority: BubbleRendererMatchPriority.LOADING,
157+
find: (message) => Boolean(message.loading),
158+
renderer: LoadingRenderer
159+
},
160+
...Object.entries(props.bubbleRenderers).map(([type, renderer]) => ({
161+
priority: BubbleRendererMatchPriority.NORMAL,
162+
find: (_message: any, content: any) => content?.type === type,
163+
renderer
164+
})),
165+
{
166+
priority: BubbleRendererMatchPriority.NORMAL,
167+
find: (message: any, content: any) => content?.type === 'tool' && message.tool_calls?.length,
168+
renderer: BubbleRenderers.Tools
169+
},
170+
{
171+
priority: BubbleRendererMatchPriority.NORMAL,
172+
find: (message: any, content: any) =>
173+
!message.loading && message.content && (!content?.type || ['markdown', 'text'].includes(content.type)),
174+
renderer: MarkdownRenderer
175+
},
176+
{
177+
priority: BubbleRendererMatchPriority.NORMAL,
178+
find: (message: any) => message?.content?.[0]?.type === 'img' || message?.content?.[0]?.type === 'image',
179+
renderer: ImgRenderer
180+
}
181+
])
182+
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+
126219
// 处理文件选择事件
127220
const handleSingleFilesSelected = (files: File[] | null, retry = false) => {
128221
if (!files?.length) return
@@ -178,32 +271,40 @@ const getSvgIcon = (name: string, style?: CSSProperties) => {
178271
const aiAvatar = getSvgIcon('AI')
179272
const welcomeIcon = getSvgIcon('AI', { fontSize: '44px' })
180273
181-
const contentRenderers = computed(() => ({
182-
markdown: MarkdownRenderer,
183-
loading: LoadingRenderer,
184-
img: ImgRenderer,
185-
...props.bubbleRenderers
186-
}))
274+
const resolveMessageContent = (message: any) => {
275+
if (Array.isArray(message.renderContent) && message.renderContent.length > 0) {
276+
return resolveAgentRenderContent(message)
277+
}
187278
188-
const roles: Record<string, BubbleRoleConfig> = {
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+
]
290+
}
291+
292+
return message.content
293+
}
294+
295+
const roleConfigs: Record<string, BubbleRoleConfig> = {
189296
assistant: {
190297
placement: 'start',
191-
avatar: aiAvatar,
192-
contentRenderer: MarkdownRenderer,
193-
customContentField: 'renderContent'
298+
avatar: aiAvatar
194299
},
195300
user: {
196-
placement: 'end',
197-
contentRenderer: MarkdownRenderer,
198-
customContentField: 'renderContent'
301+
placement: 'end'
199302
},
200303
system: {
201304
hidden: true
202305
}
203306
}
204307
205-
const senderRef = ref<InstanceType<typeof TrSender> | null>(null)
206-
207308
// 发送消息
208309
const handleSendMessage = async (content: string) => {
209310
const messageContent = content || inputMessage.value
@@ -256,6 +357,7 @@ const handleSendMessage = async (content: string) => {
256357
}
257358
messages.value.push(userMessage)
258359
inputMessage.value = ''
360+
senderRef.value?.clear()
259361
selectedAttachments.value = []
260362
emit('sendMessage')
261363
}
@@ -371,13 +473,13 @@ const handlePromptItemClick = (ev: unknown, item: { description?: string }) => {
371473
}
372474
}
373475
:deep([data-role='user']) {
374-
--tr-bubble-content-bg: var(--tr-color-primary-light);
476+
--tr-bubble-box-bg: var(--tr-color-primary-light);
375477
}
376478
}
377479
378480
&.fullscreen {
379481
:deep([data-role='assistant']) {
380-
--tr-bubble-content-bg: transparent;
482+
--tr-bubble-box-bg: transparent;
381483
.tr-bubble__content {
382484
padding: 8px 0 0;
383485
}
@@ -491,4 +593,10 @@ const handlePromptItemClick = (ev: unknown, item: { description?: string }) => {
491593
.robot-bubble-list {
492594
height: 100%;
493595
}
596+
597+
.aborted {
598+
margin-top: 6px;
599+
font-size: 12px;
600+
opacity: 0.7;
601+
}
494602
</style>

packages/plugins/robot/src/components/header-extension/History.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
<script setup lang="ts">
3030
import { IconHistory, IconClose } from '@opentiny/tiny-robot-svgs'
31-
import type { Conversation } from '@opentiny/tiny-robot-kit'
31+
import type { ConversationInfo } from '@opentiny/tiny-robot-kit'
3232
import { TrHistory, TrIconButton, type HistoryItem, type HistoryMenuItem } from '@opentiny/tiny-robot'
3333
import { computed, ref } from 'vue'
3434
@@ -37,7 +37,7 @@ const showHistory = ref(false)
3737
interface HistoryProps {
3838
conversationState: {
3939
currentId?: string | null
40-
conversations: Conversation[]
40+
conversations: ConversationInfo[]
4141
}
4242
onItemClick?: (item: HistoryItem) => void
4343
onItemAction?: (action: HistoryMenuItem, item: HistoryItem) => void
@@ -47,7 +47,7 @@ interface HistoryProps {
4747
const props = defineProps<HistoryProps>()
4848
4949
// 将平铺格式的历史会话数据转换为分组格式(基于createdAt时间戳)
50-
const convertFlatToGrouped = (flatData: Conversation[]): Array<{ group: string; items: Conversation[] }> => {
50+
const convertFlatToGrouped = (flatData: ConversationInfo[]): Array<{ group: string; items: ConversationInfo[] }> => {
5151
const now = new Date()
5252
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
5353
const getDate = (days: number) => {
@@ -64,7 +64,7 @@ const convertFlatToGrouped = (flatData: Conversation[]): Array<{ group: string;
6464
{ group: '更早', threshold: new Date(0) }
6565
]
6666
67-
const groups = groupConfigs.map((config) => ({ ...config, items: [] as Conversation[] }))
67+
const groups = groupConfigs.map((config) => ({ ...config, items: [] as ConversationInfo[] }))
6868
6969
flatData.forEach((item) => {
7070
const itemDate = new Date(item.createdAt)

0 commit comments

Comments
 (0)