feat:Adjust the logic and interaction of the AI building page (#1658)
This commit is contained in:
parent
f4b922f719
commit
05205afc20
|
|
@ -60,6 +60,13 @@ export default defineService({
|
||||||
post: (...args) => http?.post(...args),
|
post: (...args) => http?.post(...args),
|
||||||
request: (...args) => http?.request(...args),
|
request: (...args) => http?.request(...args),
|
||||||
put: (...args) => http?.put(...args),
|
put: (...args) => http?.put(...args),
|
||||||
delete: (...args) => http?.delete(...args)
|
delete: (...args) => http?.delete(...args),
|
||||||
|
stream: (config) => {
|
||||||
|
const streamConfig = {
|
||||||
|
responseType: 'stream',
|
||||||
|
...config
|
||||||
|
}
|
||||||
|
return http?.request(streamConfig)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<div class="build-loading-renderer">
|
||||||
|
<img src="../assets/loading.webp" alt="loading" />
|
||||||
|
<div class="build-loading-renderer-content">
|
||||||
|
<div class="build-loading-renderer-content-header">页面生成中,请稍等片刻</div>
|
||||||
|
<div class="build-loading-renderer-content-body">{{ renderContent }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const renderContent = computed(() => {
|
||||||
|
return props.content.slice(-30)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
renderContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.build-loading-renderer {
|
||||||
|
display: flex;
|
||||||
|
img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
&-content {
|
||||||
|
margin-left: 16px;
|
||||||
|
&-header {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
&-body {
|
||||||
|
color: var(--te-chat-model-helper-text);
|
||||||
|
font-size: 12px;
|
||||||
|
width: 160px;
|
||||||
|
height: 30px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -55,7 +55,10 @@
|
||||||
@item-click="handlePromptItemClick"
|
@item-click="handlePromptItemClick"
|
||||||
></tr-prompts>
|
></tr-prompts>
|
||||||
</div>
|
</div>
|
||||||
<tr-bubble-provider :content-renderers="contentRenderers" v-else>
|
<tr-bubble-provider
|
||||||
|
:content-renderers="aiType === AI_MODES['Chat'] ? contentRenderers : buildContentRenderers"
|
||||||
|
v-else
|
||||||
|
>
|
||||||
<tr-bubble-list :items="activeMessages" :roles="roles" autoScroll></tr-bubble-list>
|
<tr-bubble-list :items="activeMessages" :roles="roles" autoScroll></tr-bubble-list>
|
||||||
</tr-bubble-provider>
|
</tr-bubble-provider>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -144,11 +147,12 @@ import { utils } from '@opentiny/tiny-engine-utils'
|
||||||
import RobotSettingPopover from './RobotSettingPopover.vue'
|
import RobotSettingPopover from './RobotSettingPopover.vue'
|
||||||
import { PROMPTS } from './js/prompts'
|
import { PROMPTS } from './js/prompts'
|
||||||
import * as jsonpatch from 'fast-json-patch'
|
import * as jsonpatch from 'fast-json-patch'
|
||||||
import { chatStream, checkComponentNameExists } from './js/utils'
|
import { checkComponentNameExists, processSSEStream } from './js/utils'
|
||||||
import McpServer from './mcp/McpServer.vue'
|
import McpServer from './mcp/McpServer.vue'
|
||||||
import useMcpServer from './mcp/useMcp'
|
import useMcpServer from './mcp/useMcp'
|
||||||
import MarkdownRenderer from './mcp/MarkdownRenderer.vue'
|
import MarkdownRenderer from './mcp/MarkdownRenderer.vue'
|
||||||
import LoadingRenderer from './mcp/LoadingRenderer.vue'
|
import LoadingRenderer from './mcp/LoadingRenderer.vue'
|
||||||
|
import BuildLoadingRenderer from './BuildLoadingRenderer.vue'
|
||||||
import { sendMcpRequest, serializeError } from './mcp/utils'
|
import { sendMcpRequest, serializeError } from './mcp/utils'
|
||||||
import type { RobotMessage } from './mcp/types'
|
import type { RobotMessage } from './mcp/types'
|
||||||
import RobotTypeSelect from './RobotTypeSelect.vue'
|
import RobotTypeSelect from './RobotTypeSelect.vue'
|
||||||
|
|
@ -395,16 +399,27 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
let streamContent = ''
|
let streamContent = ''
|
||||||
|
let lastResponseLength = 0
|
||||||
const chatId = Date.now().toString()
|
const chatId = Date.now().toString()
|
||||||
const currentJson = deepClone(pageState.pageSchema)
|
const currentJson = deepClone(pageState.pageSchema)
|
||||||
let lastExecutionTime = 0
|
let lastExecutionTime = 0
|
||||||
const throttleDelay = 3000
|
const throttleDelay = 3000
|
||||||
await chatStream(
|
requestLoading.value = true
|
||||||
{
|
getMetaApi(META_SERVICE.Http)
|
||||||
requestUrl: '/app-center/api/ai/chat',
|
.stream({
|
||||||
requestData: params
|
method: 'POST',
|
||||||
|
url: '/app-center/api/ai/chat',
|
||||||
|
data: params,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'text/event-stream',
|
||||||
|
Authorization: `Bearer ${robotSettingState.selectedModel.apiKey || import.meta.env.VITE_API_TOKEN}`
|
||||||
},
|
},
|
||||||
{
|
onDownloadProgress: (progressEvent) => {
|
||||||
|
const currentResponse = progressEvent.currentTarget.responseText
|
||||||
|
const newData = currentResponse.substring(lastResponseLength)
|
||||||
|
lastResponseLength = currentResponse.length
|
||||||
|
processSSEStream(newData, {
|
||||||
onData: (data) => {
|
onData: (data) => {
|
||||||
const choice = data.choices?.[0]
|
const choice = data.choices?.[0]
|
||||||
if (choice && choice.delta.content) {
|
if (choice && choice.delta.content) {
|
||||||
|
|
@ -415,7 +430,7 @@ export default {
|
||||||
messages.value[messages.value.length - 1].content = ''
|
messages.value[messages.value.length - 1].content = ''
|
||||||
}
|
}
|
||||||
streamContent += choice.delta.content
|
streamContent += choice.delta.content
|
||||||
messages.value[messages.value.length - 1].content += choice.delta.content
|
messages.value.at(-1)!.renderContent = [{ type: 'loading', content: streamContent }]
|
||||||
const currentTime = Date.now()
|
const currentTime = Date.now()
|
||||||
if (currentTime - lastExecutionTime > throttleDelay) {
|
if (currentTime - lastExecutionTime > throttleDelay) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -436,15 +451,9 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
|
||||||
messages.value[messages.value.length - 1].content = '连接失败'
|
|
||||||
localStorage.removeItem('aiChat')
|
|
||||||
inProcesing.value = false
|
|
||||||
connectedFailed.value = false
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('Stream error:', error)
|
|
||||||
},
|
|
||||||
onDone: () => {
|
onDone: () => {
|
||||||
|
requestLoading.value = false
|
||||||
|
delete messages.value.at(-1)!.renderContent
|
||||||
handleResponse(
|
handleResponse(
|
||||||
{
|
{
|
||||||
id: chatId,
|
id: chatId,
|
||||||
|
|
@ -457,11 +466,18 @@ export default {
|
||||||
currentJson
|
currentJson
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
{
|
|
||||||
Authorization: `Bearer ${robotSettingState.selectedModel.apiKey || import.meta.env.VITE_API_TOKEN}`
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
messages.value[messages.value.length - 1].content = '连接失败'
|
||||||
|
localStorage.removeItem('aiChat')
|
||||||
|
requestLoading.value = false
|
||||||
|
inProcesing.value = false
|
||||||
|
connectedFailed.value = false
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Stream error:', error)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -785,6 +801,10 @@ export default {
|
||||||
loading: LoadingRenderer
|
loading: LoadingRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildContentRenderers: Record<string, Component> = {
|
||||||
|
loading: BuildLoadingRenderer
|
||||||
|
}
|
||||||
|
|
||||||
const mcpDrawerPosition = computed(() => {
|
const mcpDrawerPosition = computed(() => {
|
||||||
return {
|
return {
|
||||||
type: 'fixed',
|
type: 'fixed',
|
||||||
|
|
@ -841,7 +861,8 @@ export default {
|
||||||
typeChange,
|
typeChange,
|
||||||
isVisualModel,
|
isVisualModel,
|
||||||
contentRenderers,
|
contentRenderers,
|
||||||
mcpDrawerPosition
|
mcpDrawerPosition,
|
||||||
|
buildContentRenderers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,3 +42,25 @@ export const checkComponentNameExists = (data: any) => {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const processSSEStream = (data,handler) => {
|
||||||
|
const lines = data.split('\n')
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('data: ')) {
|
||||||
|
const dataStr = line.substring(6).trim()
|
||||||
|
|
||||||
|
// 检查结束标记
|
||||||
|
if (dataStr === '[DONE]') {
|
||||||
|
handler.onDone()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataStr) {
|
||||||
|
const data = JSON.parse(dataStr)
|
||||||
|
handler.onData(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue