Merge branch 'main' of https://github.com/maxkb-dev/maxkb
This commit is contained in:
commit
df5478a909
@ -13,11 +13,13 @@ from setting.models_provider.base_model_provider import ModelProvideInfo, ModelT
|
||||
ModelInfoManage
|
||||
from setting.models_provider.impl.aliyun_bai_lian_model_provider.credential.embedding import \
|
||||
AliyunBaiLianEmbeddingCredential
|
||||
from setting.models_provider.impl.aliyun_bai_lian_model_provider.credential.llm import BaiLianLLMModelCredential
|
||||
from setting.models_provider.impl.aliyun_bai_lian_model_provider.credential.reranker import \
|
||||
AliyunBaiLianRerankerCredential
|
||||
from setting.models_provider.impl.aliyun_bai_lian_model_provider.credential.stt import AliyunBaiLianSTTModelCredential
|
||||
from setting.models_provider.impl.aliyun_bai_lian_model_provider.credential.tts import AliyunBaiLianTTSModelCredential
|
||||
from setting.models_provider.impl.aliyun_bai_lian_model_provider.model.embedding import AliyunBaiLianEmbedding
|
||||
from setting.models_provider.impl.aliyun_bai_lian_model_provider.model.llm import BaiLianChatModel
|
||||
from setting.models_provider.impl.aliyun_bai_lian_model_provider.model.reranker import AliyunBaiLianReranker
|
||||
from setting.models_provider.impl.aliyun_bai_lian_model_provider.model.stt import AliyunBaiLianSpeechToText
|
||||
from setting.models_provider.impl.aliyun_bai_lian_model_provider.model.tts import AliyunBaiLianTextToSpeech
|
||||
@ -27,6 +29,7 @@ aliyun_bai_lian_model_credential = AliyunBaiLianRerankerCredential()
|
||||
aliyun_bai_lian_tts_model_credential = AliyunBaiLianTTSModelCredential()
|
||||
aliyun_bai_lian_stt_model_credential = AliyunBaiLianSTTModelCredential()
|
||||
aliyun_bai_lian_embedding_model_credential = AliyunBaiLianEmbeddingCredential()
|
||||
aliyun_bai_lian_llm_model_credential = BaiLianLLMModelCredential()
|
||||
|
||||
model_info_list = [ModelInfo('gte-rerank',
|
||||
'阿里巴巴通义实验室开发的GTE-Rerank文本排序系列模型,开发者可以通过LlamaIndex框架进行集成高质量文本检索、排序。',
|
||||
@ -41,11 +44,17 @@ model_info_list = [ModelInfo('gte-rerank',
|
||||
'通用文本向量,是通义实验室基于LLM底座的多语言文本统一向量模型,面向全球多个主流语种,提供高水准的向量服务,帮助开发者将文本数据快速转换为高质量的向量数据。',
|
||||
ModelTypeConst.EMBEDDING, aliyun_bai_lian_embedding_model_credential,
|
||||
AliyunBaiLianEmbedding),
|
||||
ModelInfo('qwen-turbo', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,
|
||||
BaiLianChatModel),
|
||||
ModelInfo('qwen-plus', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,
|
||||
BaiLianChatModel),
|
||||
ModelInfo('qwen-max', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,
|
||||
BaiLianChatModel)
|
||||
]
|
||||
|
||||
model_info_manage = ModelInfoManage.builder().append_model_info_list(model_info_list).append_default_model_info(
|
||||
model_info_list[1]).append_default_model_info(model_info_list[2]).append_default_model_info(
|
||||
model_info_list[3]).build()
|
||||
model_info_list[3]).append_default_model_info(model_info_list[4]).build()
|
||||
|
||||
|
||||
class AliyunBaiLianModelProvider(IModelProvider):
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
# coding=utf-8
|
||||
from typing import Dict
|
||||
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
from common import forms
|
||||
from common.exception.app_exception import AppApiException
|
||||
from common.forms import BaseForm, TooltipLabel
|
||||
from setting.models_provider.base_model_provider import BaseModelCredential, ValidCode
|
||||
|
||||
|
||||
class BaiLianLLMModelParams(BaseForm):
|
||||
temperature = forms.SliderField(TooltipLabel('温度', '较高的数值会使输出更加随机,而较低的数值会使其更加集中和确定'),
|
||||
required=True, default_value=0.7,
|
||||
_min=0.1,
|
||||
_max=1.0,
|
||||
_step=0.01,
|
||||
precision=2)
|
||||
|
||||
max_tokens = forms.SliderField(
|
||||
TooltipLabel('输出最大Tokens', '指定模型可生成的最大token个数'),
|
||||
required=True, default_value=800,
|
||||
_min=1,
|
||||
_max=100000,
|
||||
_step=1,
|
||||
precision=0)
|
||||
|
||||
|
||||
class BaiLianLLMModelCredential(BaseForm, BaseModelCredential):
|
||||
|
||||
def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], provider,
|
||||
raise_exception=False):
|
||||
model_type_list = provider.get_model_type_list()
|
||||
if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):
|
||||
raise AppApiException(ValidCode.valid_error.value, f'{model_type} 模型类型不支持')
|
||||
|
||||
for key in ['api_base', 'api_key']:
|
||||
if key not in model_credential:
|
||||
if raise_exception:
|
||||
raise AppApiException(ValidCode.valid_error.value, f'{key} 字段为必填字段')
|
||||
else:
|
||||
return False
|
||||
try:
|
||||
model = provider.get_model(model_type, model_name, model_credential)
|
||||
model.invoke([HumanMessage(content='你好')])
|
||||
except Exception as e:
|
||||
if isinstance(e, AppApiException):
|
||||
raise e
|
||||
if raise_exception:
|
||||
raise AppApiException(ValidCode.valid_error.value, f'校验失败,请检查参数是否正确: {str(e)}')
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
def encryption_dict(self, model: Dict[str, object]):
|
||||
return {**model, 'api_key': super().encryption(model.get('api_key', ''))}
|
||||
|
||||
api_base = forms.TextInputField('API 域名', required=True)
|
||||
api_key = forms.PasswordInputField('API Key', required=True)
|
||||
|
||||
def get_model_params_setting_form(self, model_name):
|
||||
return BaiLianLLMModelParams()
|
||||
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
from typing import List, Dict
|
||||
|
||||
from setting.models_provider.base_model_provider import MaxKBBaseModel
|
||||
from setting.models_provider.impl.base_chat_open_ai import BaseChatOpenAI
|
||||
|
||||
|
||||
class BaiLianChatModel(MaxKBBaseModel, BaseChatOpenAI):
|
||||
@staticmethod
|
||||
def is_cache_model():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
|
||||
optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)
|
||||
return BaiLianChatModel(
|
||||
model=model_name,
|
||||
openai_api_base=model_credential.get('api_base'),
|
||||
openai_api_key=model_credential.get('api_key'),
|
||||
**optional_params
|
||||
)
|
||||
@ -41,6 +41,7 @@ interface chatType {
|
||||
vote_status: string
|
||||
status?: number,
|
||||
execution_details: any[]
|
||||
upload_meta?: any[]
|
||||
}
|
||||
|
||||
export class ChatRecordManage {
|
||||
|
||||
@ -50,21 +50,48 @@
|
||||
<div class="card-never border-r-4">
|
||||
<h5 class="p-8-12">参数输入</h5>
|
||||
<div class="p-8-12 border-t-dashed lighter">
|
||||
<div>用户问题: {{ item.question || '-' }}</div>
|
||||
<div v-for="(f, i) in item.global_fields" :key="i">
|
||||
{{ f.label }}: {{ f.value }}
|
||||
<div class="mb-8">
|
||||
<span class="color-secondary">用户问题:</span>
|
||||
{{ item.question || '-' }}
|
||||
</div>
|
||||
<div v-for="(f, i) in item.global_fields" :key="i" class="mb-8">
|
||||
<span class="color-secondary">{{ f.label }}:</span> {{ f.value }}
|
||||
</div>
|
||||
<div v-if="item.document_list?.length > 0">
|
||||
上传的文档:
|
||||
<div v-for="(f, i) in item.document_list" :key="i">
|
||||
{{ f.name }}
|
||||
</div>
|
||||
<p class="mb-8 color-secondary">上传的文档:</p>
|
||||
|
||||
<el-space wrap>
|
||||
<template v-for="(f, i) in item.document_list" :key="i">
|
||||
{{ f.name }}
|
||||
<el-card
|
||||
shadow="never"
|
||||
style="--el-card-padding: 8px"
|
||||
class="file cursor"
|
||||
>
|
||||
<div class="flex align-center">
|
||||
<img :src="getImgUrl(f && f?.name)" alt="" width="24" />
|
||||
<div class="ml-4 ellipsis" :title="f && f?.name">
|
||||
{{ f && f?.name }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</el-space>
|
||||
</div>
|
||||
<div v-if="item.image_list?.length > 0">
|
||||
上传的图片:
|
||||
<div v-for="(f, i) in item.image_list" :key="i">
|
||||
{{ f.name }}
|
||||
</div>
|
||||
<p class="mb-8 color-secondary">上传的图片:</p>
|
||||
|
||||
<el-space wrap>
|
||||
<template v-for="(f, i) in item.image_list" :key="i">
|
||||
<el-image
|
||||
:src="f.url"
|
||||
alt=""
|
||||
fit="cover"
|
||||
style="width: 40px; height: 40px; display: block"
|
||||
class="border-r-4"
|
||||
/>
|
||||
</template>
|
||||
</el-space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -308,6 +335,7 @@ import ParagraphCard from './component/ParagraphCard.vue'
|
||||
import { arraySort } from '@/utils/utils'
|
||||
import { iconComponent } from '@/workflow/icons/utils'
|
||||
import { WorkflowType } from '@/enums/workflow'
|
||||
import { getImgUrl } from '@/utils/utils'
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const detail = ref<any[]>([])
|
||||
|
||||
@ -1,73 +1,127 @@
|
||||
<template>
|
||||
<div class="ai-chat__operate p-16-24">
|
||||
<slot name="operateBefore" />
|
||||
<div class="operate-textarea flex">
|
||||
<el-input
|
||||
ref="quickInputRef"
|
||||
v-model="inputValue"
|
||||
:placeholder="
|
||||
startRecorderTime
|
||||
? '说话中...'
|
||||
: recorderLoading
|
||||
? '转文字中...'
|
||||
: '请输入问题,Ctrl+Enter 换行,Enter发送'
|
||||
"
|
||||
:autosize="{ minRows: 1, maxRows: isMobile ? 4 : 10 }"
|
||||
type="textarea"
|
||||
:maxlength="100000"
|
||||
@keydown.enter="sendChatHandle($event)"
|
||||
/>
|
||||
|
||||
<div class="operate flex align-center">
|
||||
<span v-if="props.applicationDetails.file_upload_enable" class="flex align-center">
|
||||
<!-- accept="image/jpeg, image/png, image/gif"-->
|
||||
<el-upload
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:accept="[...imageExtensions, ...documentExtensions].map((ext) => '.' + ext).join(',')"
|
||||
:on-change="(file: any, fileList: any) => uploadFile(file, fileList)"
|
||||
>
|
||||
<el-button text>
|
||||
<el-icon><Paperclip /></el-icon>
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-divider direction="vertical" />
|
||||
</span>
|
||||
<span v-if="props.applicationDetails.stt_model_enable" class="flex align-center">
|
||||
<el-button text v-if="mediaRecorderStatus" @click="startRecording">
|
||||
<el-icon>
|
||||
<Microphone />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<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">
|
||||
<AppIcon iconName="app-video-stop"></AppIcon>
|
||||
</el-button>
|
||||
</div>
|
||||
<el-divider v-if="!startRecorderTime && !recorderLoading" direction="vertical" />
|
||||
</span>
|
||||
|
||||
<el-button
|
||||
v-if="!startRecorderTime && !recorderLoading"
|
||||
text
|
||||
class="sent-button"
|
||||
:disabled="isDisabledChart || loading"
|
||||
@click="sendChatHandle"
|
||||
<div class="operate-textarea">
|
||||
<el-scrollbar max-height="136">
|
||||
<div
|
||||
class="p-8-12"
|
||||
v-loading="localLoading"
|
||||
v-if="uploadDocumentList.length || uploadImageList.length"
|
||||
>
|
||||
<img v-show="isDisabledChart || loading" src="@/assets/icon_send.svg" alt="" />
|
||||
<SendIcon v-show="!isDisabledChart && !loading" />
|
||||
</el-button>
|
||||
<el-space wrap>
|
||||
<template v-for="(item, index) in uploadDocumentList" :key="index">
|
||||
<el-card shadow="never" style="--el-card-padding: 8px" class="file cursor">
|
||||
<div
|
||||
class="flex align-center"
|
||||
@mouseenter.stop="mouseenter(item)"
|
||||
@mouseleave.stop="mouseleave()"
|
||||
>
|
||||
<div
|
||||
@click="deleteFile(index, 'document')"
|
||||
class="delete-icon color-secondary"
|
||||
v-if="showDelete === item.url"
|
||||
>
|
||||
<el-icon><CircleCloseFilled /></el-icon>
|
||||
</div>
|
||||
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
|
||||
<div class="ml-4 ellipsis" :title="item && item?.name">
|
||||
{{ item && item?.name }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
<template v-for="(item, index) in uploadImageList" :key="index">
|
||||
<div
|
||||
class="file cursor border border-r-4"
|
||||
v-if="item.url"
|
||||
@mouseenter.stop="mouseenter(item)"
|
||||
@mouseleave.stop="mouseleave()"
|
||||
>
|
||||
<div
|
||||
@click="deleteFile(index, 'image')"
|
||||
class="delete-icon color-secondary"
|
||||
v-if="showDelete === item.url"
|
||||
>
|
||||
<el-icon><CircleCloseFilled /></el-icon>
|
||||
</div>
|
||||
<el-image
|
||||
:src="item.url"
|
||||
alt=""
|
||||
fit="cover"
|
||||
style="width: 40px; height: 40px; display: block"
|
||||
class="border-r-4"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-space>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="flex">
|
||||
<el-input
|
||||
ref="quickInputRef"
|
||||
v-model="inputValue"
|
||||
:placeholder="
|
||||
startRecorderTime
|
||||
? '说话中...'
|
||||
: recorderLoading
|
||||
? '转文字中...'
|
||||
: '请输入问题,Ctrl+Enter 换行,Enter发送'
|
||||
"
|
||||
:autosize="{ minRows: 1, maxRows: isMobile ? 4 : 10 }"
|
||||
type="textarea"
|
||||
:maxlength="100000"
|
||||
@keydown.enter="sendChatHandle($event)"
|
||||
/>
|
||||
|
||||
<div class="operate flex align-center">
|
||||
<span v-if="props.applicationDetails.file_upload_enable" class="flex align-center">
|
||||
<!-- accept="image/jpeg, image/png, image/gif"-->
|
||||
<el-upload
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:accept="
|
||||
[...imageExtensions, ...documentExtensions].map((ext) => '.' + ext).join(',')
|
||||
"
|
||||
:on-change="(file: any, fileList: any) => uploadFile(file, fileList)"
|
||||
>
|
||||
<el-button text>
|
||||
<el-icon><Paperclip /></el-icon>
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-divider direction="vertical" />
|
||||
</span>
|
||||
<span v-if="props.applicationDetails.stt_model_enable" class="flex align-center">
|
||||
<el-button text v-if="mediaRecorderStatus" @click="startRecording">
|
||||
<el-icon>
|
||||
<Microphone />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<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">
|
||||
<AppIcon iconName="app-video-stop"></AppIcon>
|
||||
</el-button>
|
||||
</div>
|
||||
<el-divider v-if="!startRecorderTime && !recorderLoading" direction="vertical" />
|
||||
</span>
|
||||
|
||||
<el-button
|
||||
v-if="!startRecorderTime && !recorderLoading"
|
||||
text
|
||||
class="sent-button"
|
||||
:disabled="isDisabledChart || loading"
|
||||
@click="sendChatHandle"
|
||||
>
|
||||
<img v-show="isDisabledChart || loading" src="@/assets/icon_send.svg" alt="" />
|
||||
<SendIcon v-show="!isDisabledChart && !loading" />
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-center"
|
||||
v-if="applicationDetails.disclaimer"
|
||||
style="margin-top: 8px"
|
||||
>
|
||||
<div class="text-center" v-if="applicationDetails.disclaimer" style="margin-top: 8px">
|
||||
<el-text type="info" v-if="applicationDetails.disclaimer" style="font-size: 12px">
|
||||
<auto-tooltip :content="applicationDetails.disclaimer_value">
|
||||
{{ applicationDetails.disclaimer_value }}
|
||||
@ -83,7 +137,9 @@ import applicationApi from '@/api/application'
|
||||
import { MsgAlert } from '@/utils/message'
|
||||
import { type chatType } from '@/api/type/application'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getImgUrl } from '@/utils/utils'
|
||||
import 'recorder-core/src/engine/mp3'
|
||||
|
||||
import 'recorder-core/src/engine/mp3-engine'
|
||||
import { MsgWarning } from '@/utils/message'
|
||||
const route = useRoute()
|
||||
@ -130,7 +186,6 @@ const localLoading = computed({
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp']
|
||||
const documentExtensions = ['pdf', 'docx', 'txt', 'xls', 'xlsx', 'md', 'html', 'csv']
|
||||
const videoExtensions = ['mp4', 'avi', 'mov', 'mkv', 'flv']
|
||||
@ -209,6 +264,8 @@ const inputValue = ref<string>('')
|
||||
const uploadImageList = ref<Array<any>>([])
|
||||
const uploadDocumentList = ref<Array<any>>([])
|
||||
const mediaRecorderStatus = ref(true)
|
||||
const showDelete = ref('')
|
||||
|
||||
// 定义响应式引用
|
||||
const mediaRecorder = ref<any>(null)
|
||||
const isDisabledChart = computed(
|
||||
@ -346,6 +403,21 @@ function sendChatHandle(event: any) {
|
||||
inputValue.value += '\n'
|
||||
}
|
||||
}
|
||||
|
||||
function deleteFile(index: number, val: string) {
|
||||
if (val === 'image') {
|
||||
uploadImageList.value.splice(index, 1)
|
||||
} else if (val === 'document') {
|
||||
uploadDocumentList.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
function mouseenter(row: any) {
|
||||
showDelete.value = row.url
|
||||
}
|
||||
function mouseleave() {
|
||||
showDelete.value = ''
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
if (quickInputRef.value && mode === 'embed') {
|
||||
@ -356,4 +428,14 @@ onMounted(() => {
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
@import '../../index.scss';
|
||||
.file {
|
||||
position: relative;
|
||||
overflow: inherit;
|
||||
.delete-icon {
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -15,6 +15,39 @@
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="text break-all pre-wrap">
|
||||
<div class="mb-8" v-if="document_list.length">
|
||||
<el-space wrap>
|
||||
<template v-for="(item, index) in document_list" :key="index">
|
||||
<el-card shadow="never" style="--el-card-padding: 8px" class="file cursor">
|
||||
<div class="flex align-center">
|
||||
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
|
||||
<div class="ml-4 ellipsis" :title="item && item?.name">
|
||||
{{ item && item?.name }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</el-space>
|
||||
</div>
|
||||
<div class="mb-8" v-if="image_list.length">
|
||||
<el-space wrap>
|
||||
<template v-for="(item, index) in image_list" :key="index">
|
||||
<div class="file cursor border-r-4" v-if="item.url">
|
||||
<el-image
|
||||
:src="item.url"
|
||||
:zoom-rate="1.2"
|
||||
:max-scale="7"
|
||||
:min-scale="0.2"
|
||||
:preview-src-list="getAttrsArray(image_list, 'url')"
|
||||
alt=""
|
||||
fit="cover"
|
||||
style="width: 170px; height: 170px; display: block"
|
||||
class="border-r-4"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-space>
|
||||
</div>
|
||||
{{ chatRecord.problem_text }}
|
||||
</div>
|
||||
</div>
|
||||
@ -22,13 +55,33 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { type chatType } from '@/api/type/application'
|
||||
import { onMounted } from 'vue'
|
||||
import { getImgUrl, getAttrsArray } from '@/utils/utils'
|
||||
import { onMounted, computed } from 'vue'
|
||||
const props = defineProps<{
|
||||
application: any
|
||||
chatRecord: chatType
|
||||
}>()
|
||||
const document_list = computed(() => {
|
||||
if (props.chatRecord?.upload_meta) {
|
||||
return props.chatRecord.upload_meta?.document_list || []
|
||||
} else if (props.chatRecord.execution_details?.length > 0) {
|
||||
return props.chatRecord.execution_details[0]?.document_list || []
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
})
|
||||
const image_list = computed(() => {
|
||||
if (props.chatRecord?.upload_meta) {
|
||||
return props.chatRecord.upload_meta?.image_list || []
|
||||
} else if (props.chatRecord.execution_details?.length > 0) {
|
||||
return props.chatRecord.execution_details[0]?.image_list || []
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
console.log(props.chatRecord.execution_details)
|
||||
if (props.chatRecord.execution_details?.length > 0) {
|
||||
props.chatRecord.execution_details[0].image_list?.forEach((image: any) => {
|
||||
console.log('image', image.name, image.url)
|
||||
@ -36,5 +89,4 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -39,6 +39,7 @@
|
||||
:type="type"
|
||||
:send-message="sendMessage"
|
||||
:open-chat-id="openChatId"
|
||||
:chat-management="ChatManagement"
|
||||
v-model:chat-id="chartOpenId"
|
||||
v-model:loading="loading"
|
||||
v-if="type !== 'log'"
|
||||
@ -286,7 +287,15 @@ function chatMessage(chat?: any, problem?: string, re_chat?: boolean, other_para
|
||||
record_id: '',
|
||||
chat_id: '',
|
||||
vote_status: '-1',
|
||||
status: undefined
|
||||
status: undefined,
|
||||
upload_meta: {
|
||||
image_list:
|
||||
other_params_data && other_params_data.image_list ? other_params_data.image_list : [],
|
||||
document_list:
|
||||
other_params_data && other_params_data.document_list
|
||||
? other_params_data.document_list
|
||||
: []
|
||||
}
|
||||
})
|
||||
chatList.value.push(chat)
|
||||
ChatManagement.addChatRecord(chat, 50, loading)
|
||||
|
||||
@ -1249,26 +1249,63 @@ export const iconMap: any = {
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
'app-retract': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
style: { height: '100%', width: '100%' },
|
||||
viewBox: '0 0 16 16',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M5.44661 0.747985C5.55509 0.639506 5.73097 0.639506 5.83945 0.747985L8.00004 2.90858L10.1606 0.748004C10.2691 0.639525 10.445 0.639525 10.5534 0.748004L11.1034 1.29798C11.2119 1.40645 11.2119 1.58233 11.1034 1.69081L8.7488 4.04544L8.74644 4.04782L8.19647 4.59779C8.16892 4.62534 8.13703 4.64589 8.10299 4.65945C8.003 4.6993 7.88453 4.67875 7.80359 4.59781L7.25362 4.04784L7.25003 4.04419L4.89664 1.69079C4.78816 1.58232 4.78816 1.40644 4.89664 1.29796L5.44661 0.747985Z',
|
||||
fill: 'currentColor'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M1.99999 5.82774C1.63181 5.82774 1.33333 6.12622 1.33333 6.49441V9.16107C1.33333 9.52926 1.63181 9.82774 2 9.82774H14C14.3682 9.82774 14.6667 9.52926 14.6667 9.16107V6.49441C14.6667 6.12622 14.3682 5.82774 14 5.82774H1.99999ZM13.3333 7.16108V8.49441H2.66666V7.16108H13.3333Z',
|
||||
fill: 'currentColor'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M10.1605 14.9075C10.269 15.016 10.4449 15.016 10.5534 14.9075L11.1033 14.3575C11.2118 14.249 11.2118 14.0732 11.1033 13.9647L8.75 11.6113L8.74637 11.6076L8.1964 11.0577C8.11546 10.9767 7.99699 10.9562 7.897 10.996C7.86296 11.0096 7.83107 11.0301 7.80352 11.0577L7.25354 11.6077L7.25117 11.6101L4.89657 13.9647C4.78809 14.0731 4.78809 14.249 4.89657 14.3575L5.44654 14.9075C5.55502 15.016 5.7309 15.016 5.83938 14.9075L7.99995 12.7469L10.1605 14.9075Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
'app-extend': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
style: { height: '100%', width: '100%' },
|
||||
viewBox: '0 0 16 16',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M10.5534 5.07974C10.4449 5.18822 10.269 5.18822 10.1605 5.07974L7.99992 2.91915L5.83935 5.07972C5.73087 5.1882 5.555 5.1882 5.44652 5.07972L4.89654 4.52975C4.78807 4.42127 4.78807 4.24539 4.89654 4.13691L7.25117 1.78229L7.25352 1.77991L7.80349 1.22994C7.83019 1.20324 7.86098 1.18311 7.89384 1.16955C7.99448 1.12801 8.11459 1.14813 8.19638 1.22992L8.74635 1.77989L8.74998 1.78359L11.1033 4.13693C11.2118 4.24541 11.2118 4.42129 11.1033 4.52977L10.5534 5.07974Z',
|
||||
fill: 'currentColor'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M5.83943 10.9202C5.73095 10.8118 5.55507 10.8118 5.44659 10.9202L4.89662 11.4702C4.78814 11.5787 4.78814 11.7546 4.89662 11.863L7.24997 14.2164L7.25359 14.2201L7.80357 14.7701C7.8862 14.8527 8.00795 14.8724 8.10922 14.8291C8.14091 14.8156 8.17059 14.7959 8.19645 14.77L8.74642 14.2201L8.74873 14.2177L11.1034 11.8631C11.2119 11.7546 11.2119 11.5787 11.1034 11.4702L10.5534 10.9202C10.4449 10.8118 10.2691 10.8118 10.1606 10.9202L8.00002 13.0808L5.83943 10.9202Z',
|
||||
fill: 'currentColor'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M2.00004 6C1.63185 6 1.33337 6.29848 1.33337 6.66667V9.33333C1.33337 9.70152 1.63185 10 2.00004 10H14C14.3682 10 14.6667 9.70152 14.6667 9.33333V6.66667C14.6667 6.29848 14.3682 6 14 6H2.00004ZM13.3334 7.33333V8.66667H2.66671V7.33333H13.3334Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
}
|
||||
// 'app-history-outlined': {
|
||||
// iconReader: () => {
|
||||
// return h('i', [
|
||||
// h(
|
||||
// 'svg',
|
||||
// {
|
||||
// style: { height: '100%', width: '100%' },
|
||||
// viewBox: '0 0 1024 1024',
|
||||
// version: '1.1',
|
||||
// xmlns: 'http://www.w3.org/2000/svg'
|
||||
// },
|
||||
// [
|
||||
// h('path', {
|
||||
// d: 'M955.733333 512c0 235.648-191.018667 426.666667-426.666666 426.666667a425.898667 425.898667 0 0 1-334.677334-162.005334l67.797334-51.84a341.333333 341.333333 0 1 0-69.717334-269.653333h30.08c18.176-0.042667 29.013333 20.181333 18.944 35.328L170.24 597.333333a22.741333 22.741333 0 0 1-37.888 0l-71.253333-106.88a22.741333 22.741333 0 0 1 18.944-35.413333h26.112C133.973333 246.4 312.746667 85.333333 529.066667 85.333333c235.648 0 426.666667 191.018667 426.666666 426.666667z" p-id="16742"></path><path d="M554.666667 497.792V364.074667A22.741333 22.741333 0 0 0 531.925333 341.333333h-39.850666a22.741333 22.741333 0 0 0-22.741334 22.741334v196.266666c0 12.586667 10.197333 22.784 22.741334 22.784H674.133333a22.741333 22.741333 0 0 0 22.741334-22.784V520.533333a22.741333 22.741333 0 0 0-22.741334-22.741333H554.666667z',
|
||||
// fill: 'currentColor'
|
||||
// })
|
||||
// ]
|
||||
// )
|
||||
// ])
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@ -8,16 +8,14 @@
|
||||
class="h-full"
|
||||
style="padding: 24px 0"
|
||||
>
|
||||
<InfiniteScroll
|
||||
:size="recordList.length"
|
||||
:total="paginationConfig.total"
|
||||
:page_size="paginationConfig.page_size"
|
||||
v-model:current_page="paginationConfig.current_page"
|
||||
@load="getChatRecord"
|
||||
:loading="loading"
|
||||
<AiChat
|
||||
ref="AiChatRef"
|
||||
:application-details="application"
|
||||
type="log"
|
||||
:record="recordList"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<AiChat :application-details="application" :record="recordList" type="log"></AiChat>
|
||||
</InfiniteScroll>
|
||||
</AiChat>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div>
|
||||
@ -29,11 +27,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { ref, reactive, watch, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import logApi from '@/api/log'
|
||||
import { type chatType } from '@/api/type/application'
|
||||
import { type ApplicationFormType } from '@/api/type/application'
|
||||
import { type ApplicationFormType, type chatType } from '@/api/type/application'
|
||||
import useStore from '@/stores'
|
||||
const AiChatRef = ref()
|
||||
const { log } = useStore()
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/**
|
||||
@ -84,12 +83,21 @@ function closeHandle() {
|
||||
}
|
||||
|
||||
function getChatRecord() {
|
||||
if (props.chatId && visible.value) {
|
||||
logApi.getChatRecordLog(id as string, props.chatId, paginationConfig, loading).then((res) => {
|
||||
return log
|
||||
.asyncChatRecordLog(id as string, props.chatId, paginationConfig, loading)
|
||||
.then((res: any) => {
|
||||
paginationConfig.total = res.data.total
|
||||
recordList.value = [...recordList.value, ...res.data.records]
|
||||
const list = res.data.records
|
||||
recordList.value = [...list, ...recordList.value].sort((a, b) =>
|
||||
a.create_time.localeCompare(b.create_time)
|
||||
)
|
||||
if (paginationConfig.current_page === 1) {
|
||||
nextTick(() => {
|
||||
// 将滚动条滚动到最下面
|
||||
AiChatRef.value.setScrollBottom()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
@ -98,7 +106,9 @@ watch(
|
||||
recordList.value = []
|
||||
paginationConfig.total = 0
|
||||
paginationConfig.current_page = 1
|
||||
getChatRecord()
|
||||
if (props.chatId) {
|
||||
getChatRecord()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -110,8 +120,21 @@ watch(visible, (bool) => {
|
||||
}
|
||||
})
|
||||
|
||||
function handleScroll(event: any) {
|
||||
if (
|
||||
props.chatId !== 'new' &&
|
||||
event.scrollTop === 0 &&
|
||||
paginationConfig.total > recordList.value.length
|
||||
) {
|
||||
const history_height = event.dialogScrollbar.offsetHeight
|
||||
paginationConfig.current_page += 1
|
||||
getChatRecord().then(() => {
|
||||
event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
getChatRecord()
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
@ -125,11 +148,13 @@ defineExpose({
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chat-record-drawer {
|
||||
.el-drawer__body {
|
||||
background: var(--app-layout-bg-color);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-divider__text) {
|
||||
background: var(--app-layout-bg-color);
|
||||
}
|
||||
|
||||
@ -158,7 +158,12 @@ function redirectAuth(authType: string) {
|
||||
const redirectUrl = eval(`\`${config.redirectUrl}\``)
|
||||
let url
|
||||
if (authType === 'CAS') {
|
||||
url = `${config.ldpUri}?service=${encodeURIComponent(redirectUrl)}`
|
||||
url = config.ldpUri
|
||||
if (url.indexOf('?') !== -1) {
|
||||
url = `${config.ldpUri}&service=${encodeURIComponent(redirectUrl)}`
|
||||
} else {
|
||||
url = `${config.ldpUri}?service=${encodeURIComponent(redirectUrl)}`
|
||||
}
|
||||
}
|
||||
if (authType === 'OIDC') {
|
||||
url = `${config.authEndpoint}?client_id=${config.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=openid+profile+email`
|
||||
|
||||
@ -6,13 +6,24 @@
|
||||
<el-button link @click="zoomIn">
|
||||
<el-icon :size="16" title="放大"><ZoomIn /></el-icon>
|
||||
</el-button>
|
||||
|
||||
<el-divider direction="vertical" />
|
||||
<el-button link @click="fitView">
|
||||
<AppIcon iconName="app-fitview" title="适应"></AppIcon>
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button link @click="retract">
|
||||
<el-tooltip class="box-item" effect="dark" content="收起全部节点" placement="top">
|
||||
<AppIcon iconName="app-retract" title="收起全部节点"></AppIcon>
|
||||
</el-tooltip>
|
||||
</el-button>
|
||||
<el-button link @click="extend">
|
||||
<el-tooltip class="box-item" effect="dark" content="展开全部节点" placement="top">
|
||||
<AppIcon iconName="app-extend" title="展开全部节点"></AppIcon>
|
||||
</el-tooltip>
|
||||
</el-button>
|
||||
<el-button link @click="layout">
|
||||
<AppIcon iconName="app-beautify" title="美化"></AppIcon>
|
||||
<el-tooltip class="box-item" effect="dark" content="一键美化" placement="top">
|
||||
<AppIcon iconName="app-beautify" title="一键美化"></AppIcon>
|
||||
</el-tooltip>
|
||||
</el-button>
|
||||
</el-card>
|
||||
</template>
|
||||
@ -36,5 +47,15 @@ function fitView() {
|
||||
const layout = () => {
|
||||
props.lf?.extension.dagre.layout()
|
||||
}
|
||||
const retract = () => {
|
||||
props.lf?.graphModel.nodes.forEach((element: any) => {
|
||||
element.properties.showNode = false
|
||||
})
|
||||
}
|
||||
const extend = () => {
|
||||
props.lf?.graphModel.nodes.forEach((element: any) => {
|
||||
element.properties.showNode = true
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
||||
@ -39,15 +39,18 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="表单配置" @click.prevent>
|
||||
<div class="flex-between mb-16">
|
||||
<h5 class="lighter">{{ '接口传参' }}</h5>
|
||||
<el-button link type="primary" @click="openAddFormCollect()">
|
||||
<el-icon class="mr-4">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
添加
|
||||
</el-button>
|
||||
</div>
|
||||
<template #label>
|
||||
<div class="flex-between mb-16">
|
||||
<h5 class="lighter">{{ '表单配置' }}</h5>
|
||||
<el-button link type="primary" @click="openAddFormCollect()">
|
||||
<el-icon class="mr-4">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
添加
|
||||
</el-button>
|
||||
</div></template
|
||||
>
|
||||
|
||||
<el-table
|
||||
v-if="form_data.form_field_list.length > 0"
|
||||
:data="form_data.form_field_list"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user