This commit is contained in:
liqiang-fit2cloud 2024-11-19 09:24:36 +08:00
commit df5478a909
13 changed files with 490 additions and 134 deletions

View File

@ -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):

View File

@ -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()

View File

@ -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
)

View File

@ -41,6 +41,7 @@ interface chatType {
vote_status: string
status?: number,
execution_details: any[]
upload_meta?: any[]
}
export class ChatRecordManage {

View File

@ -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[]>([])

View File

@ -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>

View File

@ -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>

View File

@ -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)

View File

@ -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'
// })
// ]
// )
// ])
// }
// }
}

View File

@ -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);
}

View File

@ -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`

View File

@ -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>

View File

@ -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"