2023-11-17 03:36:16 +00:00
|
|
|
|
<template>
|
2023-12-26 03:33:01 +00:00
|
|
|
|
<div ref="aiChatRef" class="ai-chat" :class="log ? 'chart-log' : ''">
|
2024-09-13 09:37:13 +00:00
|
|
|
|
<div
|
2024-09-13 10:41:32 +00:00
|
|
|
|
v-if="inputFieldList.length > 0 || (debug && apiInputFieldList.length > 0)"
|
2024-09-13 09:37:13 +00:00
|
|
|
|
class="mb-16"
|
|
|
|
|
|
style="padding: 0 24px"
|
|
|
|
|
|
>
|
2024-09-13 09:25:47 +00:00
|
|
|
|
<el-card shadow="always" class="dialog-card">
|
|
|
|
|
|
<div class="flex align-center cursor w-full" @click="showUserInput = !showUserInput">
|
|
|
|
|
|
<el-icon class="mr-8 arrow-icon" :class="showUserInput ? 'rotate-90' : ''"
|
|
|
|
|
|
><CaretRight
|
|
|
|
|
|
/></el-icon>
|
|
|
|
|
|
用户输入
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-collapse-transition>
|
2024-09-13 09:37:13 +00:00
|
|
|
|
<div v-show="showUserInput" class="mt-16">
|
|
|
|
|
|
<DynamicsForm
|
|
|
|
|
|
v-model="form_data"
|
|
|
|
|
|
:model="form_data"
|
|
|
|
|
|
label-position="left"
|
|
|
|
|
|
require-asterisk-position="right"
|
|
|
|
|
|
:render_data="inputFieldList"
|
|
|
|
|
|
ref="dynamicsFormRef"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<DynamicsForm
|
|
|
|
|
|
v-if="debug"
|
|
|
|
|
|
v-model="api_form_data"
|
|
|
|
|
|
:model="api_form_data"
|
|
|
|
|
|
label-position="left"
|
|
|
|
|
|
require-asterisk-position="right"
|
|
|
|
|
|
:render_data="apiInputFieldList"
|
|
|
|
|
|
ref="dynamicsFormRef2"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2024-09-13 09:25:47 +00:00
|
|
|
|
</el-collapse-transition>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</div>
|
2023-12-29 03:10:23 +00:00
|
|
|
|
<el-scrollbar ref="scrollDiv" @scroll="handleScrollTop">
|
2024-06-13 03:35:17 +00:00
|
|
|
|
<div ref="dialogScrollbar" class="ai-chat__content p-24 chat-width">
|
2024-05-20 09:50:14 +00:00
|
|
|
|
<div class="item-content mb-16" v-if="!props.available || (props.data?.prologue && !log)">
|
2023-11-22 03:05:06 +00:00
|
|
|
|
<div class="avatar">
|
2024-07-22 10:52:56 +00:00
|
|
|
|
<img v-if="data.avatar" :src="data.avatar" height="30px" />
|
|
|
|
|
|
<LogoIcon v-else height="30px" />
|
2023-11-22 03:05:06 +00:00
|
|
|
|
</div>
|
2023-11-21 03:43:47 +00:00
|
|
|
|
|
2023-11-22 03:05:06 +00:00
|
|
|
|
<div class="content">
|
|
|
|
|
|
<el-card shadow="always" class="dialog-card">
|
2024-01-12 06:55:08 +00:00
|
|
|
|
<template v-for="(item, index) in prologueList" :key="index">
|
|
|
|
|
|
<div
|
2024-01-23 08:39:36 +00:00
|
|
|
|
v-if="item.type === 'question'"
|
2024-04-15 10:46:03 +00:00
|
|
|
|
@click="quickProblemHandle(item.str)"
|
2024-01-12 06:55:08 +00:00
|
|
|
|
class="problem-button ellipsis-2 mb-8"
|
|
|
|
|
|
:class="log ? 'disabled' : 'cursor'"
|
|
|
|
|
|
>
|
2024-09-09 11:10:16 +00:00
|
|
|
|
<el-icon>
|
|
|
|
|
|
<EditPen />
|
|
|
|
|
|
</el-icon>
|
2024-01-23 08:39:36 +00:00
|
|
|
|
{{ item.str }}
|
2024-01-12 06:55:08 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<MdPreview
|
|
|
|
|
|
v-else
|
|
|
|
|
|
class="mb-8"
|
|
|
|
|
|
ref="editorRef"
|
|
|
|
|
|
editorId="preview-only"
|
2024-01-23 08:39:36 +00:00
|
|
|
|
:modelValue="item.str"
|
2024-07-01 01:45:59 +00:00
|
|
|
|
noIconfont
|
|
|
|
|
|
no-mermaid
|
2024-01-12 06:55:08 +00:00
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
2023-11-22 03:05:06 +00:00
|
|
|
|
</el-card>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2024-09-13 09:37:13 +00:00
|
|
|
|
|
2023-11-28 09:39:35 +00:00
|
|
|
|
<template v-for="(item, index) in chatList" :key="index">
|
|
|
|
|
|
<!-- 问题 -->
|
|
|
|
|
|
<div class="item-content mb-16 lighter">
|
|
|
|
|
|
<div class="avatar">
|
|
|
|
|
|
<AppAvatar>
|
|
|
|
|
|
<img src="@/assets/user-icon.svg" style="width: 54%" alt="" />
|
|
|
|
|
|
</AppAvatar>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="content">
|
2024-08-05 08:40:35 +00:00
|
|
|
|
<div class="text break-all pre-wrap">
|
2023-11-28 09:39:35 +00:00
|
|
|
|
{{ item.problem_text }}
|
|
|
|
|
|
</div>
|
2023-11-22 03:05:06 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2023-11-28 09:39:35 +00:00
|
|
|
|
<!-- 回答 -->
|
|
|
|
|
|
<div class="item-content mb-16 lighter">
|
|
|
|
|
|
<div class="avatar">
|
2024-07-22 10:52:56 +00:00
|
|
|
|
<img v-if="data.avatar" :src="data.avatar" height="30px" />
|
|
|
|
|
|
<LogoIcon v-else height="30px" />
|
2023-11-28 09:39:35 +00:00
|
|
|
|
</div>
|
2024-04-25 08:13:14 +00:00
|
|
|
|
|
2023-11-28 09:39:35 +00:00
|
|
|
|
<div class="content">
|
2023-11-29 07:42:48 +00:00
|
|
|
|
<div class="flex" v-if="!item.answer_text">
|
2024-03-14 06:30:17 +00:00
|
|
|
|
<el-card
|
|
|
|
|
|
v-if="item.write_ed === undefined || item.write_ed === true"
|
|
|
|
|
|
shadow="always"
|
|
|
|
|
|
class="dialog-card"
|
|
|
|
|
|
>
|
2024-07-02 02:53:17 +00:00
|
|
|
|
<MdRenderer
|
|
|
|
|
|
source=" 抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。"
|
|
|
|
|
|
></MdRenderer>
|
|
|
|
|
|
<!-- 知识来源 -->
|
|
|
|
|
|
<div v-if="showSource(item)">
|
|
|
|
|
|
<KnowledgeSource :data="item" :type="props.data.type" />
|
|
|
|
|
|
</div>
|
2024-03-14 06:30:17 +00:00
|
|
|
|
</el-card>
|
2024-04-17 05:05:19 +00:00
|
|
|
|
<el-card v-else-if="item.is_stop" shadow="always" class="dialog-card">
|
2024-04-18 03:28:10 +00:00
|
|
|
|
已停止回答
|
2024-04-17 05:05:19 +00:00
|
|
|
|
</el-card>
|
2024-03-14 06:30:17 +00:00
|
|
|
|
<el-card v-else shadow="always" class="dialog-card">
|
2023-12-01 11:17:58 +00:00
|
|
|
|
回答中 <span class="dotting"></span>
|
|
|
|
|
|
</el-card>
|
2023-11-28 09:39:35 +00:00
|
|
|
|
</div>
|
2023-12-13 11:06:13 +00:00
|
|
|
|
|
2023-11-29 07:38:22 +00:00
|
|
|
|
<el-card v-else shadow="always" class="dialog-card">
|
2024-07-25 09:57:08 +00:00
|
|
|
|
<MdRenderer
|
|
|
|
|
|
:source="item.answer_text"
|
|
|
|
|
|
:quick-problem-handle="quickProblemHandle"
|
|
|
|
|
|
></MdRenderer>
|
2024-07-01 01:45:59 +00:00
|
|
|
|
<!-- 知识来源 -->
|
2024-05-20 09:50:14 +00:00
|
|
|
|
<div v-if="showSource(item)">
|
2024-07-01 01:45:59 +00:00
|
|
|
|
<KnowledgeSource :data="item" :type="props.data.type" />
|
2024-01-17 09:07:52 +00:00
|
|
|
|
</div>
|
2023-11-29 07:38:22 +00:00
|
|
|
|
</el-card>
|
2023-12-07 11:28:38 +00:00
|
|
|
|
<div class="flex-between mt-8" v-if="log">
|
2024-09-13 07:47:05 +00:00
|
|
|
|
<LogOperationButton
|
|
|
|
|
|
v-model:data="chatList[index]"
|
|
|
|
|
|
:applicationId="appId"
|
|
|
|
|
|
:tts="props.data.tts_model_enable"
|
|
|
|
|
|
/>
|
2023-12-07 11:28:38 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="flex-between mt-8" v-else>
|
2023-11-30 11:14:40 +00:00
|
|
|
|
<div>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
v-if="item.is_stop && !item.write_ed"
|
|
|
|
|
|
@click="startChat(item)"
|
|
|
|
|
|
link
|
2024-09-12 03:02:03 +00:00
|
|
|
|
>继续
|
|
|
|
|
|
</el-button>
|
2023-11-30 11:14:40 +00:00
|
|
|
|
<el-button type="primary" v-else-if="!item.write_ed" @click="stopChat(item)" link
|
2024-09-12 03:02:03 +00:00
|
|
|
|
>停止回答
|
|
|
|
|
|
</el-button>
|
2023-11-30 11:14:40 +00:00
|
|
|
|
</div>
|
2024-05-21 10:32:48 +00:00
|
|
|
|
</div>
|
2024-07-03 07:02:44 +00:00
|
|
|
|
<div v-if="item.write_ed && props.appId && 500 != item.status" class="flex-between">
|
2024-05-21 10:32:48 +00:00
|
|
|
|
<OperationButton
|
2024-09-13 07:47:05 +00:00
|
|
|
|
:tts="props.data.tts_model_enable"
|
2024-05-21 10:32:48 +00:00
|
|
|
|
:data="item"
|
|
|
|
|
|
:applicationId="appId"
|
|
|
|
|
|
:chatId="chartOpenId"
|
2024-05-28 10:24:00 +00:00
|
|
|
|
:chat_loading="loading"
|
2024-05-21 10:32:48 +00:00
|
|
|
|
@regeneration="regenerationChart(item)"
|
|
|
|
|
|
/>
|
2023-11-30 11:14:40 +00:00
|
|
|
|
</div>
|
2023-11-28 09:39:35 +00:00
|
|
|
|
</div>
|
2023-11-22 03:05:06 +00:00
|
|
|
|
</div>
|
2023-11-28 09:39:35 +00:00
|
|
|
|
</template>
|
2023-11-20 10:56:31 +00:00
|
|
|
|
</div>
|
2023-11-21 02:56:25 +00:00
|
|
|
|
</el-scrollbar>
|
2023-12-07 11:28:38 +00:00
|
|
|
|
<div class="ai-chat__operate p-24" v-if="!log">
|
2024-06-13 03:35:17 +00:00
|
|
|
|
<slot name="operateBefore" />
|
|
|
|
|
|
<div class="operate-textarea flex chat-width">
|
2023-11-21 10:32:51 +00:00
|
|
|
|
<el-input
|
2023-12-04 10:02:00 +00:00
|
|
|
|
ref="quickInputRef"
|
2023-11-21 10:32:51 +00:00
|
|
|
|
v-model="inputValue"
|
2024-09-14 03:17:14 +00:00
|
|
|
|
:placeholder="
|
|
|
|
|
|
startRecorderTime
|
|
|
|
|
|
? '说话中...'
|
|
|
|
|
|
: recorderLoading
|
|
|
|
|
|
? '转文字中...'
|
|
|
|
|
|
: '请输入问题,Ctrl+Enter 换行,Enter发送'
|
|
|
|
|
|
"
|
2024-06-13 10:28:44 +00:00
|
|
|
|
:autosize="{ minRows: 1, maxRows: isMobile ? 4 : 10 }"
|
2024-03-25 09:33:32 +00:00
|
|
|
|
type="textarea"
|
2024-06-13 03:41:22 +00:00
|
|
|
|
:maxlength="100000"
|
2023-11-28 09:39:35 +00:00
|
|
|
|
@keydown.enter="sendChatHandle($event)"
|
2023-11-21 10:32:51 +00:00
|
|
|
|
/>
|
2024-09-13 07:47:05 +00:00
|
|
|
|
|
|
|
|
|
|
<div class="operate flex align-center">
|
2024-09-13 10:41:32 +00:00
|
|
|
|
<span v-if="props.data.stt_model_enable" class="flex align-center">
|
2024-09-13 07:47:05 +00:00
|
|
|
|
<el-button text v-if="mediaRecorderStatus" @click="startRecording">
|
|
|
|
|
|
<el-icon>
|
|
|
|
|
|
<Microphone />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</el-button>
|
2024-09-14 03:17:14 +00:00
|
|
|
|
<div v-else class="operate flex align-center">
|
|
|
|
|
|
<el-text type="info"
|
|
|
|
|
|
>00:{{ recorderTime < 10 ? `0${recorderTime}` : recorderTime }}</el-text
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-button text type="primary" @click="stopRecording" :loading="recorderLoading">
|
|
|
|
|
|
<el-icon>
|
|
|
|
|
|
<VideoPause />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-divider v-if="!startRecorderTime && !recorderLoading" direction="vertical" />
|
2024-09-13 07:47:05 +00:00
|
|
|
|
</span>
|
|
|
|
|
|
|
2023-12-04 10:10:42 +00:00
|
|
|
|
<el-button
|
2024-09-14 03:17:14 +00:00
|
|
|
|
v-if="!startRecorderTime && !recorderLoading"
|
2023-12-04 10:10:42 +00:00
|
|
|
|
text
|
|
|
|
|
|
class="sent-button"
|
|
|
|
|
|
:disabled="isDisabledChart || loading"
|
|
|
|
|
|
@click="sendChatHandle"
|
|
|
|
|
|
>
|
|
|
|
|
|
<img v-show="isDisabledChart || loading" src="@/assets/icon_send.svg" alt="" />
|
2024-07-23 10:21:46 +00:00
|
|
|
|
<SendIcon v-show="!isDisabledChart && !loading" />
|
2023-11-21 10:32:51 +00:00
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
2023-11-20 10:56:31 +00:00
|
|
|
|
</div>
|
2023-11-17 03:36:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
2024-06-13 09:22:43 +00:00
|
|
|
|
import { ref, nextTick, computed, watch, reactive, onMounted } from 'vue'
|
2023-12-06 09:30:46 +00:00
|
|
|
|
import { useRoute } from 'vue-router'
|
2023-12-07 11:28:38 +00:00
|
|
|
|
import LogOperationButton from './LogOperationButton.vue'
|
2023-12-01 03:36:04 +00:00
|
|
|
|
import OperationButton from './OperationButton.vue'
|
2024-07-01 01:45:59 +00:00
|
|
|
|
import KnowledgeSource from './KnowledgeSource.vue'
|
2023-11-24 11:02:52 +00:00
|
|
|
|
import applicationApi from '@/api/application'
|
2024-01-18 07:59:30 +00:00
|
|
|
|
import logApi from '@/api/log'
|
2023-11-30 09:12:39 +00:00
|
|
|
|
import { ChatManagement, type chatType } from '@/api/type/application'
|
2023-11-28 09:39:35 +00:00
|
|
|
|
import { randomId } from '@/utils/utils'
|
2023-12-01 10:21:49 +00:00
|
|
|
|
import useStore from '@/stores'
|
2024-07-01 01:45:59 +00:00
|
|
|
|
import MdRenderer from '@/components/markdown/MdRenderer.vue'
|
|
|
|
|
|
import { isWorkFlow } from '@/utils/application'
|
2024-03-15 08:17:21 +00:00
|
|
|
|
import { debounce } from 'lodash'
|
2024-09-06 06:38:07 +00:00
|
|
|
|
import Recorder from 'recorder-core'
|
|
|
|
|
|
import 'recorder-core/src/engine/mp3'
|
|
|
|
|
|
import 'recorder-core/src/engine/mp3-engine'
|
2024-09-09 11:10:16 +00:00
|
|
|
|
import { MsgWarning } from '@/utils/message'
|
|
|
|
|
|
import DynamicsForm from '@/components/dynamics-form/index.vue'
|
|
|
|
|
|
import type { FormField } from '@/components/dynamics-form/type'
|
2024-09-06 06:38:07 +00:00
|
|
|
|
|
2023-12-07 07:22:07 +00:00
|
|
|
|
defineOptions({ name: 'AiChat' })
|
2023-12-01 10:21:49 +00:00
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
const {
|
2024-06-13 10:28:44 +00:00
|
|
|
|
params: { accessToken, id },
|
|
|
|
|
|
query: { mode }
|
2023-12-01 10:21:49 +00:00
|
|
|
|
} = route as any
|
2023-11-17 03:36:16 +00:00
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
data: {
|
2023-11-22 09:04:47 +00:00
|
|
|
|
type: Object,
|
2024-09-12 03:02:03 +00:00
|
|
|
|
default: () => {}
|
2023-11-30 10:50:42 +00:00
|
|
|
|
},
|
2024-01-23 08:39:36 +00:00
|
|
|
|
appId: String, // 仅分享链接有
|
2023-12-07 11:28:38 +00:00
|
|
|
|
log: Boolean,
|
|
|
|
|
|
record: {
|
|
|
|
|
|
type: Array<chatType[]>,
|
|
|
|
|
|
default: () => []
|
2024-03-05 04:18:07 +00:00
|
|
|
|
},
|
|
|
|
|
|
// 应用是否可用
|
|
|
|
|
|
available: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: true
|
2024-05-20 09:50:14 +00:00
|
|
|
|
},
|
|
|
|
|
|
chatId: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: ''
|
2024-09-13 09:37:13 +00:00
|
|
|
|
}, // 历史记录Id
|
|
|
|
|
|
debug: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
}
|
2023-11-17 03:36:16 +00:00
|
|
|
|
})
|
2024-05-20 09:50:14 +00:00
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(['refresh', 'scroll'])
|
|
|
|
|
|
|
2024-06-13 09:22:43 +00:00
|
|
|
|
const { application, common } = useStore()
|
2023-11-28 09:39:35 +00:00
|
|
|
|
|
2024-06-13 10:28:44 +00:00
|
|
|
|
const isMobile = computed(() => {
|
|
|
|
|
|
return common.isMobile() || mode === 'embed'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2023-12-26 03:33:01 +00:00
|
|
|
|
const aiChatRef = ref()
|
2023-12-04 10:02:00 +00:00
|
|
|
|
const quickInputRef = ref()
|
2023-11-28 09:39:35 +00:00
|
|
|
|
const scrollDiv = ref()
|
|
|
|
|
|
const dialogScrollbar = ref()
|
2023-11-24 11:02:52 +00:00
|
|
|
|
const loading = ref(false)
|
2024-09-14 03:17:14 +00:00
|
|
|
|
const inputValue = ref<string>('')
|
2023-11-27 10:57:52 +00:00
|
|
|
|
const chartOpenId = ref('')
|
2023-12-07 11:28:38 +00:00
|
|
|
|
const chatList = ref<any[]>([])
|
2024-09-09 11:10:16 +00:00
|
|
|
|
const inputFieldList = ref<FormField[]>([])
|
2024-09-13 07:53:54 +00:00
|
|
|
|
const apiInputFieldList = ref<FormField[]>([])
|
2024-09-09 11:10:16 +00:00
|
|
|
|
const form_data = ref<any>({})
|
2024-09-13 08:15:20 +00:00
|
|
|
|
const api_form_data = ref<any>({})
|
2023-11-27 10:57:52 +00:00
|
|
|
|
|
2024-09-13 09:25:47 +00:00
|
|
|
|
const showUserInput = ref(true)
|
2024-09-14 03:17:14 +00:00
|
|
|
|
const recorderTime = ref(0)
|
|
|
|
|
|
const startRecorderTime = ref(false)
|
|
|
|
|
|
const recorderLoading = ref(false)
|
2024-09-13 09:25:47 +00:00
|
|
|
|
|
2023-11-29 03:25:14 +00:00
|
|
|
|
const isDisabledChart = computed(
|
2024-04-28 09:09:12 +00:00
|
|
|
|
() => !(inputValue.value.trim() && (props.appId || props.data?.name))
|
2023-11-29 03:25:14 +00:00
|
|
|
|
)
|
2024-01-23 08:39:36 +00:00
|
|
|
|
const isMdArray = (val: string) => val.match(/^-\s.*/m)
|
2024-01-12 06:55:08 +00:00
|
|
|
|
const prologueList = computed(() => {
|
2024-03-05 04:18:07 +00:00
|
|
|
|
const temp = props.available
|
|
|
|
|
|
? props.data?.prologue
|
|
|
|
|
|
: '抱歉,当前正在维护,无法提供服务,请稍后再试!'
|
2024-01-17 08:29:37 +00:00
|
|
|
|
const lines = temp?.split('\n')
|
2024-05-22 11:14:08 +00:00
|
|
|
|
return lines
|
|
|
|
|
|
.reduce((pre_array: Array<any>, current: string, index: number) => {
|
|
|
|
|
|
const currentObj = isMdArray(current)
|
|
|
|
|
|
? {
|
2024-09-12 03:02:03 +00:00
|
|
|
|
type: 'question',
|
|
|
|
|
|
str: current.replace(/^-\s+/, ''),
|
|
|
|
|
|
index: index
|
|
|
|
|
|
}
|
2024-05-22 11:14:08 +00:00
|
|
|
|
: {
|
2024-09-12 03:02:03 +00:00
|
|
|
|
type: 'md',
|
|
|
|
|
|
str: current,
|
|
|
|
|
|
index: index
|
|
|
|
|
|
}
|
2024-05-22 11:14:08 +00:00
|
|
|
|
if (pre_array.length > 0) {
|
|
|
|
|
|
const pre = pre_array[pre_array.length - 1]
|
|
|
|
|
|
if (!isMdArray(current) && pre.type == 'md') {
|
|
|
|
|
|
pre.str = [pre.str, current].join('\n')
|
|
|
|
|
|
pre.index = index
|
|
|
|
|
|
return pre_array
|
|
|
|
|
|
} else {
|
|
|
|
|
|
pre_array.push(currentObj)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
pre_array.push(currentObj)
|
2024-01-23 08:39:36 +00:00
|
|
|
|
}
|
2024-05-22 11:14:08 +00:00
|
|
|
|
return pre_array
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
.sort((pre: any, next: any) => pre.index - next.index)
|
2024-01-12 06:55:08 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
2024-05-20 09:50:14 +00:00
|
|
|
|
watch(
|
|
|
|
|
|
() => props.chatId,
|
|
|
|
|
|
(val) => {
|
|
|
|
|
|
if (val && val !== 'new') {
|
|
|
|
|
|
chartOpenId.value = val
|
|
|
|
|
|
} else {
|
|
|
|
|
|
chartOpenId.value = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true }
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2024-09-09 11:10:16 +00:00
|
|
|
|
function handleInputFieldList() {
|
2024-09-12 03:02:03 +00:00
|
|
|
|
props.data.work_flow?.nodes
|
|
|
|
|
|
?.filter((v: any) => v.id === 'base-node')
|
2024-09-09 11:10:16 +00:00
|
|
|
|
.map((v: any) => {
|
2024-09-12 03:02:03 +00:00
|
|
|
|
inputFieldList.value = v.properties.input_field_list
|
2024-09-13 07:47:05 +00:00
|
|
|
|
? v.properties.input_field_list
|
|
|
|
|
|
.filter((v: any) => v.assignment_method === 'user_input')
|
|
|
|
|
|
.map((v: any) => {
|
|
|
|
|
|
switch (v.type) {
|
|
|
|
|
|
case 'input':
|
|
|
|
|
|
return {
|
|
|
|
|
|
field: v.variable,
|
|
|
|
|
|
input_type: 'TextInput',
|
|
|
|
|
|
label: v.name,
|
|
|
|
|
|
required: v.is_required
|
2024-09-12 03:02:03 +00:00
|
|
|
|
}
|
2024-09-13 07:47:05 +00:00
|
|
|
|
case 'select':
|
|
|
|
|
|
return {
|
|
|
|
|
|
field: v.variable,
|
|
|
|
|
|
input_type: 'SingleSelect',
|
|
|
|
|
|
label: v.name,
|
|
|
|
|
|
required: v.is_required,
|
|
|
|
|
|
option_list: v.optionList.map((o: any) => {
|
|
|
|
|
|
return { key: o, value: o }
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
case 'date':
|
|
|
|
|
|
return {
|
|
|
|
|
|
field: v.variable,
|
|
|
|
|
|
input_type: 'DatePicker',
|
|
|
|
|
|
label: v.name,
|
|
|
|
|
|
required: v.is_required,
|
|
|
|
|
|
attrs: {
|
|
|
|
|
|
format: 'YYYY-MM-DD HH:mm:ss',
|
|
|
|
|
|
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
|
|
|
|
|
type: 'datetime'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2024-09-12 03:02:03 +00:00
|
|
|
|
: []
|
2024-09-13 05:40:43 +00:00
|
|
|
|
apiInputFieldList.value = v.properties.input_field_list
|
2024-09-13 07:47:05 +00:00
|
|
|
|
? v.properties.input_field_list
|
|
|
|
|
|
.filter((v: any) => v.assignment_method === 'api_input')
|
|
|
|
|
|
.map((v: any) => {
|
2024-09-13 07:53:54 +00:00
|
|
|
|
switch (v.type) {
|
|
|
|
|
|
case 'input':
|
|
|
|
|
|
return {
|
|
|
|
|
|
field: v.variable,
|
|
|
|
|
|
input_type: 'TextInput',
|
|
|
|
|
|
label: v.name,
|
|
|
|
|
|
required: v.is_required
|
|
|
|
|
|
}
|
|
|
|
|
|
case 'select':
|
|
|
|
|
|
return {
|
|
|
|
|
|
field: v.variable,
|
|
|
|
|
|
input_type: 'SingleSelect',
|
|
|
|
|
|
label: v.name,
|
|
|
|
|
|
required: v.is_required,
|
|
|
|
|
|
option_list: v.optionList.map((o: any) => {
|
|
|
|
|
|
return { key: o, value: o }
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
case 'date':
|
|
|
|
|
|
return {
|
|
|
|
|
|
field: v.variable,
|
|
|
|
|
|
input_type: 'DatePicker',
|
|
|
|
|
|
label: v.name,
|
|
|
|
|
|
required: v.is_required,
|
|
|
|
|
|
attrs: {
|
|
|
|
|
|
format: 'YYYY-MM-DD HH:mm:ss',
|
|
|
|
|
|
'value-format': 'YYYY-MM-DD HH:mm:ss',
|
|
|
|
|
|
type: 'datetime'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
|
|
|
break
|
2024-09-13 07:47:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
})
|
2024-09-13 05:40:43 +00:00
|
|
|
|
: []
|
2024-09-09 11:10:16 +00:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-23 03:16:23 +00:00
|
|
|
|
watch(
|
|
|
|
|
|
() => props.data,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
chartOpenId.value = ''
|
2024-09-09 11:10:16 +00:00
|
|
|
|
handleInputFieldList()
|
2024-01-23 03:16:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
{ deep: true }
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2023-12-07 11:28:38 +00:00
|
|
|
|
watch(
|
|
|
|
|
|
() => props.record,
|
|
|
|
|
|
(value) => {
|
2024-05-20 09:50:14 +00:00
|
|
|
|
chatList.value = value
|
2023-12-07 11:28:38 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
immediate: true
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2024-05-20 09:50:14 +00:00
|
|
|
|
function showSource(row: any) {
|
|
|
|
|
|
if (props.log) {
|
|
|
|
|
|
return true
|
2024-07-03 07:02:44 +00:00
|
|
|
|
} else if (row.write_ed && 500 !== row.status) {
|
2024-05-20 09:50:14 +00:00
|
|
|
|
if (id || props.data?.show_source) {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-15 10:46:03 +00:00
|
|
|
|
function quickProblemHandle(val: string) {
|
2024-09-13 08:15:20 +00:00
|
|
|
|
if (!checkInputParam()) {
|
|
|
|
|
|
return
|
2024-09-13 07:53:54 +00:00
|
|
|
|
}
|
2024-04-28 09:20:35 +00:00
|
|
|
|
if (!loading.value && props.data?.name) {
|
2024-03-15 08:17:21 +00:00
|
|
|
|
handleDebounceClick(val)
|
2023-12-07 11:28:38 +00:00
|
|
|
|
}
|
2023-11-24 11:02:52 +00:00
|
|
|
|
}
|
2023-11-27 09:06:39 +00:00
|
|
|
|
|
2024-03-15 08:17:21 +00:00
|
|
|
|
const handleDebounceClick = debounce((val) => {
|
|
|
|
|
|
chatMessage(null, val)
|
|
|
|
|
|
}, 200)
|
|
|
|
|
|
|
2024-09-13 08:15:20 +00:00
|
|
|
|
function checkInputParam() {
|
2024-09-09 11:10:16 +00:00
|
|
|
|
// 检查inputFieldList是否有未填写的字段
|
|
|
|
|
|
for (let i = 0; i < inputFieldList.value.length; i++) {
|
|
|
|
|
|
if (inputFieldList.value[i].required && !form_data.value[inputFieldList.value[i].field]) {
|
|
|
|
|
|
MsgWarning('请填写所有必填字段')
|
2024-09-13 08:15:20 +00:00
|
|
|
|
return false
|
2024-09-09 11:10:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-09-13 08:15:20 +00:00
|
|
|
|
// 浏览器query参数找到接口传参
|
|
|
|
|
|
let msg = []
|
|
|
|
|
|
for (let f of apiInputFieldList.value) {
|
2024-09-13 08:53:17 +00:00
|
|
|
|
if (!api_form_data.value[f.field]) {
|
2024-09-13 08:15:20 +00:00
|
|
|
|
api_form_data.value[f.field] = route.query[f.field]
|
2024-09-13 08:53:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
if (!api_form_data.value[f.field]) {
|
|
|
|
|
|
msg.push(f.field)
|
2024-09-13 08:15:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (msg.length > 0) {
|
|
|
|
|
|
MsgWarning(`请在URL中填写参数 ${msg.join('、')}的值`)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function sendChatHandle(event: any) {
|
|
|
|
|
|
if (!checkInputParam()) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2023-11-28 09:39:35 +00:00
|
|
|
|
if (!event.ctrlKey) {
|
|
|
|
|
|
// 如果没有按下组合键ctrl,则会阻止默认事件
|
|
|
|
|
|
event.preventDefault()
|
2024-04-01 06:39:56 +00:00
|
|
|
|
if (!isDisabledChart.value && !loading.value && !event.isComposing) {
|
2024-04-16 08:56:19 +00:00
|
|
|
|
if (inputValue.value.trim()) {
|
|
|
|
|
|
chatMessage()
|
|
|
|
|
|
}
|
2023-11-29 03:25:14 +00:00
|
|
|
|
}
|
2023-11-28 09:39:35 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
// 如果同时按下ctrl+回车键,则会换行
|
|
|
|
|
|
inputValue.value += '\n'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-30 09:12:39 +00:00
|
|
|
|
const stopChat = (chat: chatType) => {
|
|
|
|
|
|
ChatManagement.stop(chat.id)
|
|
|
|
|
|
}
|
|
|
|
|
|
const startChat = (chat: chatType) => {
|
|
|
|
|
|
ChatManagement.write(chat.id)
|
|
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-27 09:06:39 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 对话
|
|
|
|
|
|
*/
|
2024-03-05 04:18:07 +00:00
|
|
|
|
function getChartOpenId(chat?: any) {
|
2023-11-24 11:02:52 +00:00
|
|
|
|
loading.value = true
|
2024-01-17 08:29:37 +00:00
|
|
|
|
const obj = props.data
|
2023-11-30 10:50:42 +00:00
|
|
|
|
if (props.appId) {
|
2024-03-05 04:18:07 +00:00
|
|
|
|
return applicationApi
|
2023-11-30 10:50:42 +00:00
|
|
|
|
.getChatOpen(props.appId)
|
|
|
|
|
|
.then((res) => {
|
|
|
|
|
|
chartOpenId.value = res.data
|
2024-03-05 04:18:07 +00:00
|
|
|
|
chatMessage(chat)
|
2023-11-30 10:50:42 +00:00
|
|
|
|
})
|
2023-12-01 10:21:49 +00:00
|
|
|
|
.catch((res) => {
|
2024-01-31 08:23:30 +00:00
|
|
|
|
if (res.response.status === 403) {
|
2023-12-01 10:21:49 +00:00
|
|
|
|
application.asyncAppAuthentication(accessToken).then(() => {
|
2024-05-27 09:31:51 +00:00
|
|
|
|
getChartOpenId(chat)
|
2023-12-01 10:21:49 +00:00
|
|
|
|
})
|
2024-03-05 04:18:07 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
return Promise.reject(res)
|
2023-12-01 10:21:49 +00:00
|
|
|
|
}
|
2023-11-30 10:50:42 +00:00
|
|
|
|
})
|
|
|
|
|
|
} else {
|
2024-07-01 01:45:59 +00:00
|
|
|
|
if (isWorkFlow(obj.type)) {
|
|
|
|
|
|
const submitObj = {
|
|
|
|
|
|
work_flow: obj.work_flow
|
|
|
|
|
|
}
|
|
|
|
|
|
return applicationApi
|
|
|
|
|
|
.postWorkflowChatOpen(submitObj)
|
|
|
|
|
|
.then((res) => {
|
|
|
|
|
|
chartOpenId.value = res.data
|
|
|
|
|
|
chatMessage(chat)
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((res) => {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
return Promise.reject(res)
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return applicationApi
|
|
|
|
|
|
.postChatOpen(obj)
|
|
|
|
|
|
.then((res) => {
|
|
|
|
|
|
chartOpenId.value = res.data
|
|
|
|
|
|
chatMessage(chat)
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((res) => {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
return Promise.reject(res)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2023-11-30 10:50:42 +00:00
|
|
|
|
}
|
2023-11-24 11:02:52 +00:00
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2024-01-18 10:36:24 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取一个递归函数,处理流式数据
|
|
|
|
|
|
* @param chat 每一条对话记录
|
|
|
|
|
|
* @param reader 流数据
|
|
|
|
|
|
* @param stream 是否是流式数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
const getWrite = (chat: any, reader: any, stream: boolean) => {
|
|
|
|
|
|
let tempResult = ''
|
|
|
|
|
|
/**
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param done 是否结束
|
|
|
|
|
|
* @param value 值
|
|
|
|
|
|
*/
|
|
|
|
|
|
const write_stream = ({ done, value }: { done: boolean; value: any }) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (done) {
|
|
|
|
|
|
ChatManagement.close(chat.id)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const decoder = new TextDecoder('utf-8')
|
|
|
|
|
|
let str = decoder.decode(value, { stream: true })
|
|
|
|
|
|
// 这里解释一下 start 因为数据流返回流并不是按照后端chunk返回 我们希望得到的chunk是data:{xxx}\n\n 但是它获取到的可能是 data:{ -> xxx}\n\n 总而言之就是 fetch不能保证每个chunk都说以data:开始 \n\n结束
|
|
|
|
|
|
tempResult += str
|
2024-04-23 08:35:47 +00:00
|
|
|
|
const split = tempResult.match(/data:.*}\n\n/g)
|
|
|
|
|
|
if (split) {
|
|
|
|
|
|
str = split.join('')
|
|
|
|
|
|
tempResult = tempResult.replace(str, '')
|
2024-01-18 10:36:24 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
return reader.read().then(write_stream)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 这里解释一下 end
|
|
|
|
|
|
if (str && str.startsWith('data:')) {
|
|
|
|
|
|
if (split) {
|
|
|
|
|
|
for (const index in split) {
|
|
|
|
|
|
const chunk = JSON?.parse(split[index].replace('data:', ''))
|
2024-04-25 10:55:12 +00:00
|
|
|
|
chat.chat_id = chunk.chat_id
|
2024-01-18 10:36:24 +00:00
|
|
|
|
chat.record_id = chunk.id
|
|
|
|
|
|
const content = chunk?.content
|
|
|
|
|
|
if (content) {
|
|
|
|
|
|
ChatManagement.append(chat.id, content)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (chunk.is_end) {
|
|
|
|
|
|
// 流处理成功 返回成功回调
|
|
|
|
|
|
return Promise.resolve()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return Promise.reject(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
return reader.read().then(write_stream)
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理 json 响应
|
|
|
|
|
|
* @param param0
|
|
|
|
|
|
*/
|
|
|
|
|
|
const write_json = ({ done, value }: { done: boolean; value: any }) => {
|
|
|
|
|
|
if (done) {
|
|
|
|
|
|
const result_block = JSON.parse(tempResult)
|
|
|
|
|
|
if (result_block.code === 500) {
|
|
|
|
|
|
return Promise.reject(result_block.message)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (result_block.content) {
|
|
|
|
|
|
ChatManagement.append(chat.id, result_block.content)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
ChatManagement.close(chat.id)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (value) {
|
|
|
|
|
|
const decoder = new TextDecoder('utf-8')
|
|
|
|
|
|
tempResult += decoder.decode(value)
|
|
|
|
|
|
}
|
|
|
|
|
|
return reader.read().then(write_json)
|
|
|
|
|
|
}
|
|
|
|
|
|
return stream ? write_stream : write_json
|
|
|
|
|
|
}
|
2024-03-13 08:07:13 +00:00
|
|
|
|
const errorWrite = (chat: any, message?: string) => {
|
2024-03-05 04:18:07 +00:00
|
|
|
|
ChatManagement.addChatRecord(chat, 50, loading)
|
|
|
|
|
|
ChatManagement.write(chat.id)
|
2024-03-13 08:07:13 +00:00
|
|
|
|
ChatManagement.append(chat.id, message || '抱歉,当前正在维护,无法提供服务,请稍后再试!')
|
2024-07-03 07:02:44 +00:00
|
|
|
|
ChatManagement.updateStatus(chat.id, 500)
|
2024-03-05 04:18:07 +00:00
|
|
|
|
ChatManagement.close(chat.id)
|
|
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2024-04-01 06:39:56 +00:00
|
|
|
|
function chatMessage(chat?: any, problem?: string, re_chat?: boolean) {
|
2023-11-28 09:39:35 +00:00
|
|
|
|
loading.value = true
|
2024-03-05 04:18:07 +00:00
|
|
|
|
if (!chat) {
|
|
|
|
|
|
chat = reactive({
|
|
|
|
|
|
id: randomId(),
|
2024-04-16 08:56:19 +00:00
|
|
|
|
problem_text: problem ? problem : inputValue.value.trim(),
|
2024-03-05 04:18:07 +00:00
|
|
|
|
answer_text: '',
|
|
|
|
|
|
buffer: [],
|
|
|
|
|
|
write_ed: false,
|
|
|
|
|
|
is_stop: false,
|
|
|
|
|
|
record_id: '',
|
2024-07-03 07:02:44 +00:00
|
|
|
|
vote_status: '-1',
|
|
|
|
|
|
status: undefined
|
2024-03-05 04:18:07 +00:00
|
|
|
|
})
|
|
|
|
|
|
chatList.value.push(chat)
|
2024-04-17 05:05:19 +00:00
|
|
|
|
ChatManagement.addChatRecord(chat, 50, loading)
|
|
|
|
|
|
ChatManagement.write(chat.id)
|
2024-03-05 04:18:07 +00:00
|
|
|
|
inputValue.value = ''
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
// 将滚动条滚动到最下面
|
|
|
|
|
|
scrollDiv.value.setScrollTop(getMaxHeight())
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2023-11-27 10:57:52 +00:00
|
|
|
|
if (!chartOpenId.value) {
|
2024-05-20 09:50:14 +00:00
|
|
|
|
getChartOpenId(chat).catch(() => {
|
2024-03-05 04:18:07 +00:00
|
|
|
|
errorWrite(chat)
|
|
|
|
|
|
})
|
2023-11-27 10:57:52 +00:00
|
|
|
|
} else {
|
2024-04-01 06:39:56 +00:00
|
|
|
|
const obj = {
|
|
|
|
|
|
message: chat.problem_text,
|
2024-09-09 11:10:16 +00:00
|
|
|
|
re_chat: re_chat || false,
|
2024-09-13 09:25:47 +00:00
|
|
|
|
form_data: { ...form_data.value, ...api_form_data.value }
|
2024-04-01 06:39:56 +00:00
|
|
|
|
}
|
2024-01-18 10:36:24 +00:00
|
|
|
|
// 对话
|
|
|
|
|
|
applicationApi
|
2024-04-01 06:39:56 +00:00
|
|
|
|
.postChatMessage(chartOpenId.value, obj)
|
2024-01-18 10:36:24 +00:00
|
|
|
|
.then((response) => {
|
2024-01-31 08:23:30 +00:00
|
|
|
|
if (response.status === 401) {
|
2024-03-05 04:18:07 +00:00
|
|
|
|
application
|
|
|
|
|
|
.asyncAppAuthentication(accessToken)
|
|
|
|
|
|
.then(() => {
|
2024-05-27 08:19:28 +00:00
|
|
|
|
chatMessage(chat, problem)
|
2024-03-05 04:18:07 +00:00
|
|
|
|
})
|
2024-05-20 09:50:14 +00:00
|
|
|
|
.catch(() => {
|
2024-03-05 04:18:07 +00:00
|
|
|
|
errorWrite(chat)
|
|
|
|
|
|
})
|
2024-03-13 08:07:13 +00:00
|
|
|
|
} else if (response.status === 460) {
|
|
|
|
|
|
return Promise.reject('无法识别用户身份')
|
|
|
|
|
|
} else if (response.status === 461) {
|
|
|
|
|
|
return Promise.reject('抱歉,您的提问已达到最大限制,请明天再来吧!')
|
2024-01-31 08:23:30 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
// 将滚动条滚动到最下面
|
2024-02-26 03:00:19 +00:00
|
|
|
|
scrollDiv.value.setScrollTop(getMaxHeight())
|
2024-01-31 08:23:30 +00:00
|
|
|
|
})
|
|
|
|
|
|
const reader = response.body.getReader()
|
|
|
|
|
|
// 处理流数据
|
|
|
|
|
|
const write = getWrite(
|
|
|
|
|
|
chat,
|
|
|
|
|
|
reader,
|
|
|
|
|
|
response.headers.get('Content-Type') !== 'application/json'
|
|
|
|
|
|
)
|
|
|
|
|
|
return reader.read().then(write)
|
|
|
|
|
|
}
|
2024-01-18 10:36:24 +00:00
|
|
|
|
})
|
|
|
|
|
|
.then(() => {
|
2024-05-20 09:50:14 +00:00
|
|
|
|
if (props.chatId === 'new') {
|
|
|
|
|
|
emit('refresh', chartOpenId.value)
|
|
|
|
|
|
}
|
2024-06-13 03:35:17 +00:00
|
|
|
|
quickInputRef.value.textareaStyle.height = '45px'
|
2024-04-25 08:13:14 +00:00
|
|
|
|
return (id || props.data?.show_source) && getSourceDetail(chat)
|
2024-01-18 10:36:24 +00:00
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
ChatManagement.close(chat.id)
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((e: any) => {
|
2024-03-13 08:07:13 +00:00
|
|
|
|
errorWrite(chat, e + '')
|
2024-01-18 10:36:24 +00:00
|
|
|
|
})
|
2023-11-27 10:57:52 +00:00
|
|
|
|
}
|
2023-11-27 09:06:39 +00:00
|
|
|
|
}
|
2023-11-28 09:39:35 +00:00
|
|
|
|
|
2023-12-01 11:17:58 +00:00
|
|
|
|
function regenerationChart(item: chatType) {
|
|
|
|
|
|
inputValue.value = item.problem_text
|
2024-05-28 10:24:00 +00:00
|
|
|
|
if (!loading.value) {
|
|
|
|
|
|
chatMessage(null, '', true)
|
|
|
|
|
|
}
|
2023-12-01 11:17:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-18 10:36:24 +00:00
|
|
|
|
function getSourceDetail(row: any) {
|
2024-04-28 09:09:12 +00:00
|
|
|
|
logApi.getRecordDetail(id || props.appId, row.chat_id, row.record_id, loading).then((res) => {
|
|
|
|
|
|
const exclude_keys = ['answer_text', 'id']
|
|
|
|
|
|
Object.keys(res.data).forEach((key) => {
|
|
|
|
|
|
if (!exclude_keys.includes(key)) {
|
|
|
|
|
|
row[key] = res.data[key]
|
|
|
|
|
|
}
|
2024-01-18 10:36:24 +00:00
|
|
|
|
})
|
2024-04-28 09:09:12 +00:00
|
|
|
|
})
|
2024-01-18 10:36:24 +00:00
|
|
|
|
return true
|
2024-01-18 07:59:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-26 08:26:18 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 滚动条距离最上面的高度
|
|
|
|
|
|
*/
|
|
|
|
|
|
const scrollTop = ref(0)
|
|
|
|
|
|
|
|
|
|
|
|
const scorll = ref(true)
|
|
|
|
|
|
|
2024-02-26 03:00:19 +00:00
|
|
|
|
const getMaxHeight = () => {
|
|
|
|
|
|
return dialogScrollbar.value!.scrollHeight
|
|
|
|
|
|
}
|
2023-12-26 08:26:18 +00:00
|
|
|
|
const handleScrollTop = ($event: any) => {
|
|
|
|
|
|
scrollTop.value = $event.scrollTop
|
|
|
|
|
|
if (
|
|
|
|
|
|
dialogScrollbar.value.scrollHeight - (scrollTop.value + scrollDiv.value.wrapRef.offsetHeight) <=
|
2024-02-26 03:00:19 +00:00
|
|
|
|
30
|
2023-12-26 08:26:18 +00:00
|
|
|
|
) {
|
|
|
|
|
|
scorll.value = true
|
|
|
|
|
|
} else {
|
|
|
|
|
|
scorll.value = false
|
|
|
|
|
|
}
|
2024-05-20 09:50:14 +00:00
|
|
|
|
emit('scroll', { ...$event, dialogScrollbar: dialogScrollbar.value, scrollDiv: scrollDiv.value })
|
2023-11-28 09:39:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-26 08:26:18 +00:00
|
|
|
|
const handleScroll = () => {
|
|
|
|
|
|
if (!props.log && scrollDiv.value) {
|
|
|
|
|
|
// 内部高度小于外部高度 就需要出滚动条
|
|
|
|
|
|
if (scrollDiv.value.wrapRef.offsetHeight < dialogScrollbar.value.scrollHeight) {
|
|
|
|
|
|
// 如果当前滚动条距离最下面的距离在 规定距离 滚动条就跟随
|
|
|
|
|
|
if (scorll.value) {
|
2024-02-26 03:00:19 +00:00
|
|
|
|
scrollDiv.value.setScrollTop(getMaxHeight())
|
2023-12-26 08:26:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-12-07 11:28:38 +00:00
|
|
|
|
}
|
2023-12-26 08:26:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-06 06:38:07 +00:00
|
|
|
|
// 定义响应式引用
|
2024-09-09 11:10:16 +00:00
|
|
|
|
const mediaRecorder = ref<any>(null)
|
2024-09-13 07:47:05 +00:00
|
|
|
|
|
2024-09-06 06:38:07 +00:00
|
|
|
|
const mediaRecorderStatus = ref(true)
|
|
|
|
|
|
|
|
|
|
|
|
// 开始录音
|
|
|
|
|
|
const startRecording = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
mediaRecorderStatus.value = false
|
2024-09-14 03:17:14 +00:00
|
|
|
|
handleTimeChange()
|
2024-09-06 06:38:07 +00:00
|
|
|
|
mediaRecorder.value = new Recorder({
|
|
|
|
|
|
type: 'mp3',
|
|
|
|
|
|
bitRate: 128,
|
2024-09-09 11:10:16 +00:00
|
|
|
|
sampleRate: 44100
|
2024-09-06 06:38:07 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
2024-09-12 03:02:03 +00:00
|
|
|
|
mediaRecorder.value.open(
|
|
|
|
|
|
() => {
|
|
|
|
|
|
mediaRecorder.value.start()
|
|
|
|
|
|
},
|
|
|
|
|
|
(err: any) => {
|
|
|
|
|
|
console.error(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
2024-09-06 06:38:07 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('无法获取音频权限:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 停止录音
|
|
|
|
|
|
const stopRecording = () => {
|
2024-09-14 03:17:14 +00:00
|
|
|
|
startRecorderTime.value = false
|
|
|
|
|
|
recorderTime.value = 0
|
2024-09-06 06:38:07 +00:00
|
|
|
|
if (mediaRecorder.value) {
|
|
|
|
|
|
mediaRecorderStatus.value = true
|
2024-09-12 03:02:03 +00:00
|
|
|
|
mediaRecorder.value.stop(
|
|
|
|
|
|
(blob: Blob, duration: number) => {
|
|
|
|
|
|
// 测试blob是否能正常播放
|
|
|
|
|
|
// const link = document.createElement('a')
|
|
|
|
|
|
// link.href = window.URL.createObjectURL(blob)
|
|
|
|
|
|
// link.download = 'abc.mp3'
|
|
|
|
|
|
// link.click()
|
|
|
|
|
|
uploadRecording(blob) // 上传录音文件
|
|
|
|
|
|
},
|
|
|
|
|
|
(err: any) => {
|
|
|
|
|
|
console.error('录音失败:', err)
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
2024-09-06 06:38:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 上传录音文件
|
|
|
|
|
|
const uploadRecording = async (audioBlob: Blob) => {
|
|
|
|
|
|
try {
|
2024-09-14 03:17:14 +00:00
|
|
|
|
recorderLoading.value = true
|
2024-09-06 06:38:07 +00:00
|
|
|
|
const formData = new FormData()
|
|
|
|
|
|
formData.append('file', audioBlob, 'recording.mp3')
|
2024-09-12 03:02:03 +00:00
|
|
|
|
applicationApi.postSpeechToText(props.data.id as string, formData, loading).then((response) => {
|
|
|
|
|
|
console.log('上传成功:', response.data)
|
2024-09-14 03:17:14 +00:00
|
|
|
|
recorderLoading.value = false
|
|
|
|
|
|
mediaRecorder.value.close()
|
|
|
|
|
|
inputValue.value = typeof response.data === 'string' ? response.data : ''
|
2024-09-12 03:02:03 +00:00
|
|
|
|
// chatMessage(null, res.data)
|
|
|
|
|
|
})
|
2024-09-06 06:38:07 +00:00
|
|
|
|
} catch (error) {
|
2024-09-14 03:17:14 +00:00
|
|
|
|
recorderLoading.value = false
|
2024-09-06 06:38:07 +00:00
|
|
|
|
console.error('上传失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-14 03:17:14 +00:00
|
|
|
|
const handleTimeChange = () => {
|
|
|
|
|
|
startRecorderTime.value = true
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
if (recorderTime.value === 60) {
|
|
|
|
|
|
recorderTime.value = 0
|
|
|
|
|
|
stopRecording()
|
|
|
|
|
|
startRecorderTime.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!startRecorderTime.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
recorderTime.value++
|
|
|
|
|
|
handleTimeChange()
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-09 11:10:16 +00:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
handleInputFieldList()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2024-05-20 09:50:14 +00:00
|
|
|
|
function setScrollBottom() {
|
|
|
|
|
|
// 将滚动条滚动到最下面
|
|
|
|
|
|
scrollDiv.value.setScrollTop(getMaxHeight())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-26 08:26:18 +00:00
|
|
|
|
watch(
|
|
|
|
|
|
chatList,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
handleScroll()
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true, immediate: true }
|
|
|
|
|
|
)
|
2024-05-20 09:50:14 +00:00
|
|
|
|
|
2024-06-13 10:28:44 +00:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
setTimeout(() => {
|
2024-08-05 08:40:35 +00:00
|
|
|
|
if (quickInputRef.value && mode === 'embed') {
|
2024-07-01 01:45:59 +00:00
|
|
|
|
quickInputRef.value.textarea.style.height = '0'
|
|
|
|
|
|
}
|
2024-07-22 10:52:56 +00:00
|
|
|
|
}, 1800)
|
2024-06-13 10:28:44 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
2024-05-20 09:50:14 +00:00
|
|
|
|
defineExpose({
|
|
|
|
|
|
setScrollBottom
|
|
|
|
|
|
})
|
2023-11-17 03:36:16 +00:00
|
|
|
|
</script>
|
2023-11-20 10:56:31 +00:00
|
|
|
|
<style lang="scss" scoped>
|
2023-12-06 09:30:46 +00:00
|
|
|
|
.ai-chat {
|
2023-11-21 10:32:51 +00:00
|
|
|
|
--padding-left: 40px;
|
2023-11-21 03:43:47 +00:00
|
|
|
|
height: 100%;
|
2023-11-20 10:56:31 +00:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2023-11-21 02:56:25 +00:00
|
|
|
|
box-sizing: border-box;
|
2023-11-21 10:32:51 +00:00
|
|
|
|
position: relative;
|
2023-11-28 09:39:35 +00:00
|
|
|
|
color: var(--app-text-color);
|
2024-03-25 09:33:32 +00:00
|
|
|
|
box-sizing: border-box;
|
2024-06-13 09:22:43 +00:00
|
|
|
|
|
2023-11-20 10:56:31 +00:00
|
|
|
|
&__content {
|
2023-12-01 11:17:58 +00:00
|
|
|
|
padding-top: 0;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
2023-11-21 03:43:47 +00:00
|
|
|
|
.avatar {
|
|
|
|
|
|
float: left;
|
|
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-21 03:43:47 +00:00
|
|
|
|
.content {
|
2023-11-21 10:32:51 +00:00
|
|
|
|
padding-left: var(--padding-left);
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-12-01 10:21:49 +00:00
|
|
|
|
:deep(ol) {
|
2023-12-01 11:17:58 +00:00
|
|
|
|
margin-left: 16px !important;
|
2023-12-01 10:21:49 +00:00
|
|
|
|
}
|
2023-11-21 03:43:47 +00:00
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-22 03:05:06 +00:00
|
|
|
|
.text {
|
|
|
|
|
|
padding: 6px 0;
|
|
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-20 10:56:31 +00:00
|
|
|
|
.problem-button {
|
2023-11-21 10:32:51 +00:00
|
|
|
|
width: 100%;
|
2023-11-20 10:56:31 +00:00
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: var(--app-layout-bg-color);
|
|
|
|
|
|
height: 46px;
|
2023-11-22 09:04:47 +00:00
|
|
|
|
padding: 0 12px;
|
|
|
|
|
|
line-height: 46px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
color: var(--el-text-color-regular);
|
|
|
|
|
|
-webkit-line-clamp: 1;
|
|
|
|
|
|
word-break: break-all;
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-20 10:56:31 +00:00
|
|
|
|
&:hover {
|
|
|
|
|
|
background: var(--el-color-primary-light-9);
|
|
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-12-07 11:28:38 +00:00
|
|
|
|
&.disabled {
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background: var(--app-layout-bg-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-20 10:56:31 +00:00
|
|
|
|
:deep(.el-icon) {
|
|
|
|
|
|
color: var(--el-color-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-21 10:32:51 +00:00
|
|
|
|
&__operate {
|
|
|
|
|
|
background: #f3f7f9;
|
2024-06-13 03:35:17 +00:00
|
|
|
|
position: relative;
|
2023-11-21 10:32:51 +00:00
|
|
|
|
width: 100%;
|
2023-11-21 02:56:25 +00:00
|
|
|
|
box-sizing: border-box;
|
2023-11-21 10:32:51 +00:00
|
|
|
|
z-index: 10;
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-21 10:32:51 +00:00
|
|
|
|
&:before {
|
|
|
|
|
|
background: linear-gradient(0deg, #f3f7f9 0%, rgba(243, 247, 249, 0) 100%);
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
top: -16px;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
height: 16px;
|
2023-11-20 10:56:31 +00:00
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-21 10:32:51 +00:00
|
|
|
|
.operate-textarea {
|
|
|
|
|
|
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
border: 1px solid #ffffff;
|
|
|
|
|
|
box-sizing: border-box;
|
2023-11-21 02:56:25 +00:00
|
|
|
|
|
2023-11-21 10:32:51 +00:00
|
|
|
|
&:has(.el-textarea__inner:focus) {
|
|
|
|
|
|
border: 1px solid var(--el-color-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-textarea__inner) {
|
|
|
|
|
|
border-radius: 8px !important;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
resize: none;
|
|
|
|
|
|
padding: 12px 16px;
|
2024-03-25 09:33:32 +00:00
|
|
|
|
box-sizing: border-box;
|
2023-11-21 10:32:51 +00:00
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-21 10:32:51 +00:00
|
|
|
|
.operate {
|
2023-11-24 11:02:52 +00:00
|
|
|
|
padding: 6px 10px;
|
2024-09-13 07:47:05 +00:00
|
|
|
|
.el-icon {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-21 10:32:51 +00:00
|
|
|
|
.sent-button {
|
2023-11-24 11:02:52 +00:00
|
|
|
|
max-height: none;
|
2023-11-21 10:32:51 +00:00
|
|
|
|
.el-icon {
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
}
|
2023-11-20 10:56:31 +00:00
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-24 11:02:52 +00:00
|
|
|
|
:deep(.el-loading-spinner) {
|
|
|
|
|
|
margin-top: -15px;
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-24 11:02:52 +00:00
|
|
|
|
.circular {
|
|
|
|
|
|
width: 31px;
|
|
|
|
|
|
height: 31px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2023-11-20 10:56:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2023-11-21 10:32:51 +00:00
|
|
|
|
.dialog-card {
|
|
|
|
|
|
border: none;
|
2024-02-23 09:55:55 +00:00
|
|
|
|
border-radius: 8px;
|
2024-09-13 09:25:47 +00:00
|
|
|
|
box-sizing: border-box;
|
2023-11-21 10:32:51 +00:00
|
|
|
|
}
|
2023-11-20 10:56:31 +00:00
|
|
|
|
}
|
2024-09-09 11:10:16 +00:00
|
|
|
|
|
2024-06-13 03:35:17 +00:00
|
|
|
|
.chat-width {
|
|
|
|
|
|
max-width: var(--app-chat-width, 860px);
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
}
|
2023-11-20 10:56:31 +00:00
|
|
|
|
</style>
|