Skip to content

Commit f864f85

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

File tree

3 files changed

+146
-56
lines changed

3 files changed

+146
-56
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: 115 additions & 53 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,114 @@ 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()
296+
},
297+
onError: (error) => {
298+
messages.value[messages.value.length - 1].content = '连接失败'
299+
localStorage.removeItem('aiChat')
273300
inProcesing.value = false
274301
connectedFailed.value = false
275-
} catch (e) {
276-
messages.value[messages.value.length - 1].content = '处理响应时出错'
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+
// })
314+
sessionProcess.messages.push(getAiRespMessage(streamContent))
315+
sessionProcess.displayMessages.push(getAiRespMessage(streamContent))
316+
setContextSession()
277317
inProcesing.value = false
278318
connectedFailed.value = false
279319
}
320+
}
321+
)
322+
}
323+
324+
const sendRequest = async () => {
325+
try {
326+
const res: any = await getMetaApi(META_SERVICE.Http).post('/app-center/api/ai/chat', getSendSeesionProcess(), {
327+
timeout: 600000
280328
})
281-
.catch(() => {
282-
messages.value[messages.value.length - 1].content = '连接失败'
283-
localStorage.removeItem('aiChat')
284-
inProcesing.value = false
285-
connectedFailed.value = false
286-
})
329+
handleResponse({ id: res.id, chatMessage: res.choices[0].message })
330+
} catch (error) {
331+
messages.value[messages.value.length - 1].content = '连接失败'
332+
localStorage.removeItem('aiChat')
333+
inProcesing.value = false
334+
connectedFailed.value = false
335+
}
287336
}
288337
289338
const scrollContent = async () => {
@@ -387,7 +436,7 @@ export default {
387436
await sleep(1000)
388437
messages.value.push(getAiDisplayMessage('好的,正在执行相关操作,请稍等片刻...'))
389438
await scrollContent()
390-
sendRequest()
439+
await sendRequest()
391440
}
392441
}
393442
@@ -505,6 +554,10 @@ export default {
505554
placement: 'start',
506555
avatar: aiAvatar,
507556
maxWidth: '80%',
557+
type: 'markdown',
558+
mdConfig: {
559+
breaks: true
560+
},
508561
slots: {
509562
footer: ({ bubbleProps }) => {
510563
return h(TrFeedback, {
@@ -528,7 +581,15 @@ export default {
528581
}
529582
}
530583
},
531-
user: { placement: 'end', avatar: userAvatar, maxWidth: '80%' }
584+
user: {
585+
placement: 'end',
586+
avatar: userAvatar,
587+
maxWidth: '80%',
588+
type: 'markdown',
589+
mdConfig: {
590+
breaks: true
591+
}
592+
}
532593
})
533594
534595
// 处理文件选择事件
@@ -588,6 +649,7 @@ export default {
588649
}
589650
})
590651
} catch (error) {
652+
// eslint-disable-next-line no-console
591653
console.error('上传失败', error)
592654
}
593655
}
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)