fetch: 页面移植

会话模型、向量模型、文生图模型、向量数据库
This commit is contained in:
杨谢雨 2025-02-18 16:51:11 +08:00
parent f62e8d4cea
commit eb5af0eec6
15 changed files with 1081 additions and 204 deletions

View File

@ -117,7 +117,10 @@ export default defineComponent({
const getElFormRef = (): ComponentRef<typeof ElForm> => {
return unref(elFormRef) as ComponentRef<typeof ElForm>
}
const clearForm = () => {
formModel.value = {}
getElFormRef().resetFields()
}
expose({
setValues,
formModel,
@ -125,7 +128,8 @@ export default defineComponent({
delSchema,
addSchema,
setSchema,
getElFormRef
getElFormRef,
clearForm
})
// formModel

View File

@ -116,3 +116,9 @@ export const isImgPath = (path: string): boolean => {
export const isEmptyVal = (val: any): boolean => {
return val === '' || val === null || val === undefined
}
export function isWhitespace(val: unknown) {
return val === '';
}
export function isNullOrWhitespace(val: unknown) {
return isNullOrUnDef(val) || isWhitespace(val);
}

View File

@ -0,0 +1,204 @@
/*
* 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 { FormSchema } from '@/components/Form';
import { LLMProviders } from './consts';
import { getModels, ProviderEnum } from './provider';
// import { ModelTypeEnum } from '@/api/models';
import {FormSchema} from "@/types/form";
import { isNullOrWhitespace } from '@/utils/is';
const baseSchemas: FormSchema[] = [
// {
// field: 'id',
// label: 'ID',
// component: 'Input',
// },
// {
// field: 'type',
// label: 'type',
// component: 'Input',
// // value: ModelTypeEnum.CHAT,
// },
{
field: 'provider',
label: 'LLM供应商',
component: 'Select',
// isHidden: true,
componentProps: {
placeholder: 'LLM供应商',
options: LLMProviders,
labelField: 'name',
valueField: 'model',
},
// rules: [{ required: true, message: '请选择LLM供应商', trigger: ['blur'] }],
},
{
field: 'name',
label: '模型别名',
component: 'Input',
formItemProps: {
rules: [{ required: true, message: '请输入模型别名', trigger: ['blur'] }]
},
componentProps: {
placeholder: '请输入模型别名',
},
},
{
field: 'apiKey',
label: 'Api Key',
labelMessage: '模型的ApiKey',
component: 'Input',
// rules: [{ required: true, message: '请输入API Key', trigger: ['blur'] }],
componentProps: {
placeholder: '请输入Api Key',
},
},
{
field: 'responseLimit',
label: '回复上限',
labelMessage: '控制模型输出的Tokens长度上限。通常 100 Tokens 约等于150个中文汉字',
component: 'Slider',
formItemProps:{
rules: [{ type: 'number', required: true, message: '请输入回复上限', trigger: ['blur'] }]
},
componentProps: {
showTooltip: true,
value: 2000,
step: 1,
min: 1,
max: 8192,
},
},
{
field: 'temperature',
label: '生成随机性',
labelMessage: '调高参数会使得模型的输出更多样性和创新性,反之降低参数将会减少多样性',
component: 'Slider',
formItemProps: {
rules: [{ type: 'number', required: true, message: '请输入生成随机性', trigger: ['blur'] }]
},
value:0.2,
componentProps: {
showTooltip: true,
step: 0.05,
min: 0,
max: 2,
},
},
{
field: 'topP',
label: 'Top P',
labelMessage:
'模型在生成输出时会从概率最高的词汇开始选择直到这些词汇的总概率累积达到Top p值。这样可以限制模型只选择这些高概率的词汇从而控制输出内容的多样性。建议不要与“生成随机性“同时调整',
component: 'Slider',
formItemProps: {
rules: [{ type: 'number', required: true, message: '请输入', trigger: ['blur'] }]
},
componentProps: {
showTooltip: true,
value: 0.8,
step: 0.1,
min: 0,
max: 1,
},
},
];
export function getSchemas(provider: string) {
const list = JSON.parse(JSON.stringify(baseSchemas));
console.log(provider);
const modelSchema: any = {
field: 'model',
label: '模型版本',
labelMessage: '该LLM供应商对应的模型版本号',
component: 'Select',
rules: [{ required: true, message: '请选择模型', trigger: ['blur'] }],
componentProps: {
placeholder: '请选择模型版本(可以手动输入)',
filterable: true,
tag: true,
options: getModels(provider, LLMProviders),
},
};
list.splice(1, 0, modelSchema);
let value: any = undefined;
let labelMessage: any = '模型的基础请求URL地址或中转地址';
let disabled = false;
switch (provider) {
case ProviderEnum.GITEEAI:
disabled = true;
value = 'https://ai.gitee.com/v1';
labelMessage = '对于Gitee AI此Url固定不可修改';
break;
case ProviderEnum.DEEPSEEK:
disabled = true;
value = 'https://api.deepseek.com/v1';
labelMessage = '对于DeepSeek模型此Url固定不可修改';
break;
case ProviderEnum.SILICON:
disabled = true;
value = 'https://api.siliconflow.cn/v1';
labelMessage = '对于硅基流动模型此Url固定不可修改';
break;
case ProviderEnum.DOUYIN:
disabled = true;
value = 'https://ark.cn-beijing.volces.com/api/v3';
labelMessage = '对于抖音豆包模型此Url固定不可修改';
break;
case ProviderEnum.YI:
disabled = true;
value = 'https://api.lingyiwanwu.com/v1';
labelMessage = '对于零一模型此Url固定不可修改';
break;
case ProviderEnum.SPARK:
disabled = true;
value = 'https://spark-api-open.xf-yun.com/v1';
labelMessage = '对于讯飞星火大模型此Url固定不可修改';
break;
}
const baseUlrSchema: any = {
field: 'baseUrl',
label: 'Base Url',
labelMessage,
component: 'Input',
value,
componentProps: {
disabled,
placeholder: '请输入BaseUrl',
},
rules: [
{
required: false,
trigger: ['blur'],
validator: (_, value: string) => {
if (!value) {
return;
}
const urlRegex =
/^(https?:\/\/)?((([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|localhost|(\d{1,3}\.){3}\d{1,3})(:\d{1,5})?(\/.*)?)$/;
if (isNullOrWhitespace(value) || urlRegex.test(value)) {
return true;
}
return new Error('URL格式错误');
},
},
],
};
list.splice(3, 0, baseUlrSchema);
return list;
}

View File

@ -1,9 +1,39 @@
<script setup lang="ts">
import {Form} from '@/components/Form'
import {ref} from 'vue'
import {FormSchema} from "@/types/form";
import {getSchemas} from "@/views/ai/model/chatModel/composables/schemas";
const visible = ref(false)
const formData = ref({})
const formRef = ref()
const schemas = ref([])
const close = () => {
visible.value = false
formRef.value.clearForm()
}
const show = async (data: object) => {
visible.value = true
await nextTick()
formRef.value.setValues(data)
schemas.value = (getSchemas(data.provider) as FormSchema[]).splice(1);
formRef.value.setSchema(schemas.value)
// models.value = getModels(data.provider, LLMProviders)
}
defineExpose({
show,
close
})
</script>
<template>
<div></div>
<el-dialog v-model="visible" draggable title="编辑" width="800px" @close="close">
<Form ref="formRef" :model="formData" :schema="schemas" />
<template #footer>
<el-button type="primary">确认</el-button>
<el-button @click="close">取消</el-button>
</template>
</el-dialog>
</template>
<style scoped lang="scss">

View File

@ -1,16 +1,17 @@
<script lang="ts" setup>
import { Table } from '@/components/Table';
import { Delete, Edit, Plus } from '@element-plus/icons-vue';
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 {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 {ElMessage, ElMessageBox} from 'element-plus';
import {FormSchema} from "@/types/form";
// import { ModelTypeEnum } from '@/api/models';
import { ProviderEnum } from './composables/provider.ts';
import {getModels, ProviderEnum} from './composables/provider.ts';
const formData = ref({
provider: ProviderEnum.OPENAI
});
@ -19,65 +20,75 @@ 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),
},
],
});
name: '1111'
},
});
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
}, {
name: '1111'
},
{
name: '1111'
}
])
// 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 });
// 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 addModel() {
console.log(formData.value.provider);
editRef.value.show({provider: formData.value.provider});
}
function handleEdit(record: any) {
@ -106,20 +117,63 @@ function handleDel(record: any) {
</script>
<template>
<content-wrap>
<Search :schema="shema" :model="formData" @search="({ provider}) => formData.provider = provider"/>
</content-wrap>
<!-- <content-wrap>-->
<!-- <el-button v-for="(item,index) in LLMProviders" :key="index" @click="formData.provider = item.model">{{ item.name }}</el-button>-->
<!-- </content-wrap>-->
<ContentWrap>
<el-alert
class="w-full mb-10px min-alert"
title="对于完全适配OpenAI接口格式的模型都可在OpenAI中配置只需要定义BaseUrl"
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"/>
<editCom ref="editRef" :provider="provider" @reload="reloadTable" />
<div class="flex children">
<el-scrollbar class="h-full w-300px pl-10px pr-20px">
<div
v-for="(item,index) in LLMProviders" :key="index"
:class="{active: formData.provider === item.model}" class="menu"
@click="formData.provider = item.model">
<span>{{ item.name }}</span>
</div>
</el-scrollbar>
<div class="h-full flex-1 px-20px">
<el-alert
class="w-full mb-10px min-alert"
title="对于完全适配OpenAI接口格式的模型都可在OpenAI中配置只需要定义BaseUrl"
type="warning"
show-icon
/>
<el-button :icon="Plus" class="my-10px" type="primary" @click="addModel">新增模型</el-button>
<Table class="table-wrapper" height="100%" border :columns="columns" :data="tableData.concat(tableData)" :pagination="false"/>
<editCom ref="editRef" @reload="reloadTable"/>
</div>
</div>
</ContentWrap>
</template>
<style lang="less" scoped></style>
<style lang="scss" scoped>
.children {
height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - (var(--app-content-padding) * 3)) !important;
box-sizing: border-box;
& > div:nth-child(2) {
width: calc(100% - 300px);
}
}
.table-wrapper {
height: calc(100% - 100px);
}
.menu {
transition: all .15s;
cursor: pointer;
padding: 12px 10px;
border-radius: 5px;
margin-bottom: 20px;
&.active {
color: #ffffff;
background-color: var(--el-color-primary-light-3);
}
&:hover {
&:not(&.active) {
background-color: var(--el-color-info-light-7);
}
}
}
</style>

View File

@ -0,0 +1,34 @@
import {ProviderEnum} from "@/views/ai/model/chatModel/composables/provider";
export 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'],
},
];

View File

@ -1,49 +1,20 @@
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 formData = ref({
provider: ProviderEnum.OPENAI
})
const editRef = ref()
const baseColumns = [
{
label: '模型别名',
field: 'name',
field: 'name',
},
{
label: '模型版本',
field: 'model',
field: 'model',
width: '160',
},
{
@ -65,38 +36,22 @@ export default function () {
},
{
label: 'Api Key',
field: 'apiKey',
field: 'apiKey',
},
{
label: 'Base Url',
field: 'baseUrl',
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,
})),
},
}
])
const tableData = ref([])
const open = () => {
editRef.value.show({provider: formData.value.provider});
}
return {
baseColumns,
shema,
tableData
tableData,
formData,
editRef,
open
}
}

View File

@ -0,0 +1,138 @@
import {FormSchema} from "@/types/form";
// import { ModelTypeEnum } from '@/api/models';
import {getModels, ProviderEnum} from "@/views/ai/model/chatModel/composables/provider";
import {LLMProviders} from "@/views/ai/model/embedding/composables/consts";
// import { LLMProviders } from './consts';
const baseSchemas: FormSchema[] = [
// {
// field: 'id',
// label: 'ID',
// component: 'Input',
// isHidden: true,
// },
// {
// field: 'type',
// label: 'type',
// component: 'Input',
// isHidden: true,
// value: ModelTypeEnum.EMBEDDING,
// },
{
field: 'provider',
label: 'LLM供应商',
component: 'Select',
componentProps: {
placeholder: '请选择LLM供应商',
options: LLMProviders,
},
},
{
field: 'name',
label: '模型别名',
component: 'Input',
formItemProps: {
rules: [{ required: true, message: '请输入模型别名', trigger: ['blur'] }]
},
componentProps: {
placeholder: '请输入模型别名',
},
},
{
field: 'apiKey',
label: 'Api Key',
labelMessage: '模型的ApiKey',
component: 'Input',
// rules: [{ required: true, message: '请输入ApiKey', trigger: ['blur'] }],
componentProps: {
placeholder: '请输入ApiKey',
},
},
{
field: 'dimension',
label: '向量纬度',
component: 'Select',
value: 1024,
labelMessage: '慎重修改此参数,纬度高会消耗更多的算力,但纬度高并不代表搜索更精确',
componentProps: {
placeholder: '请输入向量纬度',
options: [
{
label: '512',
value: 512,
},
{
label: '768',
value: 768,
},
{
label: '1024',
value: 1024,
},
{
label: '1536',
value: 1536,
},
],
},
formItemProps:{
rules: [{ type: 'number', required: true, message: '请输入向量纬度', trigger: ['blur'] }]
},
},
];
export function getSchemas(provider: string) {
const list = JSON.parse(JSON.stringify(baseSchemas));
const modelSchema: any = {
field: 'model',
label: '模型版本',
labelMessage: '该LLM供应商对应的模型版本号',
component: 'Select',
rules: [{ required: true, message: '请选择模型', trigger: ['blur'] }],
componentProps: {
placeholder: '请选择模型版本',
filterable: true,
tag: true,
options: getModels(provider, LLMProviders),
},
};
list.splice(list.length, 0, modelSchema);
let value: any = undefined;
let labelMessage: any = '模型的基础请求URL地址或中转地址';
let disabled = false;
switch (provider) {
case ProviderEnum.DOUYIN:
disabled = true;
value = 'https://ark.cn-beijing.volces.com/api/v3';
labelMessage = '对于抖音豆包模型此Url固定不可修改';
break;
case ProviderEnum.Q_FAN:
disabled = true;
labelMessage = '对于百度千帆模型此Url固定不可修改';
break;
case ProviderEnum.Q_WEN:
disabled = true;
labelMessage = '对于阿里千问模型此Url固定不可修改';
break;
case ProviderEnum.ZHIPU:
disabled = true;
labelMessage = '对于智谱清言模型此Url固定不可修改';
break;
}
const baseUlrSchema: any = {
field: 'baseUrl',
label: 'Base Url',
labelMessage,
component: 'Input',
value,
componentProps: {
disabled,
placeholder: '请输入BaseUrl',
},
};
list.splice(list.length, 0, baseUlrSchema);
return list;
}

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
import {Form} from '@/components/Form'
import {ref} from 'vue'
import {FormSchema} from "@/types/form";
import {getSchemas} from "@/views/ai/model/embedding/composables/schemas";
const visible = ref(false)
const formData = ref({})
const formRef = ref()
const schemas = ref([])
const close = () => {
visible.value = false
formRef.value.clearForm()
}
const show = async (data: object) => {
visible.value = true
await nextTick()
formRef.value.setValues(data)
schemas.value = (getSchemas(data.provider) as FormSchema[]).splice(1);
formRef.value.setSchema(schemas.value)
// models.value = getModels(data.provider, LLMProviders)
}
defineExpose({
show,
close
})
</script>
<template>
<el-dialog v-model="visible" draggable title="编辑" width="800px" @close="close">
<Form ref="formRef" :model="formData" :schema="schemas" />
<template #footer>
<el-button type="primary">确认</el-button>
<el-button @click="close">取消</el-button>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
</style>

View File

@ -2,25 +2,68 @@
import {Plus} from "@element-plus/icons-vue";
import usePage from './composables/index'
const {shema, baseColumns: columns, tableData} = usePage()
import Edit from "@/views/ai/model/embedding/edit.vue";
import {LLMProviders} from "@/views/ai/model/embedding/composables/consts";
import {Table} from "@/components/Table";
const { baseColumns: columns, tableData, formData, editRef, open} = 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"/>
<div class="children flex">
<el-scrollbar class="h-full w-300px pl-10px pr-20px">
<div
v-for="(item,index) in LLMProviders" :key="index"
:class="{active: formData.provider === item.model}" class="menu"
@click="formData.provider = item.model">
<span>{{ item.name }}</span>
</div>
</el-scrollbar>
<div class="h-full flex-1 px-20px">
<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" @click="open">新增向量模型</el-button>
<Table class="table-wrapper" height="100%" border :columns="columns" :data="tableData.concat(tableData)" :pagination="false"/>
</div>
</div>
<Edit ref="editRef" />
</ContentWrap>
</template>
<style scoped lang="scss">
.children {
height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - (var(--app-content-padding) * 3)) !important;
box-sizing: border-box;
& > div:nth-child(2) {
width: calc(100% - 300px);
}
}
.table-wrapper {
height: calc(100% - 100px);
}
.menu {
transition: all .15s;
cursor: pointer;
padding: 12px 10px;
border-radius: 5px;
margin-bottom: 20px;
&.active {
color: #ffffff;
background-color: var(--el-color-primary-light-3);
}
&:hover {
&:not(&.active) {
background-color: var(--el-color-info-light-7);
}
}
}
</style>

View File

@ -0,0 +1,22 @@
export enum ProviderEnum {
OPENAI = 'OPENAI',
AZURE_OPENAI = 'AZURE_OPENAI',
ZHIPU = 'ZHIPU',
}
export 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'],
},
]

View File

@ -1,49 +1,12 @@
export default function () {
enum ProviderEnum {
OPENAI = 'OPENAI',
AZURE_OPENAI = 'AZURE_OPENAI',
ZHIPU = 'ZHIPU',
}
import {ProviderEnum} from "@/views/ai/model/image/composables/consts";
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'],
},
]
export default function () {
const editRef= ref()
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 = [
{
@ -100,11 +63,14 @@ export default function () {
nextTick();
return getColumns(formData.value.provider)
});
const open = () => {
editRef.value.show({provider: formData.value.provider});
}
return {
LLMProviders,
columns,
tableData,
shema,
formData
formData,
editRef,
open
}
}

View File

@ -0,0 +1,294 @@
/*
* 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 {FormSchema} from '@/types/form';
// import { LLMProviders, ProviderEnum } from './data';
// import { ModelTypeEnum } from '@/api/models';
import {isNullOrWhitespace} from '@/utils/is';
import {LLMProviders, ProviderEnum} from "@/views/ai/model/image/composables/consts";
const baseHeadSchemas: FormSchema[] = [
// {
// field: 'id',
// label: 'ID',
// component: 'Input',
// isHidden: true,
// },
// {
// field: 'type',
// label: 'type',
// component: 'Input',
// isHidden: true,
// defaultValue: ModelTypeEnum.TEXT_IMAGE,
// },
{
field: 'provider',
label: 'LLM供应商',
component: 'Select',
componentProps: {
options: LLMProviders,
labelField: 'name',
valueField: 'model',
},
formItemProps: {rules: [{required: true, message: '请选择LLM供应商', trigger: ['blur']}]},
},
{
field: 'name',
label: '模型别名',
component: 'Input',
formItemProps: {rules: [{required: true, message: '请输入模型别名', trigger: ['blur']}]},
},
];
const keySchemas: FormSchema[] = [
{
field: 'apiKey',
label: 'Api Key',
labelMessage: '模型链接的秘钥注意有些模型例如Gemini是本地认证方式则不是通过这种方式',
component: 'Input',
formItemProps: {rules: [{required: true, message: '请输入API Key', trigger: ['blur']}]},
},
{
field: 'baseUrl',
label: 'Base Url',
labelMessage: '注意对于大多数模型此参数仅代表中转地址但是对于Ollama这类本地模型则是必填的',
component: 'Input',
formItemProps: {
rules: [
{
required: false,
trigger: ['blur'],
validator: (_, value: string) => {
const urlRegex = /^(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(\/.*)?$/;
if (isNullOrWhitespace(value) || urlRegex.test(value)) {
return true;
}
return new Error('URL格式错误');
},
},
]
},
},
];
export const openaiSchemas: FormSchema[] = [
...baseHeadSchemas,
{
field: 'model',
label: '模型',
labelMessage: '该LLM供应商对应的模型版本号',
component: 'Select',
formItemProps: {
rules: [{required: true, message: '请选择模型', trigger: ['blur']}]
},
componentProps: {
filterable: true,
options: getModels(ProviderEnum.OPENAI),
},
},
{
field: 'imageSize',
label: '图片大小',
labelMessage: '生成图片的大小尺寸',
component: 'Select',
formItemProps: {
rules: [{required: true, message: '请选择图片大小', trigger: ['blur']}]
},
componentProps: {
options: [
{
label: '1024x1024',
value: '1024x1024',
},
{
label: '1024x1792',
value: '1024x1792',
},
{
label: '1792x1024',
value: '1792x1024',
},
],
},
},
{
field: 'imageQuality',
label: '图片质量',
labelMessage: '生成图片的质量',
component: 'Select',
formItemProps: {
rules: [{required: true, message: '请选择图片的质量', trigger: ['blur']}]
},
componentProps: {
options: [
{
label: 'standard',
value: 'standard',
},
{
label: 'hd',
value: 'hd',
},
],
},
},
{
field: 'imageStyle',
label: '图片风格',
labelMessage: '生成图片的风格',
component: 'Select',
formItemProps: {
rules: [{required: true, message: '请选择图片的风格', trigger: ['blur']}]
},
componentProps: {
options: [
{
label: 'vivid',
value: 'vivid',
},
{
label: 'natural',
value: 'natural',
},
],
},
},
...keySchemas,
];
export const azureOpenaiSchemas: FormSchema[] = [
...baseHeadSchemas,
{
field: 'model',
label: '模型',
labelMessage: '该LLM供应商对应的模型版本号',
component: 'Select',
formItemProps: {
rules: [{required: true, message: '请选择模型', trigger: ['blur']}]
},
componentProps: {
filterable: true,
options: getModels(ProviderEnum.AZURE_OPENAI),
},
},
{
field: 'imageSize',
label: '图片大小',
labelMessage: '生成图片的大小尺寸',
component: 'Select',
formItemProps: {
rules: [{required: true, message: '请选择图片大小', trigger: ['blur']}]
},
componentProps: {
options: [
{
label: '1024x1024',
value: '1024x1024',
},
{
label: '1024x1792',
value: '1024x1792',
},
{
label: '1792x1024',
value: '1792x1024',
},
],
},
},
{
field: 'imageQuality',
label: '图片质量',
labelMessage: '生成图片的质量',
component: 'Select',
formItemProps: {rules: [{required: true, message: '请选择图片的质量', trigger: ['blur']}]},
componentProps: {
options: [
{
label: 'standard',
value: 'standard',
},
{
label: 'hd',
value: 'hd',
},
],
},
},
{
field: 'imageStyle',
label: '图片风格',
labelMessage: '生成图片的风格',
component: 'Select',
formItemProps: {rules: [{required: true, message: '请选择图片的风格', trigger: ['blur']}]},
componentProps: {
options: [
{
label: 'vivid',
value: 'vivid',
},
{
label: 'natural',
value: 'natural',
},
],
},
},
...keySchemas,
];
export const zhipuSchemas: FormSchema[] = [
...baseHeadSchemas,
{
field: 'model',
label: '模型',
labelMessage: '该LLM供应商对应的模型版本号',
component: 'Select',
formItemProps: {rules: [{required: true, message: '请选择模型', trigger: ['blur']}]},
componentProps: {
filterable: true,
options: getModels(ProviderEnum.ZHIPU),
},
},
...keySchemas,
];
export function getSchemas(provider: string) {
switch (provider) {
case ProviderEnum.OPENAI: {
return openaiSchemas;
}
case ProviderEnum.AZURE_OPENAI: {
return azureOpenaiSchemas;
}
case ProviderEnum.ZHIPU: {
return zhipuSchemas;
}
}
return [];
}
export function getModels(provider: string) {
const arr = LLMProviders.filter((i) => i.model === provider);
if (arr.length === 0) {
return [];
}
return arr[0].models.map((i) => {
return {
label: i,
value: i,
};
});
}

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
import {Form} from '@/components/Form'
import {ref} from 'vue'
import {FormSchema} from "@/types/form";
import {getSchemas} from "@/views/ai/model/image/composables/schemas";
const visible = ref(false)
const formData = ref({})
const formRef = ref()
const schemas = ref([])
const close = () => {
visible.value = false
formRef.value.clearForm()
}
const show = async (data: object) => {
visible.value = true
await nextTick()
formRef.value.setValues(data)
schemas.value = (getSchemas(data.provider) as FormSchema[]);
// formRef.value.setSchema(schemas.value)
// models.value = getModels(data.provider, LLMProviders)
}
defineExpose({
show,
close
})
</script>
<template>
<el-dialog v-model="visible" draggable title="编辑" width="800px" @close="close">
<Form ref="formRef" :model="formData" :schema="schemas" />
<template #footer>
<el-button type="primary">确认</el-button>
<el-button @click="close">取消</el-button>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
</style>

View File

@ -2,25 +2,70 @@
import {Plus} from "@element-plus/icons-vue";
import usePage from './composables/index'
const {shema, columns, tableData, formData} = usePage()
import {LLMProviders} from "@/views/ai/model/image/composables/consts";
import Edit from './edit.vue'
const {columns, tableData, formData, editRef, open} = 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"/>
<div class="children flex">
<el-scrollbar class="h-full w-300px pl-10px pr-20px">
<div
v-for="(item,index) in LLMProviders" :key="index"
:class="{active: formData.provider === item.model}" class="menu"
@click="formData.provider = item.model">
<span>{{ item.name }}</span>
</div>
</el-scrollbar>
<div class="h-full p-20px">
<el-alert
class="w-full mb-10px min-alert"
show-icon
title="鉴于很多模型的文生图效果很差甚至没有这里只建议使用OpenAI的DALL-E模型"
type="info"
/>
<el-button :icon="Plus" class="my-10px" type="primary" @click="open">新增向量模型
</el-button>
<Table :columns="columns" :data="tableData.concat(tableData)" :pagination="false" border
class="table-wrapper" height="100%"/>
</div>
</div>
<Edit ref="editRef"/>
</ContentWrap>
</template>
<style scoped lang="scss">
.children {
height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - (var(--app-content-padding) * 3)) !important;
box-sizing: border-box;
& > div:nth-child(2) {
width: calc(100% - 300px);
}
}
.table-wrapper {
height: calc(100% - 100px);
}
.menu {
transition: all .15s;
cursor: pointer;
padding: 12px 10px;
border-radius: 5px;
margin-bottom: 20px;
&.active {
color: #ffffff;
background-color: var(--el-color-primary-light-3);
}
&:hover {
&:not(&.active) {
background-color: var(--el-color-info-light-7);
}
}
}
</style>