parent
e77b7e7afd
commit
f62e8d4cea
|
@ -0,0 +1,85 @@
|
||||||
|
import {ProviderEnum} from './provider';
|
||||||
|
|
||||||
|
export const baseColumns = [
|
||||||
|
{
|
||||||
|
label: '模型别名',
|
||||||
|
field: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '模型版本',
|
||||||
|
field: 'model',
|
||||||
|
width: '140',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '回复上限',
|
||||||
|
field: 'responseLimit',
|
||||||
|
width: '100',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '生成随机性',
|
||||||
|
field: 'temperature',
|
||||||
|
width: '100',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Top P',
|
||||||
|
field: 'topP',
|
||||||
|
width: '100',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const openaiColumns = [
|
||||||
|
...baseColumns,
|
||||||
|
{
|
||||||
|
label: 'Api Key',
|
||||||
|
field: 'apiKey',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ollamaColumns = [
|
||||||
|
...baseColumns,
|
||||||
|
{
|
||||||
|
label: 'Base Url',
|
||||||
|
field: 'baseUrl',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const qfanColumns = [
|
||||||
|
...baseColumns,
|
||||||
|
{
|
||||||
|
label: 'Api Key',
|
||||||
|
field: 'apiKey',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const qwenColumns = [
|
||||||
|
...baseColumns,
|
||||||
|
{
|
||||||
|
label: 'Api Key',
|
||||||
|
field: 'apiKey',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export const zhipuColumns = [
|
||||||
|
...baseColumns,
|
||||||
|
{
|
||||||
|
label: 'Api Key',
|
||||||
|
field: 'apiKey',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getColumns(provider: string) {
|
||||||
|
switch (provider) {
|
||||||
|
case ProviderEnum.OLLAMA: {
|
||||||
|
return ollamaColumns;
|
||||||
|
}
|
||||||
|
case ProviderEnum.Q_FAN: {
|
||||||
|
return qfanColumns;
|
||||||
|
}
|
||||||
|
case ProviderEnum.Q_WEN: {
|
||||||
|
return qwenColumns;
|
||||||
|
}
|
||||||
|
case ProviderEnum.ZHIPU: {
|
||||||
|
return zhipuColumns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return openaiColumns;
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ProviderEnum } from './provider';
|
||||||
|
|
||||||
|
export const LLMProviders: any[] = [
|
||||||
|
{
|
||||||
|
model: ProviderEnum.OPENAI,
|
||||||
|
name: 'OpenAI',
|
||||||
|
models: ['gpt-3.5-turbo', 'gpt-4', 'gpt-4-32k', 'gpt-4-turbo', 'gpt-4o'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.Q_FAN,
|
||||||
|
name: '百度千帆',
|
||||||
|
models: [
|
||||||
|
'ERNIE-Bot',
|
||||||
|
'ERNIE-Bot 4.0',
|
||||||
|
'ERNIE-Bot-8K',
|
||||||
|
'ERNIE-Bot-turbo',
|
||||||
|
'ERNIE-Speed-128K',
|
||||||
|
'EB-turbo-AppBuilder',
|
||||||
|
'Yi-34B-Chat',
|
||||||
|
'BLOOMZ-7B',
|
||||||
|
'Qianfan-BLOOMZ-7B-compressed',
|
||||||
|
'Mixtral-8x7B-Instruct',
|
||||||
|
'Llama-2-7b-chat',
|
||||||
|
'Llama-2-13b-chat',
|
||||||
|
'Llama-2-70b-chat',
|
||||||
|
'Qianfan-Chinese-Llama-2-7B',
|
||||||
|
'ChatGLM2-6B-32K',
|
||||||
|
'AquilaChat-7B',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.Q_WEN,
|
||||||
|
name: '阿里百炼',
|
||||||
|
models: [
|
||||||
|
'qwen-turbo',
|
||||||
|
'qwen-plus',
|
||||||
|
'qwen-max',
|
||||||
|
'qwen-max-longcontext',
|
||||||
|
'qwen-7b-chat',
|
||||||
|
'qwen-14b-chat',
|
||||||
|
'qwen-72b-chat',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.ZHIPU,
|
||||||
|
name: '智谱清言',
|
||||||
|
models: [
|
||||||
|
'glm-4',
|
||||||
|
'glm-4v',
|
||||||
|
'glm-4-air',
|
||||||
|
'glm-4-airx',
|
||||||
|
'glm-4-flash',
|
||||||
|
'glm-3-turbo',
|
||||||
|
'chatglm_turbo',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.GITEEAI,
|
||||||
|
name: 'Gitee AI',
|
||||||
|
models: [
|
||||||
|
'Qwen2-72B-Instruct',
|
||||||
|
'Qwen2-7B-Instruct',
|
||||||
|
'Qwen2.5-72B-Instruct',
|
||||||
|
'glm-4-9b-chat',
|
||||||
|
'deepseek-coder-33B-instruct',
|
||||||
|
'codegeex4-all-9b',
|
||||||
|
'Yi-34B-Chat',
|
||||||
|
'code-raccoon-v1',
|
||||||
|
'Qwen2.5-Coder-32B-Instruct',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.DEEPSEEK,
|
||||||
|
name: 'DeepSeek',
|
||||||
|
models: ['deepseek-chat', 'deepseek-coder'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.DOUYIN,
|
||||||
|
name: '抖音豆包',
|
||||||
|
models: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.SILICON,
|
||||||
|
name: '硅基流动',
|
||||||
|
models: [
|
||||||
|
'deepseek-ai/DeepSeek-V2-Chat',
|
||||||
|
'deepseek-ai/DeepSeek-Coder-V2-Instruct',
|
||||||
|
'deepseek-ai/DeepSeek-V2.5',
|
||||||
|
'Qwen/Qwen2.5-72B-Instruct-128K',
|
||||||
|
'Qwen/Qwen2.5-72B-Instruct',
|
||||||
|
'Qwen/Qwen2-VL-72B-Instruct',
|
||||||
|
'Qwen/Qwen2.5-32B-Instruct',
|
||||||
|
'Qwen/Qwen2.5-14B-Instruct',
|
||||||
|
'Qwen/Qwen2.5-7B-Instruct',
|
||||||
|
'Qwen/Qwen2.5-Math-72B-Instruct',
|
||||||
|
'Qwen/Qwen2.5-Coder-7B-Instruct',
|
||||||
|
'Qwen/Qwen2-72B-Instruct',
|
||||||
|
'Qwen/Qwen2-7B-Instruct',
|
||||||
|
'Qwen/Qwen2-1.5B-Instruct',
|
||||||
|
'Qwen/Qwen2-57B-A14B-Instruct',
|
||||||
|
'TeleAI/TeleChat2',
|
||||||
|
'01-ai/Yi-1.5-34B-Chat-16K',
|
||||||
|
'01-ai/Yi-1.5-9B-Chat-16K',
|
||||||
|
'01-ai/Yi-1.5-6B-Chat',
|
||||||
|
'THUDM/chatglm3-6b',
|
||||||
|
'THUDM/glm-4-9b-chat',
|
||||||
|
'Vendor-A/Qwen/Qwen2-72B-Instruct',
|
||||||
|
'Vendor-A/Qwen/Qwen2.5-72B-Instruct',
|
||||||
|
'internlm/internlm2_5-7b-chat',
|
||||||
|
'internlm/internlm2_5-20b-chat',
|
||||||
|
'OpenGVLab/InternVL2-Llama3-76B',
|
||||||
|
'OpenGVLab/InternVL2-26B',
|
||||||
|
'nvidia/Llama-3.1-Nemotron-70B-Instruct',
|
||||||
|
'meta-llama/Meta-Llama-3.1-405B-Instruct',
|
||||||
|
'meta-llama/Meta-Llama-3.1-70B-Instruct',
|
||||||
|
'meta-llama/Meta-Llama-3.1-8B-Instruct',
|
||||||
|
'meta-llama/Meta-Llama-3-8B-Instruct',
|
||||||
|
'meta-llama/Meta-Llama-3-70B-Instruct',
|
||||||
|
'google/gemma-2-27b-it',
|
||||||
|
'google/gemma-2-9b-it',
|
||||||
|
'Pro/Qwen/Qwen2.5-7B-Instruct',
|
||||||
|
'Pro/Qwen/Qwen2-7B-Instruct',
|
||||||
|
'Pro/Qwen/Qwen2-1.5B-Instruct',
|
||||||
|
'Pro/Qwen/Qwen2-VL-7B-Instruct',
|
||||||
|
'Pro/01-ai/Yi-1.5-9B-Chat-16K',
|
||||||
|
'Pro/01-ai/Yi-1.5-6B-Chat',
|
||||||
|
'Pro/THUDM/chatglm3-6b',
|
||||||
|
'Pro/THUDM/glm-4-9b-chat',
|
||||||
|
'Pro/internlm/internlm2_5-7b-chat',
|
||||||
|
'Pro/OpenGVLab/InternVL2-8B',
|
||||||
|
'Pro/meta-llama/Meta-Llama-3-8B-Instruct',
|
||||||
|
'Pro/meta-llama/Meta-Llama-3.1-8B-Instruct',
|
||||||
|
'Pro/google/gemma-2-9b-it',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.YI,
|
||||||
|
name: '零一万物',
|
||||||
|
models: [
|
||||||
|
'yi-lightning',
|
||||||
|
'yi-large',
|
||||||
|
'yi-medium',
|
||||||
|
'yi-medium-200k',
|
||||||
|
'yi-spark',
|
||||||
|
'yi-large-rag',
|
||||||
|
'yi-large-turbo',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.SPARK,
|
||||||
|
name: '讯飞星火',
|
||||||
|
// models: ['lite', 'generalv3', 'pro-128k', 'generalv3.5', 'max-32k', '4.0Ultra'],
|
||||||
|
models: [
|
||||||
|
{ label: 'Spark Lite', value: 'lite' },
|
||||||
|
{ label: 'Spark Pro', value: 'generalv3' },
|
||||||
|
{ label: 'Spark Pro-128K', value: 'pro-128k' },
|
||||||
|
{ label: 'Spark Max', value: 'generalv3.5' },
|
||||||
|
{ label: 'Spark Max-32K', value: 'max-32k' },
|
||||||
|
{ label: 'Spark4.0 Ultra', value: '4.0Ultra' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.OLLAMA,
|
||||||
|
name: 'Ollama',
|
||||||
|
models: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.AZURE_OPENAI,
|
||||||
|
name: 'Azure OpenAI',
|
||||||
|
models: ['gpt-3.5-turbo', 'gpt-4', 'gpt-4-32k', 'gpt-4-turbo', 'gpt-4o'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.GEMINI,
|
||||||
|
name: 'Gemini',
|
||||||
|
models: ['gemini-1.5-pro'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.CLAUDE,
|
||||||
|
name: 'Claude',
|
||||||
|
models: ['claude-3-opus', 'claude-3-opus-20240229', 'claude-3-sonnet', 'claude-3-haiku'],
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,37 @@
|
||||||
|
export enum ProviderEnum {
|
||||||
|
OPENAI = 'OPENAI',
|
||||||
|
AZURE_OPENAI = 'AZURE_OPENAI',
|
||||||
|
GEMINI = 'GEMINI',
|
||||||
|
OLLAMA = 'OLLAMA',
|
||||||
|
CLAUDE = 'CLAUDE',
|
||||||
|
Q_FAN = 'Q_FAN',
|
||||||
|
Q_WEN = 'Q_WEN',
|
||||||
|
ZHIPU = 'ZHIPU',
|
||||||
|
GITEEAI = 'GITEEAI',
|
||||||
|
DEEPSEEK = 'DEEPSEEK',
|
||||||
|
DOUYIN = 'DOUYIN',
|
||||||
|
SILICON = 'SILICON',
|
||||||
|
YI = 'YI',
|
||||||
|
SPARK = 'SPARK',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModels(provider: string, providers: Array<any>) {
|
||||||
|
const arr = providers.filter((i) => i.model === provider);
|
||||||
|
if (arr.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (typeof arr[0].models[0] === 'string') {
|
||||||
|
return arr[0].models.map((i) => {
|
||||||
|
return {
|
||||||
|
label: i,
|
||||||
|
value: i,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return arr[0].models;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTitle(provider: string, providers: Array<any>) {
|
||||||
|
return providers.filter((i) => i.model === provider)[0].name;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,185 +1,125 @@
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Table } from '@/components/Table';
|
||||||
|
import { Delete, Edit, Plus } from '@element-plus/icons-vue';
|
||||||
|
import editCom from './edit.vue';
|
||||||
|
import { computed, h, nextTick, reactive, ref } from 'vue';
|
||||||
|
import { getColumns } from './composables/columns.ts';
|
||||||
|
import { LLMProviders } from './composables/consts.ts';
|
||||||
|
// import { del, list as getModels } from '@/api/aigc/model';
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
import {FormSchema} from "@/types/form";
|
||||||
|
// import { ModelTypeEnum } from '@/api/models';
|
||||||
|
import { ProviderEnum } from './composables/provider.ts';
|
||||||
|
const formData = ref({
|
||||||
|
provider: ProviderEnum.OPENAI
|
||||||
|
});
|
||||||
|
const message = ElMessage;
|
||||||
|
const dialog = ElMessageBox;
|
||||||
|
const actionRef = ref();
|
||||||
|
const editRef = ref();
|
||||||
|
const tableData = ref([
|
||||||
|
])
|
||||||
|
const shema = ref([
|
||||||
|
{
|
||||||
|
label: '模型名称',
|
||||||
|
field: 'provider',
|
||||||
|
component: 'Select',
|
||||||
|
colProps: {
|
||||||
|
span: 6
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
style: {
|
||||||
|
width: '150px',
|
||||||
|
},
|
||||||
|
options: LLMProviders.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.model,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
]) as FormSchema[]
|
||||||
|
const actionColumn = reactive({
|
||||||
|
width: 100,
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
align: 'center',
|
||||||
|
render(record: any) {
|
||||||
|
return h(TableAction as any, {
|
||||||
|
style: 'text',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'info',
|
||||||
|
icon: Edit,
|
||||||
|
onClick: handleEdit.bind(null, record),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'error',
|
||||||
|
icon: Delete,
|
||||||
|
onClick: handleDel.bind(null, record),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = computed(() => {
|
||||||
|
console.log(formData.value.provider);
|
||||||
|
nextTick();
|
||||||
|
return getColumns(formData.value.provider);
|
||||||
|
});
|
||||||
|
const loadDataTable = async (params: any) => {
|
||||||
|
if (formData.value.provider === '') {
|
||||||
|
formData.value.provider = LLMProviders[0].model;
|
||||||
|
}
|
||||||
|
return await getModels({ ...params, provider: formData.value.provider, type: ModelTypeEnum.CHAT });
|
||||||
|
};
|
||||||
|
async function handleAdd() {
|
||||||
|
editRef.value.show({ provider: formData.value.provider });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEdit(record: any) {
|
||||||
|
editRef.value.show(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadTable() {
|
||||||
|
actionRef.value.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDel(record: any) {
|
||||||
|
dialog.warning({
|
||||||
|
title: '警告',
|
||||||
|
message: `你确定删除 [${record.name}] 模型吗?删除之后不可再用该模型对话`,
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '不确定',
|
||||||
|
type: 'warning',
|
||||||
|
}).then(async () => {
|
||||||
|
await del(record.id);
|
||||||
|
reloadTable();
|
||||||
|
message.success('模型删除成功');
|
||||||
|
}).catch(() => {
|
||||||
|
// 取消删除
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<content-wrap>
|
||||||
|
<Search :schema="shema" :model="formData" @search="({ provider}) => formData.provider = provider"/>
|
||||||
|
</content-wrap>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<el-alert
|
||||||
<el-form
|
class="w-full mb-10px min-alert"
|
||||||
class="-mb-15px"
|
title="对于完全适配OpenAI接口格式的模型都可在OpenAI中配置(只需要定义BaseUrl)"
|
||||||
:model="queryParams"
|
type="info"
|
||||||
ref="queryFormRef"
|
show-icon
|
||||||
:inline="true"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="模型名字" prop="name">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.name"
|
|
||||||
placeholder="请输入模型名字"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
<el-button class="my-10px" type="primary" :icon="Plus">新增模型</el-button>
|
||||||
<el-form-item label="模型标识" prop="model">
|
<Table height="cacl(100% - 400px)" border :columns="columns" :data="tableData.concat(tableData)" :pagination="false"/>
|
||||||
<el-input
|
<editCom ref="editRef" :provider="provider" @reload="reloadTable" />
|
||||||
v-model="queryParams.model"
|
|
||||||
placeholder="请输入模型标识"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="模型平台" prop="platform">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.platform"
|
|
||||||
placeholder="请输入模型平台"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
|
||||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openForm('create')"
|
|
||||||
v-hasPermi="['ai:chat-model:create']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
|
||||||
<ContentWrap>
|
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
|
||||||
<el-table-column label="所属平台" align="center" prop="platform">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.AI_PLATFORM" :value="scope.row.platform" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="模型名字" align="center" prop="name" />
|
|
||||||
<el-table-column label="模型标识" align="center" prop="model" />
|
|
||||||
<el-table-column label="API 秘钥" align="center" prop="keyId" min-width="140">
|
|
||||||
<template #default="scope">
|
|
||||||
<span>{{ apiKeyList.find((item) => item.id === scope.row.keyId)?.name }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="排序" align="center" prop="sort" />
|
|
||||||
<el-table-column label="状态" align="center" prop="status">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="温度参数" align="center" prop="temperature" />
|
|
||||||
<el-table-column label="回复数 Token 数" align="center" prop="maxTokens" min-width="140" />
|
|
||||||
<el-table-column label="上下文数量" align="center" prop="maxContexts" />
|
|
||||||
<el-table-column label="操作" align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
@click="openForm('update', scope.row.id)"
|
|
||||||
v-hasPermi="['ai:chat-model:update']"
|
|
||||||
>
|
|
||||||
编辑
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
@click="handleDelete(scope.row.id)"
|
|
||||||
v-hasPermi="['ai:chat-model:delete']"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<!-- 分页 -->
|
|
||||||
<Pagination
|
|
||||||
:total="total"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
|
||||||
<ChatModelForm ref="formRef" @success="getList" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<style lang="less" scoped></style>
|
||||||
import { ChatModelApi, ChatModelVO } from '@/api/ai/model/chatModel'
|
|
||||||
import ChatModelForm from './ChatModelForm.vue'
|
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
|
||||||
import { ApiKeyApi, ApiKeyVO } from '@/api/ai/model/apiKey'
|
|
||||||
|
|
||||||
/** API 聊天模型 列表 */
|
|
||||||
defineOptions({ name: 'AiChatModel' })
|
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
|
||||||
const { t } = useI18n() // 国际化
|
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
|
||||||
const list = ref<ChatModelVO[]>([]) // 列表的数据
|
|
||||||
const total = ref(0) // 列表的总页数
|
|
||||||
const queryParams = reactive({
|
|
||||||
pageNo: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
name: undefined,
|
|
||||||
model: undefined,
|
|
||||||
platform: undefined
|
|
||||||
})
|
|
||||||
const queryFormRef = ref() // 搜索的表单
|
|
||||||
const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表
|
|
||||||
|
|
||||||
/** 查询列表 */
|
|
||||||
const getList = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const data = await ChatModelApi.getChatModelPage(queryParams)
|
|
||||||
list.value = data.list
|
|
||||||
total.value = data.total
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
|
||||||
const handleQuery = () => {
|
|
||||||
queryParams.pageNo = 1
|
|
||||||
getList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
|
||||||
const resetQuery = () => {
|
|
||||||
queryFormRef.value.resetFields()
|
|
||||||
handleQuery()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
|
||||||
const formRef = ref()
|
|
||||||
const openForm = (type: string, id?: number) => {
|
|
||||||
formRef.value.open(type, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
|
||||||
const handleDelete = async (id: number) => {
|
|
||||||
try {
|
|
||||||
// 删除的二次确认
|
|
||||||
await message.delConfirm()
|
|
||||||
// 发起删除
|
|
||||||
await ChatModelApi.deleteChatModel(id)
|
|
||||||
message.success(t('common.delSuccess'))
|
|
||||||
// 刷新列表
|
|
||||||
await getList()
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 初始化 **/
|
|
||||||
onMounted(async () => {
|
|
||||||
getList()
|
|
||||||
// 获得下拉数据
|
|
||||||
apiKeyList.value = await ApiKeyApi.getApiKeySimpleList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {FormSchema} from "@/types/form";
|
||||||
|
import {ElTag} from "element-plus";
|
||||||
|
|
||||||
|
export enum ProviderEnum {
|
||||||
|
Redis = 'REDIS',
|
||||||
|
PgVector = 'PGVECTOR',
|
||||||
|
Milvus = 'MILVUS',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const ProviderConst = [
|
||||||
|
{ label: 'Redis', value: ProviderEnum.Redis },
|
||||||
|
{ label: 'PgVector', value: ProviderEnum.PgVector },
|
||||||
|
{ label: 'Milvus', value: ProviderEnum.Milvus },
|
||||||
|
];
|
||||||
|
function getProviderLabel(value: any) {
|
||||||
|
const arr = ProviderConst.filter((i) => i.value === value);
|
||||||
|
if (arr === undefined || arr.length === 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return arr[0].label;
|
||||||
|
}
|
||||||
|
const shema = ref<FormSchema[]>([
|
||||||
|
{
|
||||||
|
label: '模型名称',
|
||||||
|
field: 'name',
|
||||||
|
component: 'Input',
|
||||||
|
colProps: {
|
||||||
|
span: 6
|
||||||
|
},
|
||||||
|
}
|
||||||
|
])
|
||||||
|
const columns = ref<object[]>([
|
||||||
|
{
|
||||||
|
label: '数据库别名',
|
||||||
|
field: 'name',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '供应商',
|
||||||
|
field: 'provider',
|
||||||
|
align: 'center',
|
||||||
|
width: '120',
|
||||||
|
render(row) {
|
||||||
|
return h(
|
||||||
|
ElTag,
|
||||||
|
{
|
||||||
|
type: 'success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => getProviderLabel(row.provider),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '向量纬度',
|
||||||
|
field: 'dimension',
|
||||||
|
align: 'center',
|
||||||
|
width: '80',
|
||||||
|
render(row) {
|
||||||
|
return h(
|
||||||
|
ElTag,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => row.dimension,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '数据库地址',
|
||||||
|
field: 'host',
|
||||||
|
align: 'center',
|
||||||
|
width: '110',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '数据库端口',
|
||||||
|
field: 'port',
|
||||||
|
align: 'center',
|
||||||
|
width: '100',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '数据库用户名',
|
||||||
|
field: 'username',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '数据库密码',
|
||||||
|
field: 'password',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '数据库名',
|
||||||
|
field: 'databaseName',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '表名称',
|
||||||
|
field: 'tableName',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
const tableData = ref([])
|
||||||
|
return {
|
||||||
|
ProviderConst,
|
||||||
|
getProviderLabel,
|
||||||
|
shema,
|
||||||
|
columns,
|
||||||
|
tableData
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {Plus} from "@element-plus/icons-vue";
|
||||||
|
import {Table} from "@/components/Table";
|
||||||
|
import useEmbedStore from './composables'
|
||||||
|
const { shema, columns, tableData } = useEmbedStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<content-wrap>
|
||||||
|
<Search :schema="shema"/>
|
||||||
|
</content-wrap>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-alert
|
||||||
|
class="w-full mb-10px min-alert"
|
||||||
|
title="注意:请慎重修改模型的向量纬度参数(Dimension),此参数需要和向量库匹配(错误修改可能将影响已有的向量数据)"
|
||||||
|
type="info"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
|
<el-button class="my-10px" type="primary" :icon="Plus">新增向量数据库</el-button>
|
||||||
|
<Table height="cacl(100% - 400px)" border :columns="columns" :data="tableData.concat(tableData)" :pagination="false"/>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,102 @@
|
||||||
|
import {ElTag} from "element-plus";
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {ProviderEnum} from "@/views/ai/model/chatModel/composables/provider";
|
||||||
|
import {FormSchema} from "@/types/form";
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const LLMProviders: any[] = [
|
||||||
|
{
|
||||||
|
model: ProviderEnum.OPENAI,
|
||||||
|
name: 'OpenAI',
|
||||||
|
models: ['text-embedding-3-small', 'text-embedding-3-large', 'text-embedding-ada-002'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.Q_FAN,
|
||||||
|
name: '百度千帆',
|
||||||
|
models: ['bge-large-zh', 'bge-large-en', 'tao-8k'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.Q_WEN,
|
||||||
|
name: '阿里百炼',
|
||||||
|
models: ['text-embedding-v3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.ZHIPU,
|
||||||
|
name: '智谱清言',
|
||||||
|
models: ['embedding-2', 'embedding-3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.DOUYIN,
|
||||||
|
name: '抖音豆包',
|
||||||
|
models: ['text-240715', 'text-240515'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.OLLAMA,
|
||||||
|
name: 'Ollama',
|
||||||
|
models: ['text2vec-bge-large-chinese:latest'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const baseColumns = [
|
||||||
|
{
|
||||||
|
label: '模型别名',
|
||||||
|
field: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '模型版本',
|
||||||
|
field: 'model',
|
||||||
|
width: '160',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '向量纬度',
|
||||||
|
field: 'dimension',
|
||||||
|
align: 'center',
|
||||||
|
width: '100',
|
||||||
|
render(row) {
|
||||||
|
return h(
|
||||||
|
ElTag,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => row.dimension,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Api Key',
|
||||||
|
field: 'apiKey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Base Url',
|
||||||
|
field: 'baseUrl',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const tableData = ref([
|
||||||
|
])
|
||||||
|
const shema = ref<FormSchema[]>([
|
||||||
|
{
|
||||||
|
label: '模型名称',
|
||||||
|
field: 'name',
|
||||||
|
component: 'Select',
|
||||||
|
colProps: {
|
||||||
|
span: 6
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
style: {
|
||||||
|
width: '150px',
|
||||||
|
},
|
||||||
|
options: LLMProviders.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.model,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
])
|
||||||
|
return {
|
||||||
|
baseColumns,
|
||||||
|
shema,
|
||||||
|
tableData
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import {Plus} from "@element-plus/icons-vue";
|
||||||
|
import usePage from './composables/index'
|
||||||
|
const {shema, baseColumns: columns, tableData} = usePage()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<content-wrap>
|
||||||
|
<Search :schema="shema"/>
|
||||||
|
</content-wrap>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-alert
|
||||||
|
class="w-full mb-10px min-alert"
|
||||||
|
title="注意:为了实现向量数据库的动态切换,这里Embedding供应商统一选择支持1024纬度的模型"
|
||||||
|
type="info"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
|
<el-button class="my-10px" type="primary" :icon="Plus">新增向量模型</el-button>
|
||||||
|
<Table height="cacl(100% - 400px)" border :columns="columns" :data="tableData.concat(tableData)" :pagination="false"/>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,110 @@
|
||||||
|
export default function () {
|
||||||
|
enum ProviderEnum {
|
||||||
|
OPENAI = 'OPENAI',
|
||||||
|
AZURE_OPENAI = 'AZURE_OPENAI',
|
||||||
|
ZHIPU = 'ZHIPU',
|
||||||
|
}
|
||||||
|
|
||||||
|
const LLMProviders: any[] = [
|
||||||
|
{
|
||||||
|
model: ProviderEnum.OPENAI,
|
||||||
|
name: 'OpenAI',
|
||||||
|
models: ['dall-e-2', 'dall-e-3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.AZURE_OPENAI,
|
||||||
|
name: 'Azure OpenAI',
|
||||||
|
models: ['dall-e-2', 'dall-e-3'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: ProviderEnum.ZHIPU,
|
||||||
|
name: '智谱清言',
|
||||||
|
models: ['cogview-3'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const formData = ref({
|
||||||
|
provider: ProviderEnum.OPENAI,
|
||||||
|
});
|
||||||
|
const tableData = ref([])
|
||||||
|
const shema = ref([
|
||||||
|
{
|
||||||
|
field: 'provider',
|
||||||
|
label: '模型别名',
|
||||||
|
component: 'Select',
|
||||||
|
required: true,
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入模型别名',
|
||||||
|
style: {
|
||||||
|
width: '180px',
|
||||||
|
},
|
||||||
|
options: LLMProviders.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.model,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const baseColumns = [
|
||||||
|
{
|
||||||
|
label: '模型别名',
|
||||||
|
field: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '模型版本',
|
||||||
|
field: 'model',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const openaiColumns = [
|
||||||
|
...baseColumns,
|
||||||
|
{
|
||||||
|
label: 'Api Key',
|
||||||
|
field: 'apiKey',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const azureOpenaiColumns = [
|
||||||
|
...baseColumns,
|
||||||
|
{
|
||||||
|
label: 'Api Key',
|
||||||
|
field: 'apiKey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Endpoint',
|
||||||
|
field: 'endpoint',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Deployment Name',
|
||||||
|
field: 'azureDeploymentName',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const zhipuColumns = [...baseColumns];
|
||||||
|
function getColumns(provider: string) {
|
||||||
|
console.log(provider);
|
||||||
|
switch (provider) {
|
||||||
|
case ProviderEnum.OPENAI: {
|
||||||
|
return openaiColumns;
|
||||||
|
}
|
||||||
|
case ProviderEnum.AZURE_OPENAI: {
|
||||||
|
return azureOpenaiColumns;
|
||||||
|
}
|
||||||
|
case ProviderEnum.ZHIPU: {
|
||||||
|
return zhipuColumns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const columns = computed(() => {
|
||||||
|
nextTick();
|
||||||
|
return getColumns(formData.value.provider)
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
LLMProviders,
|
||||||
|
columns,
|
||||||
|
tableData,
|
||||||
|
shema,
|
||||||
|
formData
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import {Plus} from "@element-plus/icons-vue";
|
||||||
|
import usePage from './composables/index'
|
||||||
|
const {shema, columns, tableData, formData} = usePage()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<content-wrap>
|
||||||
|
<Search :schema="shema" :model="formData" @search="(model) => formData.provider = model.provider"/>
|
||||||
|
</content-wrap>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-alert
|
||||||
|
class="w-full mb-10px min-alert"
|
||||||
|
title="鉴于很多模型的文生图效果很差甚至没有,这里只建议使用OpenAI的DALL-E模型"
|
||||||
|
type="info"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
|
<el-button class="my-10px" type="primary" :icon="Plus">新增向量模型</el-button>
|
||||||
|
<Table height="cacl(100% - 400px)" border :columns="columns" :data="tableData.concat(tableData)" :pagination="false"/>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
Loading…
Reference in New Issue