Skip to content

Commit d4b7900

Browse files
committed
feat: add stream
1 parent 1550861 commit d4b7900

File tree

3 files changed

+143
-58
lines changed

3 files changed

+143
-58
lines changed

packages/plugins/robot/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
"homepage": "https://opentiny.design/tiny-engine",
2727
"dependencies": {
2828
"@opentiny/tiny-engine-meta-register": "workspace:*",
29-
"@opentiny/tiny-robot": "^0.3.0-alpha.0",
30-
"@opentiny/tiny-robot-kit": "^0.2.0-alpha.3",
31-
"@opentiny/tiny-robot-svgs": "^0.2.0-alpha.3",
29+
"@opentiny/tiny-robot": "0.3.0-alpha.0",
30+
"@opentiny/tiny-robot-kit": "0.3.0-alpha.0",
31+
"@opentiny/tiny-robot-svgs": "0.3.0-alpha.0",
3232
"@opentiny/tiny-schema-renderer": "1.0.0-beta.5",
3333
"fast-json-patch": "~3.1.1"
3434
},

packages/plugins/robot/src/Main.vue

Lines changed: 112 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,9 @@
8282

8383
<script lang="ts">
8484
import { ref, onMounted, watchEffect, type CSSProperties, h, resolveComponent } from 'vue'
85-
import { Notify, Loading, TinyPopover, TinyDialogBox, TinyButton } from '@opentiny/vue'
85+
import { Notify, Loading, TinyPopover, TinyDialogBox } from '@opentiny/vue'
8686
import { useCanvas, usePage, useModal, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
87-
import {
88-
TrContainer,
89-
TrWelcome,
90-
TrPrompts,
91-
TrBubbleList,
92-
TrSender,
93-
TrFeedback,
94-
TrAttachments
95-
} from '@opentiny/tiny-robot'
87+
import { TrContainer, TrWelcome, TrBubbleList, TrSender, TrFeedback, TrAttachments } from '@opentiny/tiny-robot'
9688
import { IconNewSession } from '@opentiny/tiny-robot-svgs'
9789
import SchemaRenderer from '@opentiny/tiny-schema-renderer'
9890
import RobotSettingPopover from './RobotSettingPopover.vue'
@@ -105,19 +97,17 @@ import {
10597
} from './js/robotSetting'
10698
import { PROMPTS } from './js/prompts'
10799
import * as jsonpatch from 'fast-json-patch'
100+
import { chatStream } from './js/utils'
108101
109102
export default {
110103
components: {
111-
TinyPopover,
112-
TinyDialogBox,
113-
TinyButton,
104+
TinyPopover: TinyPopover as unknown,
105+
TinyDialogBox: TinyDialogBox as unknown,
114106
RobotSettingPopover,
115107
TrContainer,
116108
TrWelcome,
117-
TrPrompts,
118109
TrBubbleList,
119110
TrSender,
120-
TrFeedback,
121111
TrAttachments,
122112
IconNewSession,
123113
SchemaRenderer
@@ -215,7 +205,7 @@ export default {
215205
showPreview.value = false
216206
}
217207
218-
const fixedJson = (op) => {
208+
const _fixedJson = (op) => {
219209
// 修正 path:替换 ; 和 : 为 /
220210
if (op.path) {
221211
op.path = op.path.replace(/[;:]/g, '/').replace(/\/+/g, '/')
@@ -235,55 +225,109 @@ export default {
235225
op.value = { [key.trim()]: val.trim() }
236226
}
237227
} catch (e) {
228+
// eslint-disable-next-line no-console
238229
console.warn('Failed to parse style:', op.value)
239230
}
240231
}
241232
242233
return op
243234
}
244235
245-
const sendRequest = () => {
246-
getMetaApi(META_SERVICE.Http)
247-
.post('/app-center/api/ai/chat', getSendSeesionProcess(), { timeout: 600000 })
248-
.then((res: any) => {
249-
const { choices } = res
250-
const chatMessage = choices[0]?.message
251-
try {
252-
const regex = /```json([\s\S]*?)```/
253-
const match = chatMessage?.content.match(regex)
254-
255-
if (match && match[1] && JSON.parse(match[1]) && isValidFastJsonPatch(JSON.parse(match[1]))) {
256-
const newValue = JSON.parse(match[1])
257-
// 使用 applyPatch 修改 Schema
258-
const result = newValue.reduce(jsonpatch.applyReducer, pageState.pageSchema)
259-
260-
sessionProcess.messages.push(
261-
getAiRespMessage(JSON.stringify(pageState.pageSchema, null, 2), chatMessage.role)
262-
)
263-
sessionProcess.displayMessages.push(getAiDisplayMessage(MESSAGE_TIP, chatMessage.role, result, res.id))
264-
messages.value[messages.value.length - 1].content = MESSAGE_TIP
265-
messages.value[messages.value.length - 1].schema = result
266-
messages.value[messages.value.length - 1].id = res.id
267-
} else {
268-
sessionProcess.messages.push(getAiRespMessage(chatMessage?.content))
269-
sessionProcess.displayMessages.push(getAiRespMessage(chatMessage?.content))
270-
messages.value[messages.value.length - 1].content = chatMessage?.content
236+
// 处理响应
237+
const handleResponse = (res: { id: string; chatMessage: any }) => {
238+
try {
239+
const regex = /```json([\s\S]*?)```/
240+
const match = chatMessage?.content.match(regex)
241+
242+
if (match && match[1] && JSON.parse(match[1]) && isValidFastJsonPatch(JSON.parse(match[1]))) {
243+
const newValue = JSON.parse(match[1])
244+
// 使用 applyPatch 修改 Schema
245+
const result = newValue.reduce(jsonpatch.applyReducer, pageState.pageSchema)
246+
247+
sessionProcess.messages.push(
248+
getAiRespMessage(JSON.stringify(pageState.pageSchema, null, 2), chatMessage.role)
249+
)
250+
sessionProcess.displayMessages.push(getAiDisplayMessage(MESSAGE_TIP, chatMessage.role, result, res.id))
251+
messages.value[messages.value.length - 1].content = MESSAGE_TIP
252+
messages.value[messages.value.length - 1].schema = result
253+
messages.value[messages.value.length - 1].id = res.id
254+
} else {
255+
sessionProcess.messages.push(getAiRespMessage(chatMessage?.content))
256+
sessionProcess.displayMessages.push(getAiRespMessage(chatMessage?.content))
257+
messages.value[messages.value.length - 1].content = chatMessage?.content
258+
}
259+
setContextSession()
260+
inProcesing.value = false
261+
connectedFailed.value = false
262+
} catch (e) {
263+
messages.value[messages.value.length - 1].content = '处理响应时出错'
264+
inProcesing.value = false
265+
connectedFailed.value = false
266+
}
267+
}
268+
269+
// 发送流式请求
270+
const _sendStreamRequest = async () => {
271+
const requestData = getSendSeesionProcess()
272+
if (requestData.foundationModel) {
273+
requestData.foundationModel.stream = true
274+
}
275+
276+
let streamContent = ''
277+
const chatId = Date.now().toString()
278+
await chatStream(
279+
{
280+
requestUrl: '/app-center/api/ai/chat',
281+
requestData
282+
},
283+
{
284+
onData: (data) => {
285+
const choice = data.choices?.[0]
286+
if (choice && choice.delta.content) {
287+
if (messages.value.length === 0 || messages.value[messages.value.length - 1].role !== 'assistant') {
288+
messages.value.push(getAiDisplayMessage('', 'assistant', {}, chatId))
289+
}
290+
if (streamContent !== messages.value[messages.value.length - 1].content) {
291+
messages.value[messages.value.length - 1].content = ''
292+
}
293+
streamContent += choice.delta.content
294+
messages.value[messages.value.length - 1].content += choice.delta.content
271295
}
272-
setContextSession()
273-
inProcesing.value = false
274-
connectedFailed.value = false
275-
} catch (e) {
276-
messages.value[messages.value.length - 1].content = '处理响应时出错'
296+
},
297+
onError: (error) => {
298+
messages.value[messages.value.length - 1].content = '连接失败'
299+
localStorage.removeItem('aiChat')
277300
inProcesing.value = false
278301
connectedFailed.value = false
302+
// eslint-disable-next-line no-console
303+
console.error('Stream error:', error)
304+
},
305+
onDone: () => {
306+
handleResponse({
307+
id: chatId,
308+
chatMessage: {
309+
role: 'assistant',
310+
content: streamContent || '没有返回内容',
311+
name: 'AI'
312+
}
313+
})
279314
}
315+
}
316+
)
317+
}
318+
319+
const sendRequest = async () => {
320+
try {
321+
const res: any = await getMetaApi(META_SERVICE.Http).post('/app-center/api/ai/chat', getSendSeesionProcess(), {
322+
timeout: 600000
280323
})
281-
.catch(() => {
282-
messages.value[messages.value.length - 1].content = '连接失败'
283-
localStorage.removeItem('aiChat')
284-
inProcesing.value = false
285-
connectedFailed.value = false
286-
})
324+
handleResponse({ id: res.id, chatMessage: res.choices[0].message })
325+
} catch (error) {
326+
messages.value[messages.value.length - 1].content = '连接失败'
327+
localStorage.removeItem('aiChat')
328+
inProcesing.value = false
329+
connectedFailed.value = false
330+
}
287331
}
288332
289333
const scrollContent = async () => {
@@ -387,7 +431,7 @@ export default {
387431
await sleep(1000)
388432
messages.value.push(getAiDisplayMessage('好的,正在执行相关操作,请稍等片刻...'))
389433
await scrollContent()
390-
sendRequest()
434+
await sendRequest()
391435
}
392436
}
393437
@@ -505,6 +549,10 @@ export default {
505549
placement: 'start',
506550
avatar: aiAvatar,
507551
maxWidth: '80%',
552+
type: 'markdown',
553+
mdConfig: {
554+
breaks: true
555+
},
508556
slots: {
509557
footer: ({ bubbleProps }) => {
510558
return h(TrFeedback, {
@@ -528,7 +576,15 @@ export default {
528576
}
529577
}
530578
},
531-
user: { placement: 'end', avatar: userAvatar, maxWidth: '80%' }
579+
user: {
580+
placement: 'end',
581+
avatar: userAvatar,
582+
maxWidth: '80%',
583+
type: 'markdown',
584+
mdConfig: {
585+
breaks: true
586+
}
587+
}
532588
})
533589
534590
// 处理文件选择事件
@@ -588,6 +644,7 @@ export default {
588644
}
589645
})
590646
} catch (error) {
647+
// eslint-disable-next-line no-console
591648
console.error('上传失败', error)
592649
}
593650
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { handleSSEStream, type StreamHandler } from '@opentiny/tiny-robot-kit'
2+
3+
export const chatStream = async (requestOpts: any, handler: StreamHandler, headers = {}) => {
4+
try {
5+
const { requestData, requestUrl } = requestOpts
6+
7+
const requestOptions = {
8+
method: 'POST',
9+
headers: {
10+
'Content-Type': 'application/json',
11+
Accept: 'text/event-stream',
12+
...headers
13+
},
14+
body: JSON.stringify(requestData)
15+
}
16+
const response = await fetch(requestUrl, requestOptions)
17+
18+
if (!response.ok) {
19+
const errorText = await response.text()
20+
throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`)
21+
}
22+
23+
await handleSSEStream(response, handler)
24+
} catch (error: unknown) {
25+
const logger = console
26+
logger.error('Error in chatStream:', error)
27+
}
28+
}

0 commit comments

Comments
 (0)