Commit fa279451 authored by nick zheng's avatar nick zheng

Merge branch 'beta' into 'master'

fix: 多模型调试异常自定义提示及提问携带模型

See merge request !35
parents e1e55d9f 889e83f5
import CMessage from './src/message'
import './src/message.scss'
export default CMessage
import { createApp } from 'vue'
import Message from './message.vue'
import { CMessageOptions } from './message'
const createInstance = (cfg: CMessageOptions) => {
const config = cfg || {}
let targetElementId = ''
let targetHTMLElement: HTMLElement | null = null
if (config.to && config.to[0] === '#') {
targetElementId = config.to.slice(1)
targetHTMLElement = document.getElementById(targetElementId)
}
// 创建包裹容器
const messageNode = document.createElement('div')
const attr = document.createAttribute('class')
if (targetHTMLElement) {
attr.value = 'message'
} else {
attr.value = 'message message-body'
}
messageNode.setAttributeNode(attr)
let messageList: HTMLCollectionOf<HTMLElement>
let messageContainerHeight = 0
const offsetTop = 12
if (targetElementId && targetHTMLElement) {
messageList = document
.getElementById(targetElementId)
?.getElementsByClassName('message') as HTMLCollectionOf<HTMLElement>
} else {
messageList = document.getElementsByClassName('message-body') as HTMLCollectionOf<HTMLElement>
}
for (let i = 0; i < messageList.length; i++) {
messageContainerHeight += messageList[i].offsetHeight
}
messageNode.style.top = `${messageContainerHeight + (messageList.length + 1) * offsetTop}px`
// 创建实例并挂载
const app: any = createApp(Message, {
config,
remove() {
handleRemove()
},
})
app.vm = app.mount(messageNode)
if (targetHTMLElement) {
targetHTMLElement.style.position = 'relative'
messageNode.style.position = 'absolute'
targetHTMLElement.appendChild(messageNode)
} else {
document.body.appendChild(messageNode)
}
app.close = () => {
handleRemove()
}
// 取消挂载
const handleRemove = () => {
app.unmount(messageNode)
if (targetHTMLElement) {
targetHTMLElement.removeChild(messageNode)
} else {
document.body.removeChild(messageNode)
}
resetMsgTop()
}
const resetMsgTop = () => {
for (let i = 0; i < messageList.length; i++) {
messageList[i].style.top = `${offsetTop + i * (messageList[i].offsetHeight + offsetTop)}px`
}
}
return app
}
export default createInstance
.message {
position: fixed;
top: 12px;
left: 50%;
z-index: 9999;
max-width: 720px;
padding: 10px 20px;
border-radius: 3px;
box-shadow:
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
transform: translateX(-50%);
}
.slide-fade-enter-active {
transition: all 0.2s ease-out;
}
.slide-fade-leave-active {
transition: all 0.2s ease;
}
.slide-fade-enter-from,
.slide-fade-leave-to {
opacity: 0;
transform: translateY(-20px);
}
import createInstance from './instance.ts'
enum CMessageTypeEnum {
text = 'text',
success = 'success',
error = 'error',
warning = 'warning',
info = 'info',
}
export type CMessageType = 'text' | 'success' | 'error' | 'warning' | 'info'
export interface CMessageOptions {
type: CMessageType // 类型
content: string // 消息内容
icon?: string // 消息图标
duration?: number // 自动关闭延迟时间
close?: boolean // 是否显示关闭按钮
to?: string | 'body' // 挂载位置 仅支持Id选择器 / body
}
function renderMsg(messageType: CMessageType, messageContent: string = '', options?: Partial<CMessageOptions>) {
return new Promise((resolve) => {
const defaultCfg: CMessageOptions = {
type: 'text',
content: '',
icon: '',
duration: 3000,
close: false,
to: 'body',
}
const config: CMessageOptions = Object.assign(
{ ...defaultCfg },
{ type: messageType, content: messageContent },
{ ...options },
)
const { type = 'text', content = '', icon = '', duration = 3000, close = false, to = 'body' } = config
createInstance({
type,
content,
duration,
icon,
close,
to,
})
resolve('')
})
}
export default {
text(content = '', options?: Partial<CMessageOptions>) {
return renderMsg(CMessageTypeEnum.text, content, options)
},
success(content = '', options?: Partial<CMessageOptions>) {
return renderMsg(CMessageTypeEnum.success, content, options)
},
error(content = '', options?: Partial<CMessageOptions>) {
return renderMsg(CMessageTypeEnum.error, content, options)
},
info(content = '', options?: Partial<CMessageOptions>) {
return renderMsg(CMessageTypeEnum.info, content, options)
},
warning(content = '', options?: Partial<CMessageOptions>) {
return renderMsg(CMessageTypeEnum.warning, content, options)
},
}
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { CMessageType, CMessageOptions } from './message'
interface MessageIconItem {
color: string
icon: string
}
interface Props {
config: CMessageOptions
remove: () => void
}
const visible = ref(false)
const props = defineProps<Props>()
const messageIconList: { [k in CMessageType]: MessageIconItem } = {
text: {
icon: '',
color: '#333333',
},
warning: {
icon: 'icon-warning',
color: '#F0A020',
},
info: {
icon: 'icon-info',
color: '#2080F0',
},
error: {
icon: 'icon-error',
color: '#D03050',
},
success: {
icon: 'icon-success',
color: '#18A058',
},
}
onMounted(() => {
handleOpen()
})
function handleOpen() {
visible.value = true
if (props.config.duration !== 0) {
setTimeout(() => {
handleClose()
}, props.config.duration)
}
}
function handleClose() {
visible.value = false
props.remove()
}
</script>
<template>
<transition name="slide-fade">
<div v-show="visible" class="message-container">
<div class="message-content flex items-center">
<div v-if="messageIconList[config.type].icon" class="mr-2.5 h-5 w-5">
<i
class="iconfont flex h-[22px] items-center justify-center text-[20px]"
:class="[messageIconList[config.type].icon]"
:style="{ color: messageIconList[config.type].color }"
/>
</div>
<span class="break-all" v-text="config.content" />
<div
v-if="config.close"
class="hover:bg-background-color rounded-theme ml-2.5 flex h-6 w-6 cursor-pointer items-center justify-center"
@click="handleClose"
>
<i class="iconfont icon-close text-font-color text-sm" />
</div>
</div>
</div>
</transition>
</template>
......@@ -3,8 +3,9 @@ import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { nanoid } from 'nanoid'
import { throttle } from 'lodash-es'
import CMessage from './c-message'
import { MessageItemInterface, MultiModelDialogueItem, QuestionMessageItem } from '../types'
import { fetchCustomEventSource } from '@/composables/useEventSource'
import { fetchEventStreamSource } from '../utils/fetch-event-stream-source'
const { t } = useI18n()
......@@ -77,7 +78,7 @@ function handleQuestionSubmit() {
multiModelDialogueList.value.forEach((modelItem, modelIndex) => {
const messages: QuestionMessageItem[] = []
const { topP, temperature, agentSystem } = modelItem
const { topP, temperature, agentSystem, modelNickName } = modelItem
modelItem.messageList.forEach((messageItem) => {
messages.push({
......@@ -111,7 +112,7 @@ function handleQuestionSubmit() {
modelItem.controller = new AbortController()
fetchCustomEventSource({
fetchEventStreamSource({
path: '/api/rest/agentApplicationInfoRest/preview.json',
payload: {
agentId: props.agentId,
......@@ -119,6 +120,7 @@ function handleQuestionSubmit() {
topP,
temperature,
agentSystem,
modelNickName,
},
controller: modelItem.controller,
onMessage: (data: any) => {
......@@ -150,6 +152,9 @@ function handleQuestionSubmit() {
onRequestError: () => {
errorMessageResponse(questionMessageId, answerMessageId, modelIndex)
},
onError: (err: any) => {
CMessage.error(err.message || '', { to: `#${modelItem.id}` })
},
onFinally: () => {
modelItem.controller = null
modelItem.isAnswerResponseWait = false
......
......@@ -88,7 +88,10 @@ function scrollToBottom() {
</script>
<template>
<div class="border-inactive-border-color flex flex-col overflow-hidden border-r px-6 last-of-type:border-none">
<div
:id="modelDialogueItem.id"
class="border-inactive-border-color flex flex-col overflow-hidden border-r px-6 last-of-type:border-none"
>
<div class="flex items-center justify-between">
<div class="flex items-center gap-5">
<n-popover
......
import { fetchEventSource } from '@microsoft/fetch-event-source'
import { BASE_URLS } from '@/config/base-url'
import { useUserStore } from '@/store/modules/user'
const EVENT_SOURCE_BASE_URL = `${BASE_URLS[window.ENV || 'DEV']}`
export function fetchEventStreamSource(config: {
path: string
payload: any
controller: AbortController
onMessage: (data: string) => void
onRequestError: (err: any) => void
onError?: (err: any) => void
onFinally?: () => void
}) {
const userStore = useUserStore()
let responseError = false
fetchEventSource(`${EVENT_SOURCE_BASE_URL}${config.path}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Request-Token': userStore.token || '',
},
body: JSON.stringify(config.payload || {}),
signal: config.controller?.signal,
openWhenHidden: true,
onmessage: (e) => {
if (e.data === '[DONE]' && !responseError) {
config.onMessage(e.data)
config.onFinally && config.onFinally()
return
}
try {
const data = JSON.parse(e.data)
if (data.code === -10) {
window.$message.info('身份已过期,请重新登陆')
config.onError && config.onError(data)
userStore.logout()
return
}
if (data.code === -1) {
responseError = true
config.controller?.abort()
config.onFinally && config.onFinally()
config.onError && config.onError(data)
return
}
config.onMessage(data.message)
} catch (err) {
config.onRequestError(err)
config.onFinally && config.onFinally()
}
},
onclose: () => {},
onerror: (err) => {
config.onRequestError(err)
config.onFinally && config.onFinally()
throw err
},
})
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment