初始化

This commit is contained in:
2026-02-22 18:56:10 +08:00
commit 26677972a6
3112 changed files with 255972 additions and 0 deletions

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

View File

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

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

View 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-valuegroupName-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>

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

View File

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

View File

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

View File

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

View File

@@ -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',
];

View File

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