初始化
This commit is contained in:
377
easyflow-ui-admin/app/src/views/ai/model/AddModelModal.vue
Normal file
377
easyflow-ui-admin/app/src/views/ai/model/AddModelModal.vue
Normal file
@@ -0,0 +1,377 @@
|
||||
<script setup lang="ts">
|
||||
import type { ModelAbilityItem } from '#/views/ai/model/modelUtils/model-ability';
|
||||
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElDialog,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElMessage,
|
||||
ElTag,
|
||||
} from 'element-plus';
|
||||
|
||||
import { api } from '#/api/request';
|
||||
import { $t } from '#/locales';
|
||||
import {
|
||||
getDefaultModelAbility,
|
||||
handleTagClick as handleTagClickUtil,
|
||||
syncTagSelectedStatus as syncTagSelectedStatusUtil,
|
||||
} from '#/views/ai/model/modelUtils/model-ability';
|
||||
import {
|
||||
generateFeaturesFromModelAbility,
|
||||
resetModelAbility,
|
||||
} from '#/views/ai/model/modelUtils/model-ability-utils';
|
||||
|
||||
interface FormData {
|
||||
modelType: string;
|
||||
title: string;
|
||||
modelName: string;
|
||||
groupName: string;
|
||||
providerId: string;
|
||||
provider: string;
|
||||
apiKey: string;
|
||||
endpoint: string;
|
||||
requestPath: string;
|
||||
supportThinking: boolean;
|
||||
supportTool: boolean;
|
||||
supportImage: boolean;
|
||||
supportAudio: boolean;
|
||||
supportFree: boolean;
|
||||
supportVideo: boolean;
|
||||
supportImageB64Only: boolean;
|
||||
supportToolMessage: boolean;
|
||||
options: {
|
||||
chatPath: string;
|
||||
embedPath: string;
|
||||
llmEndpoint: string;
|
||||
rerankPath: string;
|
||||
};
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
providerId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['reload']);
|
||||
const selectedProviderId = ref<string>(props.providerId ?? '');
|
||||
|
||||
// 监听 providerId 的变化
|
||||
watch(
|
||||
() => props.providerId,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
selectedProviderId.value = newVal;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const formDataRef = ref();
|
||||
const isAdd = ref(true);
|
||||
const dialogVisible = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive<FormData>({
|
||||
modelType: '',
|
||||
title: '',
|
||||
modelName: '',
|
||||
groupName: '',
|
||||
providerId: '',
|
||||
provider: '',
|
||||
apiKey: '',
|
||||
endpoint: '',
|
||||
requestPath: '',
|
||||
supportThinking: false,
|
||||
supportTool: false,
|
||||
supportImage: false,
|
||||
supportAudio: false,
|
||||
supportFree: false,
|
||||
supportVideo: false,
|
||||
supportImageB64Only: false,
|
||||
supportToolMessage: false,
|
||||
options: {
|
||||
llmEndpoint: '',
|
||||
chatPath: '',
|
||||
embedPath: '',
|
||||
rerankPath: '',
|
||||
},
|
||||
});
|
||||
|
||||
// 使用抽取的函数获取模型能力配置
|
||||
const modelAbility = ref<ModelAbilityItem[]>(getDefaultModelAbility());
|
||||
|
||||
/**
|
||||
* 同步标签选中状态与formData中的布尔字段
|
||||
*/
|
||||
const syncTagSelectedStatus = () => {
|
||||
syncTagSelectedStatusUtil(modelAbility.value, formData);
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理标签点击事件
|
||||
*/
|
||||
const handleTagClick = (item: ModelAbilityItem) => {
|
||||
// handleTagClickUtil(modelAbility.value, item, formData);
|
||||
handleTagClickUtil(item, formData);
|
||||
};
|
||||
|
||||
// 打开新增弹窗
|
||||
defineExpose({
|
||||
openAddDialog(modelType: string) {
|
||||
isAdd.value = true;
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.resetFields();
|
||||
}
|
||||
|
||||
// 重置表单数据
|
||||
Object.assign(formData, {
|
||||
id: '',
|
||||
modelType,
|
||||
title: '',
|
||||
modelName: '',
|
||||
groupName: '',
|
||||
provider: '',
|
||||
endPoint: '',
|
||||
providerId: '',
|
||||
supportThinking: false,
|
||||
supportTool: false,
|
||||
supportAudio: false,
|
||||
supportVideo: false,
|
||||
supportImage: false,
|
||||
supportImageB64Only: false,
|
||||
supportFree: false,
|
||||
supportToolMessage: true,
|
||||
options: {
|
||||
llmEndpoint: '',
|
||||
chatPath: '',
|
||||
embedPath: '',
|
||||
rerankPath: '',
|
||||
},
|
||||
});
|
||||
showMoreFields.value = false;
|
||||
// 重置标签状态
|
||||
resetModelAbility(modelAbility.value);
|
||||
syncTagSelectedStatus();
|
||||
dialogVisible.value = true;
|
||||
},
|
||||
|
||||
openEditDialog(item: any) {
|
||||
dialogVisible.value = true;
|
||||
isAdd.value = false;
|
||||
|
||||
// 填充表单数据
|
||||
Object.assign(formData, {
|
||||
id: item.id,
|
||||
modelType: item.modelType || '',
|
||||
title: item.title || '',
|
||||
modelName: item.modelName || '',
|
||||
groupName: item.groupName || '',
|
||||
provider: item.provider || '',
|
||||
endpoint: item.endpoint || '',
|
||||
requestPath: item.requestPath || '',
|
||||
supportThinking: item.supportThinking || false,
|
||||
supportAudio: item.supportAudio || false,
|
||||
supportImage: item.supportImage || false,
|
||||
supportImageB64Only: item.supportImageB64Only || false,
|
||||
supportVideo: item.supportVideo || false,
|
||||
supportTool: item.supportTool || false,
|
||||
supportFree: item.supportFree || false,
|
||||
supportToolMessage: item.supportToolMessage || false,
|
||||
options: {
|
||||
llmEndpoint: item.options?.llmEndpoint || '',
|
||||
chatPath: item.options?.chatPath || '',
|
||||
embedPath: item.options?.embedPath || '',
|
||||
rerankPath: item.options?.rerankPath || '',
|
||||
},
|
||||
});
|
||||
showMoreFields.value = false;
|
||||
// 同步标签状态
|
||||
syncTagSelectedStatus();
|
||||
},
|
||||
});
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const rules = {
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: $t('message.required'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
modelName: [
|
||||
{
|
||||
required: true,
|
||||
message: $t('message.required'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
groupName: [
|
||||
{
|
||||
required: true,
|
||||
message: $t('message.required'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
provider: [
|
||||
{
|
||||
required: true,
|
||||
message: $t('message.required'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const btnLoading = ref(false);
|
||||
|
||||
const save = async () => {
|
||||
btnLoading.value = true;
|
||||
|
||||
// 使用工具函数从模型能力生成features
|
||||
const features = generateFeaturesFromModelAbility(modelAbility.value);
|
||||
|
||||
try {
|
||||
await formDataRef.value.validate();
|
||||
const submitData = { ...formData, ...features };
|
||||
|
||||
if (isAdd.value) {
|
||||
submitData.providerId = selectedProviderId.value;
|
||||
const res = await api.post('/api/v1/model/save', submitData);
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message);
|
||||
emit('reload');
|
||||
closeDialog();
|
||||
} else {
|
||||
ElMessage.error(res.message || $t('ui.actionMessage.operationFailed'));
|
||||
}
|
||||
} else {
|
||||
const res = await api.post('/api/v1/model/update', submitData);
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message);
|
||||
emit('reload');
|
||||
closeDialog();
|
||||
} else {
|
||||
ElMessage.error(res.message || $t('ui.actionMessage.operationFailed'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Save model error:', error);
|
||||
ElMessage.error($t('ui.actionMessage.operationFailed'));
|
||||
} finally {
|
||||
btnLoading.value = false;
|
||||
}
|
||||
};
|
||||
const showMoreFields = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
draggable
|
||||
:title="isAdd ? $t('button.add') : $t('button.edit')"
|
||||
:before-close="closeDialog"
|
||||
:close-on-click-modal="false"
|
||||
align-center
|
||||
width="482"
|
||||
>
|
||||
<ElForm
|
||||
label-width="100px"
|
||||
ref="formDataRef"
|
||||
:model="formData"
|
||||
status-icon
|
||||
:rules="rules"
|
||||
>
|
||||
<ElFormItem prop="title" :label="$t('llm.title')">
|
||||
<ElInput v-model.trim="formData.title" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="modelName" :label="$t('llm.llmModel')">
|
||||
<ElInput v-model.trim="formData.modelName" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="groupName" :label="$t('llm.groupName')">
|
||||
<ElInput v-model.trim="formData.groupName" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="ability" :label="$t('llm.ability')">
|
||||
<div class="model-ability">
|
||||
<ElTag
|
||||
class="model-ability-tag"
|
||||
v-for="item in modelAbility"
|
||||
:key="item.value"
|
||||
:type="item.selected ? item.activeType : item.defaultType"
|
||||
@click="handleTagClick(item)"
|
||||
:class="{ 'tag-selected': item.selected }"
|
||||
>
|
||||
{{ item.label }}
|
||||
</ElTag>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem label=" " v-if="!showMoreFields">
|
||||
<ElButton @click="showMoreFields = !showMoreFields" type="primary">
|
||||
{{ showMoreFields ? $t('button.hide') : $t('button.more') }}
|
||||
</ElButton>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
prop="apiKey"
|
||||
:label="$t('llmProvider.apiKey')"
|
||||
v-show="showMoreFields"
|
||||
>
|
||||
<ElInput v-model.trim="formData.apiKey" />
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
prop="endpoint"
|
||||
:label="$t('llmProvider.endpoint')"
|
||||
v-show="showMoreFields"
|
||||
>
|
||||
<ElInput v-model.trim="formData.endpoint" />
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
prop="requestPath"
|
||||
:label="$t('llm.requestPath')"
|
||||
v-show="showMoreFields"
|
||||
>
|
||||
<ElInput v-model.trim="formData.requestPath" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<ElButton @click="closeDialog">
|
||||
{{ $t('button.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="save"
|
||||
:loading="btnLoading"
|
||||
:disabled="btnLoading"
|
||||
>
|
||||
{{ $t('button.save') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.model-ability {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.model-ability-tag {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tag-selected {
|
||||
font-weight: bold;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,197 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElDialog,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElMessage,
|
||||
ElOption,
|
||||
ElSelect,
|
||||
} from 'element-plus';
|
||||
|
||||
import { api } from '#/api/request';
|
||||
import UploadAvatar from '#/components/upload/UploadAvatar.vue';
|
||||
import { $t } from '#/locales';
|
||||
import providerList from '#/views/ai/model/modelUtils/providerList.json';
|
||||
|
||||
const emit = defineEmits(['reload']);
|
||||
|
||||
const formDataRef = ref();
|
||||
|
||||
defineExpose({
|
||||
openAddDialog() {
|
||||
formDataRef.value?.resetFields();
|
||||
dialogVisible.value = true;
|
||||
},
|
||||
openEditDialog(item: any) {
|
||||
dialogVisible.value = true;
|
||||
isAdd.value = false;
|
||||
Object.assign(formData, item);
|
||||
},
|
||||
});
|
||||
const providerOptions =
|
||||
ref<Array<{ label: string; options: any; value: string }>>(providerList);
|
||||
const isAdd = ref(true);
|
||||
const dialogVisible = ref(false);
|
||||
const formData = reactive({
|
||||
id: '',
|
||||
icon: '',
|
||||
providerName: '',
|
||||
providerType: '',
|
||||
apiKey: '',
|
||||
endpoint: '',
|
||||
chatPath: '',
|
||||
embedPath: '',
|
||||
rerankPath: '',
|
||||
});
|
||||
const closeDialog = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
const rules = {
|
||||
providerName: [
|
||||
{
|
||||
required: true,
|
||||
message: $t('message.required'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
providerType: [
|
||||
{
|
||||
required: true,
|
||||
message: $t('message.required'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
};
|
||||
const btnLoading = ref(false);
|
||||
const save = async () => {
|
||||
btnLoading.value = true;
|
||||
try {
|
||||
if (!isAdd.value) {
|
||||
api.post('/api/v1/modelProvider/update', formData).then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message);
|
||||
emit('reload');
|
||||
closeDialog();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
await formDataRef.value.validate();
|
||||
api.post('/api/v1/modelProvider/save', formData).then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message);
|
||||
emit('reload');
|
||||
closeDialog();
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
btnLoading.value = false;
|
||||
}
|
||||
};
|
||||
const handleChangeProvider = (val: string) => {
|
||||
const tempProvider = providerList.find((item) => item.value === val);
|
||||
if (!tempProvider) {
|
||||
return;
|
||||
}
|
||||
formData.providerName = tempProvider.label;
|
||||
formData.endpoint = providerOptions.value.find(
|
||||
(item) => item.value === val,
|
||||
)?.options.llmEndpoint;
|
||||
formData.chatPath = providerOptions.value.find(
|
||||
(item) => item.value === val,
|
||||
)?.options.chatPath;
|
||||
formData.embedPath = providerOptions.value.find(
|
||||
(item) => item.value === val,
|
||||
)?.options.embedPath;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
draggable
|
||||
:title="isAdd ? $t('button.add') : $t('button.edit')"
|
||||
:before-close="closeDialog"
|
||||
:close-on-click-modal="false"
|
||||
align-center
|
||||
width="482"
|
||||
>
|
||||
<ElForm
|
||||
label-width="100px"
|
||||
ref="formDataRef"
|
||||
:model="formData"
|
||||
status-icon
|
||||
:rules="rules"
|
||||
>
|
||||
<ElFormItem
|
||||
prop="icon"
|
||||
style="display: flex; align-items: center"
|
||||
:label="$t('llmProvider.icon')"
|
||||
>
|
||||
<UploadAvatar v-model="formData.icon" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="providerName" :label="$t('llmProvider.providerName')">
|
||||
<ElInput v-model.trim="formData.providerName" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="provider" :label="$t('llmProvider.apiType')">
|
||||
<ElSelect v-model="formData.providerType" @change="handleChangeProvider">
|
||||
<ElOption
|
||||
v-for="item in providerOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value || ''"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="apiKey" :label="$t('llmProvider.apiKey')">
|
||||
<ElInput v-model.trim="formData.apiKey" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="endpoint" :label="$t('llmProvider.endpoint')">
|
||||
<ElInput v-model.trim="formData.endpoint" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="chatPath" :label="$t('llmProvider.chatPath')">
|
||||
<ElInput v-model.trim="formData.chatPath" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="rerankPath" :label="$t('llmProvider.rerankPath')">
|
||||
<ElInput v-model.trim="formData.rerankPath" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="embedPath" :label="$t('llmProvider.embedPath')">
|
||||
<ElInput v-model.trim="formData.embedPath" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<ElButton @click="closeDialog">
|
||||
{{ $t('button.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="save"
|
||||
:loading="btnLoading"
|
||||
:disabled="btnLoading"
|
||||
>
|
||||
{{ $t('button.save') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.headers-container-reduce {
|
||||
align-items: center;
|
||||
}
|
||||
.addHeadersBtn {
|
||||
width: 100%;
|
||||
border-style: dashed;
|
||||
border-color: var(--el-color-primary);
|
||||
border-radius: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.head-con-content {
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
350
easyflow-ui-admin/app/src/views/ai/model/ManageModelModal.vue
Normal file
350
easyflow-ui-admin/app/src/views/ai/model/ManageModelModal.vue
Normal file
@@ -0,0 +1,350 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, reactive, ref } from 'vue';
|
||||
|
||||
import {
|
||||
CirclePlus,
|
||||
Loading,
|
||||
Minus,
|
||||
RefreshRight,
|
||||
} from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElCollapse,
|
||||
ElCollapseItem,
|
||||
ElDialog,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElIcon,
|
||||
ElInput,
|
||||
ElMessageBox,
|
||||
ElTabPane,
|
||||
ElTabs,
|
||||
ElTooltip,
|
||||
} from 'element-plus';
|
||||
|
||||
import { api } from '#/api/request';
|
||||
import { $t } from '#/locales';
|
||||
import ModelViewItemOperation from '#/views/ai/model/ModelViewItemOperation.vue';
|
||||
|
||||
const emit = defineEmits(['reload']);
|
||||
const tabList = ref<any>([]);
|
||||
const isLoading = ref(false);
|
||||
const chatModelTabList = [
|
||||
// {
|
||||
// label: $t('llm.all'),
|
||||
// name: 'all',
|
||||
// },
|
||||
{
|
||||
label: $t('llmProvider.chatModel'),
|
||||
name: 'chatModel',
|
||||
},
|
||||
// {
|
||||
// label: $t('llm.modelAbility.free'),
|
||||
// name: 'supportFree',
|
||||
// },
|
||||
];
|
||||
const embeddingModelTabList = [
|
||||
{
|
||||
label: $t('llmProvider.embeddingModel'),
|
||||
name: 'embeddingModel',
|
||||
},
|
||||
];
|
||||
|
||||
const rerankModelTabList = [
|
||||
{
|
||||
label: $t('llmProvider.rerankModel'),
|
||||
name: 'rerankModel',
|
||||
},
|
||||
];
|
||||
const formDataRef = ref();
|
||||
const providerInfo = ref<any>();
|
||||
const getProviderInfo = (id: string) => {
|
||||
api.get(`/api/v1/modelProvider/detail?id=${id}`).then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
providerInfo.value = res.data;
|
||||
}
|
||||
});
|
||||
};
|
||||
const modelList = ref<any>([]);
|
||||
const getLlmList = (providerId: string, modelType: string) => {
|
||||
isLoading.value = true;
|
||||
const url =
|
||||
modelType === ''
|
||||
? `/api/v1/model/selectLlmByProviderAndModelType?providerId=${providerId}&modelType=${modelType}&supportFree=true`
|
||||
: `/api/v1/model/selectLlmByProviderAndModelType?providerId=${providerId}&modelType=${modelType}&selectText=${searchFormDada.searchText}`;
|
||||
api.get(url).then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
const chatModelMap = res.data || {};
|
||||
modelList.value = Object.entries(chatModelMap).map(
|
||||
([groupName, llmList]) => ({
|
||||
groupName,
|
||||
llmList,
|
||||
}),
|
||||
);
|
||||
}
|
||||
isLoading.value = false;
|
||||
});
|
||||
};
|
||||
const selectedProviderId = ref('');
|
||||
defineExpose({
|
||||
// providerId: 供应商id, clickModelType 父组件点击的是什么类型的模型 可以是chatModel or embeddingModel
|
||||
openDialog(providerId: string, clickModelType: string) {
|
||||
switch (clickModelType) {
|
||||
case 'chatModel': {
|
||||
tabList.value = [...chatModelTabList];
|
||||
break;
|
||||
}
|
||||
case 'embeddingModel': {
|
||||
tabList.value = [...embeddingModelTabList];
|
||||
break;
|
||||
}
|
||||
case 'rerankModel': {
|
||||
tabList.value = [...rerankModelTabList];
|
||||
break;
|
||||
}
|
||||
// No default
|
||||
}
|
||||
selectedProviderId.value = providerId;
|
||||
formDataRef.value?.resetFields();
|
||||
modelList.value = [];
|
||||
activeName.value = tabList.value[0]?.name;
|
||||
getProviderInfo(providerId);
|
||||
getLlmList(providerId, clickModelType);
|
||||
dialogVisible.value = true;
|
||||
},
|
||||
openEditDialog(item: any) {
|
||||
dialogVisible.value = true;
|
||||
isAdd.value = false;
|
||||
formData.icon = item.icon;
|
||||
formData.providerName = item.providerName;
|
||||
formData.provider = item.provider;
|
||||
},
|
||||
});
|
||||
const isAdd = ref(true);
|
||||
const dialogVisible = ref(false);
|
||||
const formData = reactive({
|
||||
icon: '',
|
||||
providerName: '',
|
||||
provider: '',
|
||||
apiKey: '',
|
||||
endPoint: '',
|
||||
chatPath: '',
|
||||
embedPath: '',
|
||||
});
|
||||
const closeDialog = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
const handleTabClick = async () => {
|
||||
await nextTick();
|
||||
getLlmList(providerInfo.value.id, activeName.value);
|
||||
};
|
||||
const activeName = ref('all');
|
||||
const handleGroupNameDelete = (groupName: string) => {
|
||||
ElMessageBox.confirm(
|
||||
$t('message.deleteModelGroupAlert'),
|
||||
$t('message.noticeTitle'),
|
||||
{
|
||||
confirmButtonText: $t('message.ok'),
|
||||
cancelButtonText: $t('message.cancel'),
|
||||
type: 'warning',
|
||||
},
|
||||
).then(() => {
|
||||
api
|
||||
.post(`/api/v1/model/removeByEntity`, {
|
||||
groupName,
|
||||
providerId: selectedProviderId.value,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
getLlmList(providerInfo.value.id, activeName.value);
|
||||
emit('reload');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
const handleDeleteLlm = (id: any) => {
|
||||
ElMessageBox.confirm(
|
||||
$t('message.deleteModelAlert'),
|
||||
$t('message.noticeTitle'),
|
||||
{
|
||||
confirmButtonText: $t('message.ok'),
|
||||
cancelButtonText: $t('message.cancel'),
|
||||
type: 'warning',
|
||||
},
|
||||
).then(() => {
|
||||
api.post(`/api/v1/model/removeLlmByIds`, { id }).then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
getLlmList(providerInfo.value.id, activeName.value);
|
||||
emit('reload');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
const handleAddLlm = (id: string) => {
|
||||
api
|
||||
.post(`/api/v1/model/update`, {
|
||||
id,
|
||||
withUsed: true,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
getLlmList(providerInfo.value.id, activeName.value);
|
||||
emit('reload');
|
||||
}
|
||||
});
|
||||
};
|
||||
const searchFormDada = reactive({
|
||||
searchText: '',
|
||||
});
|
||||
const handleAddAllLlm = () => {
|
||||
api
|
||||
.post(`/api/v1/model/addAllLlm`, {
|
||||
providerId: selectedProviderId.value,
|
||||
withUsed: true,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
getLlmList(providerInfo.value.id, activeName.value);
|
||||
emit('reload');
|
||||
}
|
||||
});
|
||||
};
|
||||
const handleRefresh = () => {
|
||||
if (isLoading.value) return;
|
||||
getLlmList(providerInfo.value.id, activeName.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
draggable
|
||||
:title="`${providerInfo?.providerName}${$t('llmProvider.model')}`"
|
||||
:before-close="closeDialog"
|
||||
:close-on-click-modal="false"
|
||||
align-center
|
||||
width="762"
|
||||
>
|
||||
<div class="manage-llm-container">
|
||||
<div class="form-container">
|
||||
<ElForm ref="formDataRef" :model="searchFormDada" status-icon>
|
||||
<ElFormItem prop="searchText">
|
||||
<div class="search-container">
|
||||
<ElInput
|
||||
v-model.trim="searchFormDada.searchText"
|
||||
@input="handleRefresh"
|
||||
:placeholder="$t('llm.searchTextPlaceholder')"
|
||||
/>
|
||||
<ElTooltip
|
||||
:content="$t('llm.button.addAllLlm')"
|
||||
placement="top"
|
||||
effect="dark"
|
||||
>
|
||||
<ElIcon
|
||||
size="20"
|
||||
@click="handleAddAllLlm"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<CirclePlus />
|
||||
</ElIcon>
|
||||
</ElTooltip>
|
||||
<ElTooltip
|
||||
:content="$t('llm.button.RetrieveAgain')"
|
||||
placement="top"
|
||||
effect="dark"
|
||||
>
|
||||
<ElIcon size="20" @click="handleRefresh" class="cursor-pointer">
|
||||
<RefreshRight />
|
||||
</ElIcon>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</div>
|
||||
<div class="llm-table-container">
|
||||
<ElTabs v-model="activeName" @tab-click="handleTabClick">
|
||||
<ElTabPane
|
||||
:label="item.label"
|
||||
:name="item.name"
|
||||
v-for="item in tabList"
|
||||
default-active="all"
|
||||
:key="item.name"
|
||||
>
|
||||
<div v-if="isLoading" class="collapse-loading">
|
||||
<ElIcon class="is-loading" size="24">
|
||||
<Loading />
|
||||
</ElIcon>
|
||||
</div>
|
||||
<div v-else>
|
||||
<ElCollapse
|
||||
expand-icon-position="left"
|
||||
v-if="modelList.length > 0"
|
||||
>
|
||||
<ElCollapseItem
|
||||
v-for="group in modelList"
|
||||
:key="group.groupName"
|
||||
:title="group.groupName"
|
||||
:name="group.groupName"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between pr-2">
|
||||
<span>{{ group.groupName }}</span>
|
||||
<span>
|
||||
<ElIcon
|
||||
@click.stop="handleGroupNameDelete(group.groupName)"
|
||||
>
|
||||
<Minus />
|
||||
</ElIcon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<ModelViewItemOperation
|
||||
:need-hidden-setting-icon="true"
|
||||
:llm-list="group.llmList"
|
||||
@delete-llm="handleDeleteLlm"
|
||||
@add-llm="handleAddLlm"
|
||||
:is-management="true"
|
||||
/>
|
||||
</ElCollapseItem>
|
||||
</ElCollapse>
|
||||
</div>
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
</div>
|
||||
</div>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.manage-llm-container {
|
||||
height: 540px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.form-container {
|
||||
height: 30px;
|
||||
}
|
||||
.search-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.llm-table-container {
|
||||
flex: 1;
|
||||
}
|
||||
.collapse-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300px;
|
||||
gap: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
height: 1px !important;
|
||||
background-color: #e4e7ed !important;
|
||||
}
|
||||
</style>
|
||||
622
easyflow-ui-admin/app/src/views/ai/model/Model.vue
Normal file
622
easyflow-ui-admin/app/src/views/ai/model/Model.vue
Normal file
@@ -0,0 +1,622 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { $t } from '@easyflow/locales';
|
||||
|
||||
import { Delete, Edit, Minus, Plus } from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElButton,
|
||||
ElCollapse,
|
||||
ElCollapseItem,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElIcon,
|
||||
ElInput,
|
||||
ElMessage,
|
||||
ElMessageBox,
|
||||
} from 'element-plus';
|
||||
|
||||
import { getLlmProviderList } from '#/api/ai/llm.js';
|
||||
import { api } from '#/api/request.js';
|
||||
import ManageIcon from '#/components/icons/ManageIcon.vue';
|
||||
import PageSide from '#/components/page/PageSide.vue';
|
||||
import AddModelModal from '#/views/ai/model/AddModelModal.vue';
|
||||
import AddModelProviderModal from '#/views/ai/model/AddModelProviderModal.vue';
|
||||
import ManageModelModal from '#/views/ai/model/ManageModelModal.vue';
|
||||
import {
|
||||
getIconByValue,
|
||||
isSvgString,
|
||||
} from '#/views/ai/model/modelUtils/defaultIcon.ts';
|
||||
import { modelTypes } from '#/views/ai/model/modelUtils/modelTypes.ts';
|
||||
import ModelVerifyConfig from '#/views/ai/model/ModelVerifyConfig.vue';
|
||||
import ModelViewItemOperation from '#/views/ai/model/ModelViewItemOperation.vue';
|
||||
|
||||
const brandListData = ref([]);
|
||||
const defaultSelectProviderId = ref('');
|
||||
const defaultIcon = ref('');
|
||||
const modelListData = ref([]);
|
||||
onMounted(() => {
|
||||
getLlmProviderListData();
|
||||
});
|
||||
|
||||
const checkAndFillDefaultIcon = (list) => {
|
||||
if (!list || list.length === 0) return;
|
||||
list.forEach((item) => {
|
||||
if (!item.icon) {
|
||||
item.icon = getIconByValue(item.providerType);
|
||||
}
|
||||
});
|
||||
};
|
||||
const chatModelListData = ref([]);
|
||||
const embeddingModelListData = ref([]);
|
||||
const rerankModelListData = ref([]);
|
||||
|
||||
const getLlmDetailList = (providerId) => {
|
||||
api
|
||||
.get(`/api/v1/model/getList?providerId=${providerId}&withUsed=true`, {})
|
||||
.then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
modelListData.value = res.data;
|
||||
// 初始化模型分组数据(按modelType分类,存储groupName和对应的llm列表)
|
||||
chatModelListData.value = [];
|
||||
embeddingModelListData.value = [];
|
||||
|
||||
// 处理chatModel数据
|
||||
const chatModelMap = res.data.chatModel || {};
|
||||
// 将chatModel的key-value(groupName-llmList)转为数组,方便v-for遍历
|
||||
chatModelListData.value = Object.entries(chatModelMap).map(
|
||||
([groupName, llmList]) => ({
|
||||
groupName,
|
||||
llmList,
|
||||
}),
|
||||
);
|
||||
// 处理embeddingModel数据
|
||||
const embeddingModelMap = res.data.embeddingModel || {};
|
||||
embeddingModelListData.value = Object.entries(embeddingModelMap).map(
|
||||
([groupName, llmList]) => ({
|
||||
groupName,
|
||||
llmList,
|
||||
}),
|
||||
);
|
||||
|
||||
// 处理rerankModel数据
|
||||
const rerankModelMap = res.data.rerankModel || {};
|
||||
rerankModelListData.value = Object.entries(rerankModelMap).map(
|
||||
([groupName, llmList]) => ({
|
||||
groupName,
|
||||
llmList,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getLlmProviderListData = () => {
|
||||
getLlmProviderList().then((res) => {
|
||||
brandListData.value = res.data;
|
||||
checkAndFillDefaultIcon(brandListData.value);
|
||||
if (!defaultSelectProviderId.value) {
|
||||
defaultSelectProviderId.value = res.data[0].id;
|
||||
defaultIcon.value = res.data[0].icon;
|
||||
}
|
||||
llmProviderForm.value = {
|
||||
...res.data[0],
|
||||
};
|
||||
getLlmDetailList(defaultSelectProviderId.value);
|
||||
});
|
||||
};
|
||||
const selectCategory = ref({
|
||||
providerName: '',
|
||||
provider: '',
|
||||
});
|
||||
const handleCategoryClick = (category) => {
|
||||
selectCategory.value.providerName = category.providerName;
|
||||
selectCategory.value.provider = category.provider;
|
||||
defaultSelectProviderId.value = category.id;
|
||||
defaultIcon.value = category.icon;
|
||||
llmProviderForm.value = {
|
||||
...category,
|
||||
};
|
||||
getLlmDetailList(category.id);
|
||||
};
|
||||
|
||||
// 添加模型供应商
|
||||
const addLlmProviderRef = ref();
|
||||
// 模型管理ref
|
||||
const manageLlmRef = ref();
|
||||
// 模型验证配置ref
|
||||
const llmVerifyConfigRef = ref();
|
||||
// 添加模型
|
||||
const addLlmRef = ref();
|
||||
const handleDeleteProvider = (row) => {
|
||||
ElMessageBox.confirm($t('message.deleteAlert'), $t('message.noticeTitle'), {
|
||||
confirmButtonText: $t('message.ok'),
|
||||
cancelButtonText: $t('message.cancel'),
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
api
|
||||
.post('/api/v1/modelProvider/remove', {
|
||||
id: row.id,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success(res.message);
|
||||
getLlmProviderListData();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
const llmProviderForm = ref({});
|
||||
const llmProviderFormRef = ref();
|
||||
const isEdit = ref(false);
|
||||
const dialogAddProviderVisible = ref(false);
|
||||
const controlBtns = [
|
||||
{
|
||||
icon: Edit,
|
||||
label: $t('button.edit'),
|
||||
onClick(row) {
|
||||
isEdit.value = true;
|
||||
dialogAddProviderVisible.value = true;
|
||||
|
||||
const tempRow = {
|
||||
...row,
|
||||
};
|
||||
if (isSvgString(tempRow.icon)) {
|
||||
tempRow.icon = '';
|
||||
}
|
||||
addLlmProviderRef.value.openEditDialog(tempRow);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'danger',
|
||||
icon: Delete,
|
||||
label: $t('button.delete'),
|
||||
onClick(row) {
|
||||
handleDeleteProvider(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
const footerButton = {
|
||||
icon: Plus,
|
||||
label: $t('button.add'),
|
||||
onClick() {
|
||||
dialogAddProviderVisible.value = true;
|
||||
addLlmProviderRef.value.openAddDialog();
|
||||
isEdit.value = false;
|
||||
},
|
||||
};
|
||||
const handleAddLlm = (modelType) => {
|
||||
addLlmRef.value.openAddDialog(modelType);
|
||||
};
|
||||
const handleManageLlm = (clickModelType) => {
|
||||
manageLlmRef.value.openDialog(defaultSelectProviderId.value, clickModelType);
|
||||
};
|
||||
const handleDeleteLlm = (id) => {
|
||||
ElMessageBox.confirm($t('message.deleteAlert'), $t('message.noticeTitle'), {
|
||||
confirmButtonText: $t('message.ok'),
|
||||
cancelButtonText: $t('message.cancel'),
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
api.post('/api/v1/model/remove', { id }).then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success($t('message.deleteOkMessage'));
|
||||
getLlmDetailList(defaultSelectProviderId.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleEditLlm = (id) => {
|
||||
api.get(`/api/v1/model/detail?id=${id}`).then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
addLlmRef.value.openEditDialog(res.data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleGroupNameUpdateModel = (groupName) => {
|
||||
api
|
||||
.post('/api/v1/model/updateByEntity', {
|
||||
providerId: defaultSelectProviderId.value,
|
||||
groupName,
|
||||
withUsed: false,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
getLlmDetailList(defaultSelectProviderId.value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 输入框失去焦点时更新配置
|
||||
const handleFormBlur = async () => {
|
||||
if (!defaultSelectProviderId.value) return;
|
||||
|
||||
try {
|
||||
const res = await api.post('/api/v1/modelProvider/update', {
|
||||
id: defaultSelectProviderId.value,
|
||||
apiKey: llmProviderForm.value.apiKey,
|
||||
endpoint: llmProviderForm.value.endpoint,
|
||||
chatPath: llmProviderForm.value.chatPath,
|
||||
embedPath: llmProviderForm.value.embedPath,
|
||||
rerankPath: llmProviderForm.value.rerankPath,
|
||||
});
|
||||
|
||||
if (res.errorCode === 0) {
|
||||
getLlmProviderList().then((res) => {
|
||||
brandListData.value = res.data;
|
||||
checkAndFillDefaultIcon(res.data);
|
||||
brandListData.value.forEach((item) => {
|
||||
if (item.id === defaultSelectProviderId.value) {
|
||||
llmProviderForm.value = { ...item };
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
ElMessage.error(res.message || $t('message.updateFail'));
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error($t('message.networkError'));
|
||||
console.error('更新失败:', error);
|
||||
}
|
||||
};
|
||||
const handleTest = () => {
|
||||
llmVerifyConfigRef.value.openDialog(defaultSelectProviderId.value);
|
||||
};
|
||||
|
||||
const handleUpdateLlm = (id) => {
|
||||
api.post('/api/v1/model/update', { id, withUsed: false }).then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
getLlmDetailList(defaultSelectProviderId.value);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="llm-container">
|
||||
<div>
|
||||
<PageSide
|
||||
:title="$t('llm.addProvider')"
|
||||
label-key="providerName"
|
||||
value-key="id"
|
||||
:menus="brandListData"
|
||||
:control-btns="controlBtns"
|
||||
:footer-button="footerButton"
|
||||
@change="handleCategoryClick"
|
||||
:default-selected="defaultSelectProviderId"
|
||||
:icon-size="21"
|
||||
/>
|
||||
</div>
|
||||
<div class="llm-table-container">
|
||||
<div class="llm-form-container">
|
||||
<div class="title">{{ selectCategory.providerName }}</div>
|
||||
<ElForm
|
||||
ref="llmProviderFormRef"
|
||||
:model="llmProviderForm"
|
||||
status-icon
|
||||
label-position="top"
|
||||
>
|
||||
<ElFormItem prop="apiKey" :label="$t('llmProvider.apiKey')">
|
||||
<ElInput
|
||||
v-model="llmProviderForm.apiKey"
|
||||
@blur="handleFormBlur"
|
||||
type="password"
|
||||
show-password
|
||||
>
|
||||
<template #append>
|
||||
<ElButton
|
||||
@click="handleTest"
|
||||
style="
|
||||
background-color: var(--el-bg-color);
|
||||
width: 80px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 0 8px 8px 0;
|
||||
"
|
||||
>
|
||||
{{ $t('llm.button.test') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="endpoint" :label="$t('llmProvider.endpoint')">
|
||||
<ElInput
|
||||
v-model.trim="llmProviderForm.endpoint"
|
||||
@blur="handleFormBlur"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="chatPath" :label="$t('llmProvider.chatPath')">
|
||||
<ElInput
|
||||
v-model.trim="llmProviderForm.chatPath"
|
||||
@blur="handleFormBlur"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="embedPath" :label="$t('llmProvider.embedPath')">
|
||||
<ElInput
|
||||
v-model.trim="llmProviderForm.embedPath"
|
||||
@blur="handleFormBlur"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="rerankPath" :label="$t('llmProvider.rerankPath')">
|
||||
<ElInput
|
||||
v-model.trim="llmProviderForm.rerankPath"
|
||||
@blur="handleFormBlur"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<div class="llm-manage-container">
|
||||
<div
|
||||
v-for="(model, index) in modelTypes"
|
||||
:key="model.value"
|
||||
class="model-container"
|
||||
>
|
||||
<div
|
||||
class="model-common-title"
|
||||
:class="[index === 0 ? 'first-model-title' : '']"
|
||||
>
|
||||
{{ model.label }}
|
||||
</div>
|
||||
|
||||
<!-- 对话模型(chatModel)遍历 -->
|
||||
<div
|
||||
v-if="model.value === 'chatModel' && chatModelListData.length > 0"
|
||||
>
|
||||
<ElCollapse expand-icon-position="left">
|
||||
<ElCollapseItem
|
||||
v-for="group in chatModelListData"
|
||||
:key="group.groupName"
|
||||
:title="group.groupName"
|
||||
:name="group.groupName"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between pr-2">
|
||||
<span>{{ group.groupName }}</span>
|
||||
<span>
|
||||
<ElIcon
|
||||
@click.stop="
|
||||
handleGroupNameUpdateModel(group.groupName)
|
||||
"
|
||||
>
|
||||
<Minus />
|
||||
</ElIcon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<ModelViewItemOperation
|
||||
:llm-list="group.llmList"
|
||||
:icon="defaultIcon"
|
||||
@delete-llm="handleDeleteLlm"
|
||||
@edit-llm="handleEditLlm"
|
||||
@update-with-used="handleUpdateLlm"
|
||||
/>
|
||||
</ElCollapseItem>
|
||||
</ElCollapse>
|
||||
</div>
|
||||
|
||||
<!-- 嵌入模型(embeddingModel)遍历-->
|
||||
<div
|
||||
v-if="
|
||||
model.value === 'embeddingModel' &&
|
||||
embeddingModelListData.length > 0
|
||||
"
|
||||
>
|
||||
<ElCollapse expand-icon-position="left">
|
||||
<ElCollapseItem
|
||||
v-for="group in embeddingModelListData"
|
||||
:key="group.groupName"
|
||||
:title="group.groupName"
|
||||
:name="group.groupName"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between pr-2">
|
||||
<span>{{ group.groupName }}</span>
|
||||
<span
|
||||
@click.stop="
|
||||
handleGroupNameUpdateModel(group.groupName)
|
||||
"
|
||||
>
|
||||
<ElIcon>
|
||||
<Minus />
|
||||
</ElIcon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<ModelViewItemOperation
|
||||
:llm-list="group.llmList"
|
||||
:icon="defaultIcon"
|
||||
@delete-llm="handleDeleteLlm"
|
||||
@edit-llm="handleEditLlm"
|
||||
@update-with-used="handleUpdateLlm"
|
||||
/>
|
||||
</ElCollapseItem>
|
||||
</ElCollapse>
|
||||
</div>
|
||||
|
||||
<!-- 重排模型(rerankModel)遍历-->
|
||||
<div
|
||||
v-if="
|
||||
model.value === 'rerankModel' &&
|
||||
embeddingModelListData.length > 0
|
||||
"
|
||||
>
|
||||
<ElCollapse expand-icon-position="left">
|
||||
<ElCollapseItem
|
||||
v-for="group in rerankModelListData"
|
||||
:key="group.groupName"
|
||||
:title="group.groupName"
|
||||
:name="group.groupName"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between pr-2">
|
||||
<span>{{ group.groupName }}</span>
|
||||
<span
|
||||
@click.stop="
|
||||
handleGroupNameUpdateModel(group.groupName)
|
||||
"
|
||||
>
|
||||
<ElIcon>
|
||||
<Minus />
|
||||
</ElIcon>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<ModelViewItemOperation
|
||||
:llm-list="group.llmList"
|
||||
:icon="defaultIcon"
|
||||
@delete-llm="handleDeleteLlm"
|
||||
@edit-llm="handleEditLlm"
|
||||
@update-with-used="handleUpdateLlm"
|
||||
/>
|
||||
</ElCollapseItem>
|
||||
</ElCollapse>
|
||||
</div>
|
||||
|
||||
<div class="model-operation-container">
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleManageLlm(model.value)"
|
||||
:icon="ManageIcon"
|
||||
>
|
||||
{{ $t('llm.button.management') }}
|
||||
</ElButton>
|
||||
<ElButton :icon="Plus" @click="handleAddLlm(model.value)">
|
||||
{{ $t('button.add') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--添加模型供应商模态框-->
|
||||
<AddModelProviderModal
|
||||
ref="addLlmProviderRef"
|
||||
@reload="getLlmProviderListData()"
|
||||
/>
|
||||
<!--添加模型模态框-->
|
||||
<AddModelModal
|
||||
ref="addLlmRef"
|
||||
@reload="getLlmProviderListData()"
|
||||
:provider-id="defaultSelectProviderId"
|
||||
/>
|
||||
<!--模型管理模态框-->
|
||||
<ManageModelModal ref="manageLlmRef" @reload="getLlmProviderListData()" />
|
||||
|
||||
<!--模型检测配置模态框-->
|
||||
<ModelVerifyConfig ref="llmVerifyConfigRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.llm-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 20px;
|
||||
height: calc(100vh - 90px);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
line-height: 22px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.llm-table-container {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
background-color: var(--el-bg-color);
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.llm-form-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.model-common-title {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
margin: 24px 0 12px 0;
|
||||
}
|
||||
|
||||
.first-model-title {
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
/* 折叠面板容器 */
|
||||
:deep(.el-collapse) {
|
||||
border: none;
|
||||
border-radius: 8px !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item) {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item__header) {
|
||||
background-color: #f9fafc;
|
||||
padding: 0 9px 0 17px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
border: 1px solid #f0f0f0;
|
||||
height: 20px !important;
|
||||
line-height: 20px !important;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item__arrow) {
|
||||
line-height: 38px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item__wrap) {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item__content) {
|
||||
border: 1px solid #f0f0f0;
|
||||
background: #ffffff;
|
||||
border-radius: 0 0 8px 8px;
|
||||
padding: 12px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.model-operation-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.flex.items-center.justify-between.pr-2 {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
139
easyflow-ui-admin/app/src/views/ai/model/ModelVerifyConfig.vue
Normal file
139
easyflow-ui-admin/app/src/views/ai/model/ModelVerifyConfig.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElDialog,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElMessage,
|
||||
ElOption,
|
||||
ElSelect,
|
||||
} from 'element-plus';
|
||||
|
||||
import { api } from '#/api/request';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const options = ref<any[]>([]);
|
||||
const getLlmList = (providerId: string) => {
|
||||
api.get(`/api/v1/model/list?providerId=${providerId}`, {}).then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
options.value = res.data;
|
||||
}
|
||||
});
|
||||
};
|
||||
const modelType = ref('');
|
||||
const vectorDimension = ref('');
|
||||
const formDataRef = ref();
|
||||
const dialogVisible = ref(false);
|
||||
defineExpose({
|
||||
openDialog(providerId: string) {
|
||||
formDataRef.value?.resetFields();
|
||||
modelType.value = '';
|
||||
vectorDimension.value = '';
|
||||
getLlmList(providerId);
|
||||
dialogVisible.value = true;
|
||||
},
|
||||
});
|
||||
const formData = reactive({
|
||||
llmId: '',
|
||||
});
|
||||
|
||||
const rules = {
|
||||
llmId: [
|
||||
{
|
||||
required: true,
|
||||
message: $t('message.required'),
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
btnLoading.value = true;
|
||||
await formDataRef.value.validate();
|
||||
api
|
||||
.get(`/api/v1/model/verifyLlmConfig?id=${formData.llmId}`, {})
|
||||
.then((res) => {
|
||||
if (res.errorCode === 0) {
|
||||
ElMessage.success($t('llm.testSuccess'));
|
||||
if (modelType.value === 'embeddingModel' && res?.data?.dimension) {
|
||||
vectorDimension.value = res?.data?.dimension;
|
||||
}
|
||||
}
|
||||
btnLoading.value = false;
|
||||
});
|
||||
};
|
||||
const btnLoading = ref(false);
|
||||
const closeDialog = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
const getModelInfo = (id: string) => {
|
||||
options.value.forEach((item: any) => {
|
||||
if (item.id === id) {
|
||||
modelType.value = item.modelType;
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
draggable
|
||||
:title="$t('llm.verifyLlmTitle')"
|
||||
:close-on-click-modal="false"
|
||||
align-center
|
||||
width="482"
|
||||
>
|
||||
<ElForm ref="formDataRef" :model="formData" status-icon :rules="rules">
|
||||
<ElFormItem prop="llmId" :label="$t('llm.modelToBeTested')">
|
||||
<ElSelect v-model="formData.llmId" @change="getModelInfo">
|
||||
<ElOption
|
||||
v-for="item in options"
|
||||
:key="item.id"
|
||||
:label="item.title"
|
||||
:value="item.id || ''"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="modelType === 'embeddingModel' && vectorDimension"
|
||||
:label="$t('documentCollection.dimensionOfVectorModel')"
|
||||
label-width="100px"
|
||||
>
|
||||
{{ vectorDimension }}
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<ElButton @click="closeDialog">
|
||||
{{ $t('button.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="save"
|
||||
:loading="btnLoading"
|
||||
:disabled="btnLoading"
|
||||
>
|
||||
{{ $t('button.confirm') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.headers-container-reduce {
|
||||
align-items: center;
|
||||
}
|
||||
.addHeadersBtn {
|
||||
width: 100%;
|
||||
border-style: dashed;
|
||||
border-color: var(--el-color-primary);
|
||||
border-radius: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.head-con-content {
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,201 @@
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import type { llmType } from '#/api';
|
||||
import type { ModelAbilityItem } from '#/views/ai/model/modelUtils/model-ability';
|
||||
|
||||
import { Minus, Plus, Setting } from '@element-plus/icons-vue';
|
||||
import { ElIcon, ElImage, ElTag } from 'element-plus';
|
||||
|
||||
import { getIconByValue } from '#/views/ai/model/modelUtils/defaultIcon';
|
||||
import { getDefaultModelAbility } from '#/views/ai/model/modelUtils/model-ability';
|
||||
import { mapLlmToModelAbility } from '#/views/ai/model/modelUtils/model-ability-utils';
|
||||
|
||||
defineProps({
|
||||
llmList: {
|
||||
type: Array as PropType<llmType[]>,
|
||||
default: () => [],
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
needHiddenSettingIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isManagement: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['deleteLlm', 'editLlm', 'addLlm', 'updateWithUsed']);
|
||||
|
||||
const handleDeleteLlm = (id: string) => {
|
||||
emit('deleteLlm', id);
|
||||
};
|
||||
|
||||
const handleAddLlm = (id: string) => {
|
||||
emit('addLlm', id);
|
||||
};
|
||||
|
||||
const handleEditLlm = (id: string) => {
|
||||
emit('editLlm', id);
|
||||
};
|
||||
|
||||
// 修改该模型为未使用状态,修改数据库的with_used字段为false
|
||||
const handleUpdateWithUsedLlm = (id: string) => {
|
||||
emit('updateWithUsed', id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取LLM支持的选中的能力标签
|
||||
* 只返回 selected 为 true 的标签
|
||||
*/
|
||||
const getSelectedAbilityTagsForLlm = (llm: llmType): ModelAbilityItem[] => {
|
||||
const defaultAbility = getDefaultModelAbility();
|
||||
const allTags = mapLlmToModelAbility(llm, defaultAbility);
|
||||
return allTags.filter((tag) => tag.selected);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-for="llm in llmList" :key="llm.id" class="container">
|
||||
<div class="llm-item">
|
||||
<div class="start">
|
||||
<ElImage
|
||||
v-if="llm.modelProvider.icon"
|
||||
:src="llm.modelProvider.icon"
|
||||
style="width: 21px; height: 21px"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-else
|
||||
v-html="getIconByValue(llm.modelProvider.providerType)"
|
||||
:style="{
|
||||
width: '21px',
|
||||
height: '21px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
}"
|
||||
class="svg-container"
|
||||
></div>
|
||||
|
||||
<div>{{ llm?.modelProvider?.providerName }}/{{ llm.title }}</div>
|
||||
|
||||
<!-- 模型能力 -->
|
||||
<div
|
||||
v-if="getSelectedAbilityTagsForLlm(llm).length > 0"
|
||||
class="ability-tags"
|
||||
>
|
||||
<ElTag
|
||||
v-for="tag in getSelectedAbilityTagsForLlm(llm)"
|
||||
:key="tag.value"
|
||||
class="ability-tag"
|
||||
:type="tag.activeType"
|
||||
size="small"
|
||||
>
|
||||
{{ tag.label }}
|
||||
</ElTag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="end">
|
||||
<ElIcon
|
||||
v-if="!needHiddenSettingIcon"
|
||||
size="16"
|
||||
@click="handleEditLlm(llm.id)"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<Setting />
|
||||
</ElIcon>
|
||||
<template v-if="!isManagement">
|
||||
<ElIcon
|
||||
size="16"
|
||||
@click="handleUpdateWithUsedLlm(llm.id)"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<Minus />
|
||||
</ElIcon>
|
||||
</template>
|
||||
|
||||
<template v-if="isManagement">
|
||||
<ElIcon
|
||||
v-if="llm.withUsed"
|
||||
size="16"
|
||||
@click="handleDeleteLlm(llm.id)"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<Minus />
|
||||
</ElIcon>
|
||||
<ElIcon
|
||||
v-else
|
||||
size="16"
|
||||
@click="handleAddLlm(llm.id)"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<Plus />
|
||||
</ElIcon>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.llm-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px 18px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.container:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.start {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.end {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ability-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.ability-tag {
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.svg-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.svg-container :deep(svg) {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
import providerList from './providerList.json';
|
||||
|
||||
const providerOptions =
|
||||
ref<Array<{ icon: string; label: string; options: any; value: string }>>(
|
||||
providerList,
|
||||
);
|
||||
|
||||
/**
|
||||
* 根据传入的value,返回对应的icon属性
|
||||
* @param targetValue 要匹配的value值
|
||||
* @returns 匹配到的icon字符串,未匹配到返回空字符串
|
||||
*/
|
||||
export const getIconByValue = (targetValue: string): string => {
|
||||
const matchItem = providerOptions.value.find(
|
||||
(item) => item.value === targetValue,
|
||||
);
|
||||
|
||||
return matchItem?.icon || '';
|
||||
};
|
||||
|
||||
export const isSvgString = (icon: any) => {
|
||||
if (typeof icon !== 'string') return false;
|
||||
// 简单判断:是否包含 SVG 根标签
|
||||
return icon.trim().startsWith('<svg') && icon.trim().endsWith('</svg>');
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { BooleanField, ModelAbilityItem } from './model-ability';
|
||||
|
||||
import type { llmType } from '#/api';
|
||||
|
||||
/**
|
||||
* 将 llm 数据转换为标签选中状态
|
||||
* @param llm LLM数据对象
|
||||
* @param modelAbility 模型能力数组
|
||||
* @returns 更新后的模型能力数组
|
||||
*/
|
||||
export const mapLlmToModelAbility = (
|
||||
llm: llmType,
|
||||
modelAbility: ModelAbilityItem[],
|
||||
): ModelAbilityItem[] => {
|
||||
return modelAbility.map((tag) => ({
|
||||
...tag,
|
||||
selected: Boolean(llm[tag.field as keyof llmType]),
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 从标签选中状态生成 features 对象
|
||||
* @param modelAbility 模型能力数组
|
||||
* @returns 包含所有字段的features对象
|
||||
*/
|
||||
export const generateFeaturesFromModelAbility = (
|
||||
modelAbility: ModelAbilityItem[],
|
||||
): Record<BooleanField, boolean> => {
|
||||
const features: Partial<Record<BooleanField, boolean>> = {};
|
||||
|
||||
modelAbility.forEach((tag) => {
|
||||
features[tag.field] = tag.selected;
|
||||
});
|
||||
|
||||
return features as Record<BooleanField, boolean>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 过滤显示选中的标签
|
||||
* @param modelAbility 模型能力数组
|
||||
* @returns 选中的标签数组
|
||||
*/
|
||||
export const getSelectedModelAbility = (
|
||||
modelAbility: ModelAbilityItem[],
|
||||
): ModelAbilityItem[] => {
|
||||
return modelAbility.filter((tag) => tag.selected);
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置所有标签为未选中状态
|
||||
* @param modelAbility 模型能力数组
|
||||
*/
|
||||
export const resetModelAbility = (modelAbility: ModelAbilityItem[]): void => {
|
||||
modelAbility.forEach((tag) => {
|
||||
tag.selected = false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据标签选中状态更新表单数据
|
||||
* @param modelAbility 模型能力数组
|
||||
* @param formData 表单数据对象
|
||||
*/
|
||||
export const updateFormDataFromModelAbility = (
|
||||
modelAbility: ModelAbilityItem[],
|
||||
formData: Record<BooleanField, boolean>,
|
||||
): void => {
|
||||
modelAbility.forEach((tag) => {
|
||||
formData[tag.field] = tag.selected;
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
import { $t } from '#/locales';
|
||||
|
||||
export type BooleanField =
|
||||
| 'supportAudio'
|
||||
| 'supportFree'
|
||||
| 'supportImage'
|
||||
| 'supportImageB64Only'
|
||||
| 'supportThinking'
|
||||
| 'supportTool'
|
||||
| 'supportToolMessage'
|
||||
| 'supportVideo';
|
||||
|
||||
export interface ModelAbilityItem {
|
||||
activeType: 'danger' | 'info' | 'primary' | 'success' | 'warning';
|
||||
defaultType: 'info';
|
||||
field: BooleanField;
|
||||
label: string;
|
||||
selected: boolean;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型能力标签的默认配置
|
||||
* @returns ModelAbilityItem[] 模型能力配置数组
|
||||
*/
|
||||
export const getDefaultModelAbility = (): ModelAbilityItem[] => [
|
||||
{
|
||||
label: $t('llm.modelAbility.supportThinking'),
|
||||
value: 'thinking',
|
||||
defaultType: 'info',
|
||||
activeType: 'success',
|
||||
selected: false,
|
||||
field: 'supportThinking',
|
||||
},
|
||||
{
|
||||
label: $t('llm.modelAbility.supportTool'),
|
||||
value: 'tool',
|
||||
defaultType: 'info',
|
||||
activeType: 'success',
|
||||
selected: false,
|
||||
field: 'supportTool',
|
||||
},
|
||||
{
|
||||
label: $t('llm.modelAbility.supportVideo'),
|
||||
value: 'video',
|
||||
defaultType: 'info',
|
||||
activeType: 'success',
|
||||
selected: false,
|
||||
field: 'supportVideo',
|
||||
},
|
||||
{
|
||||
label: $t('llm.modelAbility.supportImage'),
|
||||
value: 'image',
|
||||
defaultType: 'info',
|
||||
activeType: 'success',
|
||||
selected: false,
|
||||
field: 'supportImage',
|
||||
},
|
||||
{
|
||||
label: $t('llm.modelAbility.supportFree'),
|
||||
value: 'free',
|
||||
defaultType: 'info',
|
||||
activeType: 'success',
|
||||
selected: false,
|
||||
field: 'supportFree',
|
||||
},
|
||||
{
|
||||
label: $t('llm.modelAbility.supportAudio'),
|
||||
value: 'audio',
|
||||
defaultType: 'info',
|
||||
activeType: 'success',
|
||||
selected: false,
|
||||
field: 'supportAudio',
|
||||
},
|
||||
{
|
||||
label: $t('llm.modelAbility.supportImageB64Only'),
|
||||
value: 'imageB64',
|
||||
defaultType: 'info',
|
||||
activeType: 'success',
|
||||
selected: false,
|
||||
field: 'supportImageB64Only',
|
||||
},
|
||||
{
|
||||
label: $t('llm.modelAbility.supportToolMessage'),
|
||||
value: 'toolMessage',
|
||||
defaultType: 'info',
|
||||
activeType: 'success',
|
||||
selected: true,
|
||||
field: 'supportToolMessage',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 根据字段数组获取对应的标签选中状态
|
||||
* @param modelAbility 模型能力数组
|
||||
* @param fields 需要获取的字段数组
|
||||
* @returns 以字段名为键、选中状态为值的对象
|
||||
*/
|
||||
export const getTagsSelectedStatus = (
|
||||
modelAbility: ModelAbilityItem[],
|
||||
fields: BooleanField[],
|
||||
): Record<BooleanField, boolean> => {
|
||||
const result: Partial<Record<BooleanField, boolean>> = {};
|
||||
|
||||
fields.forEach((field) => {
|
||||
const tagItem = modelAbility.find((tag) => tag.field === field);
|
||||
result[field] = tagItem?.selected ?? false;
|
||||
});
|
||||
|
||||
return result as Record<BooleanField, boolean>;
|
||||
};
|
||||
|
||||
/**
|
||||
* 同步标签选中状态与formData中的布尔字段
|
||||
* @param modelAbility 模型能力数组
|
||||
* @param formData 表单数据对象
|
||||
*/
|
||||
export const syncTagSelectedStatus = (
|
||||
modelAbility: ModelAbilityItem[],
|
||||
formData: Record<BooleanField, boolean>,
|
||||
): void => {
|
||||
modelAbility.forEach((tag) => {
|
||||
tag.selected = formData[tag.field] ?? false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理标签点击事件
|
||||
* @param modelAbility 模型能力数组
|
||||
* @param item 被点击的标签项
|
||||
* @param formData 表单数据对象
|
||||
*/
|
||||
export const handleTagClick = (
|
||||
// modelAbility: ModelAbilityItem[],
|
||||
item: ModelAbilityItem,
|
||||
formData: Record<BooleanField, boolean>,
|
||||
): void => {
|
||||
// 切换标签选中状态
|
||||
item.selected = !item.selected;
|
||||
|
||||
// 同步更新formData中的布尔字段
|
||||
formData[item.field] = item.selected;
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据字段获取对应的标签项
|
||||
* @param modelAbility 模型能力数组
|
||||
* @param field 布尔字段名
|
||||
* @returns 标签项 | undefined
|
||||
*/
|
||||
export const getTagByField = (
|
||||
modelAbility: ModelAbilityItem[],
|
||||
field: BooleanField,
|
||||
): ModelAbilityItem | undefined => {
|
||||
return modelAbility.find((tag) => tag.field === field);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有支持的BooleanField数组
|
||||
*/
|
||||
export const getAllBooleanFields = (): BooleanField[] => [
|
||||
'supportThinking',
|
||||
'supportTool',
|
||||
'supportImage',
|
||||
'supportImageB64Only',
|
||||
'supportVideo',
|
||||
'supportAudio',
|
||||
'supportFree',
|
||||
];
|
||||
@@ -0,0 +1,16 @@
|
||||
import { $t } from '@easyflow/locales';
|
||||
|
||||
export const modelTypes = [
|
||||
{
|
||||
label: $t('llmProvider.chatModel'),
|
||||
value: 'chatModel',
|
||||
},
|
||||
{
|
||||
label: $t('llmProvider.embeddingModel'),
|
||||
value: 'embeddingModel',
|
||||
},
|
||||
{
|
||||
label: $t('llmProvider.rerankModel'),
|
||||
value: 'rerankModel',
|
||||
},
|
||||
];
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user