增加深度思考的显示,修复一些bug

master
落雨楓 3 weeks ago
parent 963373647f
commit e01a7fdde0

@ -85,6 +85,12 @@ export type GetBotPersonaInfoParams = {
export type GetBotPersonaInfoResponse = BotPersonaInfo
export type ChatCompleteStreamOutputEvents = {
onToolRun?: (toolName: string) => any,
onToolOutput?: (output: string) => any,
onMessage?: (deltaMessage: string) => any
}
export const chatCompleteApi = {
indexPage(params: IndexPageParams, onProgress: IndexPageOnProgress): Promise<IndexPageResponse> {
return new Promise((resolve, reject) => {
@ -184,7 +190,7 @@ export const chatCompleteApi = {
return data.data
},
chatCompleteStreamOutput(taskId: string, onMessage: (deltaMessage: string) => any): Promise<ChatCompleteServiceResponse> {
chatCompleteStreamOutput(taskId: string, eventListeners: ChatCompleteStreamOutputEvents = {}): Promise<ChatCompleteServiceResponse> {
return new Promise((resolve, reject) => {
let url = getWebSocketApiUrl('/chatcomplete/message/stream', {
task_id: taskId
@ -194,11 +200,15 @@ export const chatCompleteApi = {
let ws = new WebSocket(url)
ws.addEventListener('message', (event) => {
if (event.data.startsWith('+')) { // 实时输出的消息是以+开头的纯文本
onMessage(event.data.substring(1))
eventListeners.onMessage?.(event.data.substring(1))
} else if (event.data.startsWith('>')) {
eventListeners.onToolOutput?.(event.data.substring(1))
} else { // 其他消息是JSON格式
let data = JSON.parse(event.data)
if (data.event === 'connected') {
onMessage(data.outputed_message)
eventListeners.onMessage?.(data.outputed_message)
} else if (data.event === 'tool_run') {
eventListeners.onToolRun?.(data.tool_name)
} else if (data.event === 'finished') {
isFinished = true
resolve(data.result)

@ -12,6 +12,8 @@ declare module '@vue/runtime-core' {
ChatCompleteSettingsModal: typeof import('./components/ChatCompleteSettingsModal.vue')['default']
ChatMessage: typeof import('./components/ChatMessage.vue')['default']
ConversationList: typeof import('./components/ConversationList.vue')['default']
IonAccordion: typeof import('@ionic/vue')['IonAccordion']
IonAccordionGroup: typeof import('@ionic/vue')['IonAccordionGroup']
IonApp: typeof import('@ionic/vue')['IonApp']
IonAvatar: typeof import('@ionic/vue')['IonAvatar']
IonButton: typeof import('@ionic/vue')['IonButton']

@ -1,4 +1,5 @@
<script lang="ts" setup>
import { LLMToolOutputData } from '@/types/chatComplete';
import { createOutline, reloadOutline, gitNetworkOutline } from 'ionicons/icons'
defineOptions({
@ -15,7 +16,10 @@ const props = defineProps<{
assistant?: boolean,
cursor?: boolean,
edit?: boolean,
change?: boolean
change?: boolean,
toolRunning?: boolean,
toolOutput?: any[],
}>()
const emit = defineEmits<{
@ -38,6 +42,19 @@ const displayTime = computed(() => {
return ''
}
})
const toolOutput: Ref<LLMToolOutputData[] | undefined> = toRef(props, 'toolOutput')
const toolRunningTitle = computed(() => {
if (props.toolRunning) {
if (toolOutput.value && toolOutput.value.length > 0) {
return '正在进行:' + toolOutput.value[0].name;
}
return '正在运行工具...';
} else {
return '工具运行结果';
}
})
</script>
<template>
@ -70,6 +87,21 @@ const displayTime = computed(() => {
</div>
<div class="message-content">
<div class="message-body">
<div class="preprocess-tool-info-container" v-if="toolOutput && toolOutput.length > 0">
<ion-accordion-group expand="inset">
<ion-accordion value="1">
<ion-item slot="header" color="light">
<loading-title :loading="props.toolRunning">{{ toolRunningTitle }}</loading-title>
</ion-item>
<div class="ion-padding" slot="content">
<div v-for="item in toolOutput" :key="item.name">
<div class="text-selectable tool-output-title">{{ item.name }}</div>
<markdown-parser :md="item.output"></markdown-parser>
</div>
</div>
</ion-accordion>
</ion-accordion-group>
</div>
<markdown-parser v-if="props.content !== undefined" :md="props.content" :cursor="props.cursor"></markdown-parser>
<div class="message-error text-selectable" v-else-if="props.error">
{{ props.error }}
@ -95,7 +127,7 @@ const displayTime = computed(() => {
.message {
display: grid;
grid-template-areas: "avatar-col message-header avatar-balance" "avatar-col message-content avatar-balance";
grid-template-columns: 46px 1fr 46px;
grid-template-columns: 46px minmax(0, 1fr) 46px;
grid-template-rows: auto auto;
column-gap: 16px;
row-gap: 8px;
@ -104,7 +136,7 @@ const displayTime = computed(() => {
@media (max-width: 767px) {
grid-template-areas: "avatar-col message-header" "message-content message-content";
grid-template-columns: auto 1fr;
grid-template-columns: auto minmax(0, 1fr);
grid-template-rows: auto auto;
}
@ -170,6 +202,36 @@ const displayTime = computed(() => {
width: 100%;
}
.preprocess-tool-info-container {
margin-bottom: 1rem;
ion-accordion-group {
-webkit-margin-start: 0;
margin-inline-start: 0;
-webkit-margin-end: 0;
margin-inline-end: 0;
margin-top: 0;
margin-bottom: 0;
ion-accordion {
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1),
0 2px 4px -1px rgba(0,0,0,0.06),
0 -1px 4px -1px rgba(0,0,0,0.1),
0 0 0 1px rgba(53,72,91,0.07000000000000001);
}
}
.tool-output-title {
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.loading-title-container {
justify-content: start;
}
}
.message-error {
position: relative;
padding: .75rem 1.25rem;

@ -1,15 +1,13 @@
<script lang="ts">
export default {
name: 'MarkdownParser'
}
</script>
<script lang="ts" setup>
import MarkdownIt from 'markdown-it'
import hljs from 'highlight.js'
import 'github-markdown-css/github-markdown.css'
import { move } from 'ionicons/icons';
defineOptions({
name: 'MarkdownParser'
})
const props = defineProps<{
md: string,
cursor?: boolean

@ -1,3 +1,5 @@
import { LLMToolOutputData } from "./chatComplete"
export type ConversationInfo = {
id: number,
module: string,
@ -31,7 +33,9 @@ export type ChatCompleteMessage = {
streaming?: boolean,
}
export type ChatCompleteChunkInfo = ChatCompleteMessage[]
export type ChatCompleteChunkInfo = (ChatCompleteMessage & {
tool_output?: LLMToolOutputData[],
})[]
export type BotPersonaInfo = {
id: number,

@ -0,0 +1,4 @@
export type LLMToolOutputData = {
name: string,
output: string,
};

@ -48,6 +48,8 @@ const state = reactive<ChatCompletePageState>({
lastUserMsgId: [],
lastAssistantMsgId: [],
streamingToolRunnning: false,
streamingToolOutput: [],
streamingMessage: null,
editingId: null,
@ -101,7 +103,20 @@ onMounted(async () => {
setPageTitle(state.conversationTitle, '写作助手')
nextTick(() => {
if (contentRef.value) {
let contentEl = contentRef.value.$el;
(<any>window)._contentEl = contentEl
contentEl.scrollEl.addEventListener('scroll', controller.onMainContentScroll, { passive: true })
mainContentResizeObserver = new ResizeObserver(() => {
if (!controller.ignoreAutoScroll) {
contentEl.scrollEl.scrollTo({
top: contentEl.scrollEl.scrollHeight,
})
}
})
mainContentResizeObserver.observe(contentEl.scrollEl)
}
})
})
@ -177,16 +192,16 @@ watch(() => conversationInfo.value, (currentConversation) => {
<chat-message v-else-if="message.role == 'assistant'" :sender="state.botName" :avatar="state.botAvatar"
:content="message.content" :time="message.time" assistant :cursor="message.streaming" :msg-id="message.id"
:change="chunk.id == state.lastAssistantMsgId[0] && message.id == state.lastAssistantMsgId[1]"
:tool-output="message.tool_output"
@click-create-branch="controller.onCreateBranch(chunk.id, message.id)"
@click-change="controller.onChangeResponse(chunk.id, message.id)"></chat-message>
<chat-message v-else-if="message.role == 'placeholder'" :sender="state.botName" :avatar="state.botAvatar"
:content="state.streamingMessage ?? undefined"
:error="state.chatCompleteError ?? undefined" assistant cursor :change="state.chatCompleteError !== null"
:tool-running="state.streamingToolRunnning" :tool-output="state.streamingToolOutput ?? undefined"
@click-change="controller.onChangeLastResponse()"></chat-message>
</template>
</div>
<!-- 流式输出消息 -->
<div v-if="state.streamingMessage !== null || state.chatCompleteError !== null" class="message-chunk">
<chat-message :sender="state.botName" :avatar="state.botAvatar" :content="state.streamingMessage ?? undefined"
:error="state.chatCompleteError ?? undefined" assistant cursor :change="state.chatCompleteError !== null"
@click-change="controller.onChangeLastResponse()"></chat-message>
</div>
</transition-group>
</ion-content>
@ -203,7 +218,7 @@ watch(() => conversationInfo.value, (currentConversation) => {
<ion-label color="medium" v-show="!state.pointUsageLoading">{{ state.pointUsage }}</ion-label>
<ion-spinner v-show="state.pointUsageLoading" color="medium" name="dots"></ion-spinner>
</ion-chip>
<ion-textarea class="message-input" aria-label="" :rows="1" :maxlength="768" placeholder="输入问题" auto-grow
<ion-textarea class="message-input" aria-label="" :rows="1" :maxlength="2000" placeholder="输入问题" auto-grow
v-model="state.formMessage" @keydown="controller.onMessageInputKeyDown" @ion-input="controller.onInput"
@compositionstart="controller.onCompositionStart" @compositionend="controller.onCompositionEnd"
:disabled="state.conversationLoading"></ion-textarea>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save