parent
eeb8d31f99
commit
2fd76aa5b9
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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 request from '@/config/axios'
|
||||||
|
|
||||||
|
// AI 文档 VO
|
||||||
|
export interface DocsVO {
|
||||||
|
id: string
|
||||||
|
// TODO: Add other fields based on your data model
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI 文档 API
|
||||||
|
export const DocsApi = {
|
||||||
|
// 获得文档分页
|
||||||
|
async getDocsPage(params: any) {
|
||||||
|
return await request.get({ url: '/aigc/docs/page', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得文档列表
|
||||||
|
async getDocsList(params: any) {
|
||||||
|
return await request.get({ url: '/aigc/docs/list', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得文档详情
|
||||||
|
async getDocsById(id: string) {
|
||||||
|
return await request.get({ url: `/aigc/docs/${id}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 创建文档
|
||||||
|
async addDocs(data: any) {
|
||||||
|
return await request.post({ url: '/aigc/docs', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新文档
|
||||||
|
async updateDocs(data: any) {
|
||||||
|
return await request.put({ url: '/aigc/docs', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除文档
|
||||||
|
async deleteDocs(id: string) {
|
||||||
|
return await request.delete({ url: `/aigc/docs/${id}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重新向量化
|
||||||
|
async reEmbedDocs(id: string) {
|
||||||
|
return await request.get({ url: `/aigc/embedding/re-embed/${id}` })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* 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 request from '@/config/axios'
|
||||||
|
|
||||||
|
// AI 嵌入存储 VO
|
||||||
|
export interface EmbedStoreVO {
|
||||||
|
id: string
|
||||||
|
// TODO: Add other fields based on your data model
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI 嵌入存储 API
|
||||||
|
export const EmbedStoreApi = {
|
||||||
|
// 获得嵌入存储列表
|
||||||
|
getEmbedStoreList: async (params: any) => {
|
||||||
|
return await request.get({ url: '/aigc/embed-store/list', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得嵌入存储分页
|
||||||
|
getEmbedStorePage: async (params: any) => {
|
||||||
|
return await request.get({ url: '/aigc/embed-store/page', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得嵌入存储详情
|
||||||
|
getEmbedStore: async (id: string) => {
|
||||||
|
return await request.get({ url: `/aigc/embed-store/${id}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 创建嵌入存储
|
||||||
|
createEmbedStore: async (data: any) => {
|
||||||
|
return await request.post({ url: '/aigc/embed-store', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新嵌入存储
|
||||||
|
updateEmbedStore: async (data: any) => {
|
||||||
|
return await request.put({ url: '/aigc/embed-store', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除嵌入存储
|
||||||
|
deleteEmbedStore: async (id: string) => {
|
||||||
|
return await request.delete({ url: `/aigc/embed-store/${id}` })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// export function list(params: any) {
|
||||||
|
// return http.request({
|
||||||
|
// url: '/aigc/embed-store/list',
|
||||||
|
// method: 'get',
|
||||||
|
// params,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function page(params: any) {
|
||||||
|
// return http.request({
|
||||||
|
// url: '/aigc/embed-store/page',
|
||||||
|
// method: 'get',
|
||||||
|
// params,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function getById(id: string) {
|
||||||
|
// return http.request({
|
||||||
|
// url: `/aigc/embed-store/${id}`,
|
||||||
|
// method: 'get',
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function add(params: any) {
|
||||||
|
// return http.request({
|
||||||
|
// url: '/aigc/embed-store',
|
||||||
|
// method: 'post',
|
||||||
|
// params,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function update(params: any) {
|
||||||
|
// return http.request({
|
||||||
|
// url: '/aigc/embed-store',
|
||||||
|
// method: 'put',
|
||||||
|
// params,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function del(id?: string) {
|
||||||
|
// return http.request({
|
||||||
|
// url: `/aigc/embed-store/${id}`,
|
||||||
|
// method: 'delete',
|
||||||
|
// });
|
||||||
|
// }
|
|
@ -0,0 +1,81 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
import { AxiosProgressEvent } from 'axios'
|
||||||
|
|
||||||
|
// AI 嵌入 API
|
||||||
|
export const EmbeddingApi = {
|
||||||
|
// 文本嵌入
|
||||||
|
embeddingText: async (params: any) => {
|
||||||
|
return await request.post({ url: '/aigc/embedding/text', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 嵌入搜索
|
||||||
|
embeddingSearch: async (data: any) => {
|
||||||
|
return await request.post({ url: '/aigc/embedding/search', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 文档嵌入
|
||||||
|
embeddingDocs: async (
|
||||||
|
knowledgeId: string,
|
||||||
|
data: any,
|
||||||
|
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void
|
||||||
|
) => {
|
||||||
|
return await request.post({
|
||||||
|
url: `/aigc/embedding/docs/${knowledgeId}`,
|
||||||
|
data,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
},
|
||||||
|
onUploadProgress
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// /*
|
||||||
|
// * 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 { http } from '@/utils/http/axios';
|
||||||
|
// import { AxiosProgressEvent } from 'axios';
|
||||||
|
|
||||||
|
// export function embeddingText(params: any) {
|
||||||
|
// return http.request({
|
||||||
|
// url: '/aigc/embedding/text',
|
||||||
|
// method: 'post',
|
||||||
|
// params,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function embeddingSearch(data: any) {
|
||||||
|
// return http.request({
|
||||||
|
// url: '/aigc/embedding/search',
|
||||||
|
// method: 'post',
|
||||||
|
// data,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function embeddingDocs(
|
||||||
|
// knowledgeId: string,
|
||||||
|
// data: any,
|
||||||
|
// onUploadProgress?: (progressEvent: AxiosProgressEvent) => void
|
||||||
|
// ) {
|
||||||
|
// return http.request({
|
||||||
|
// url: `/aigc/embedding/docs/${knowledgeId}`,
|
||||||
|
// method: 'post',
|
||||||
|
// data,
|
||||||
|
// headers: {
|
||||||
|
// 'Content-Type': 'multipart/form-data',
|
||||||
|
// },
|
||||||
|
// onUploadProgress,
|
||||||
|
// });
|
||||||
|
// }
|
|
@ -0,0 +1,40 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
// AI 知识库 VO
|
||||||
|
export interface KnowledgeVO {
|
||||||
|
id: string
|
||||||
|
// TODO: Add other fields based on your data model
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI 知识库 API
|
||||||
|
export const KnowledgeApi = {
|
||||||
|
// 获得知识库列表
|
||||||
|
getKnowledgeList: async (params: any) => {
|
||||||
|
return await request.get({ url: '/aigc/knowledge/list', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得知识库分页
|
||||||
|
getKnowledgePage: async (params: any) => {
|
||||||
|
return await request.get({ url: '/aigc/knowledge/page', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得知识库详情
|
||||||
|
getKnowledge: async (id: string) => {
|
||||||
|
return await request.get({ url: `/aigc/knowledge/${id}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 创建知识库
|
||||||
|
createKnowledge: async (data: any) => {
|
||||||
|
return await request.post({ url: '/aigc/knowledge', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新知识库
|
||||||
|
updateKnowledge: async (data: any) => {
|
||||||
|
return await request.put({ url: '/aigc/knowledge', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除知识库
|
||||||
|
deleteKnowledge: async (id: string) => {
|
||||||
|
return await request.delete({ url: `/aigc/knowledge/${id}` })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
// AI 模型 VO
|
||||||
|
export interface ModelVO {
|
||||||
|
id: string
|
||||||
|
// TODO: Add other fields based on your data model
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI 模型 API
|
||||||
|
export const ModelApi = {
|
||||||
|
// 获得模型分页
|
||||||
|
getModelPage: async (params: any) => {
|
||||||
|
return await request.get({ url: '/chat/aigc/model/page', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得模型列表
|
||||||
|
getModelList: async (params: any) => {
|
||||||
|
return await request.get({ url: '/chat/aigc/model/list', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得模型详情
|
||||||
|
getModel: async (id: string) => {
|
||||||
|
return await request.get({ url: `/chat/aigc/model/${id}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 创建模型
|
||||||
|
createModel: async (data: any) => {
|
||||||
|
return await request.post({ url: '/chat/aigc/model', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新模型
|
||||||
|
updateModel: async (data: any) => {
|
||||||
|
return await request.put({ url: '/chat/aigc/model', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除模型
|
||||||
|
deleteModel: async (id: string) => {
|
||||||
|
return await request.delete({ url: `/chat/aigc/model/${id}` })
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* 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 request from '@/config/axios'
|
||||||
|
|
||||||
|
// AI 文档切片 VO
|
||||||
|
export interface SliceVO {
|
||||||
|
id: string
|
||||||
|
// TODO: Add other fields based on your data model
|
||||||
|
}
|
||||||
|
|
||||||
|
// AI 文档切片 API
|
||||||
|
export const SliceApi = {
|
||||||
|
// 获得切片分页
|
||||||
|
getSlicePage: async (params: any) => {
|
||||||
|
return await request.get({ url: '/aigc/docs/slice/page', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得切片列表
|
||||||
|
getSliceList: async (params: any) => {
|
||||||
|
return await request.get({ url: '/aigc/docs/slice/list', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获得切片详情
|
||||||
|
getSlice: async (id: string) => {
|
||||||
|
return await request.get({ url: `/aigc/docs/slice/${id}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 创建切片
|
||||||
|
createSlice: async (data: any) => {
|
||||||
|
return await request.post({ url: '/aigc/docs/slice', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新切片
|
||||||
|
updateSlice: async (data: any) => {
|
||||||
|
return await request.put({ url: '/aigc/docs/slice', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除切片
|
||||||
|
deleteSlice: async (id: string) => {
|
||||||
|
return await request.delete({ url: `/aigc/docs/slice/${id}` })
|
||||||
|
}
|
||||||
|
}
|
|
@ -191,7 +191,7 @@ export default defineComponent({
|
||||||
) {
|
) {
|
||||||
slotsMap.default = () => renderOptions(item)
|
slotsMap.default = () => renderOptions(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
|
const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
|
||||||
// 如果有 labelMessage,自动使用插槽渲染
|
// 如果有 labelMessage,自动使用插槽渲染
|
||||||
if (item?.labelMessage) {
|
if (item?.labelMessage) {
|
||||||
|
@ -230,6 +230,7 @@ export default defineComponent({
|
||||||
return slots[item.field] ? (
|
return slots[item.field] ? (
|
||||||
getSlot(slots, item.field, formModel.value)
|
getSlot(slots, item.field, formModel.value)
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
<Com
|
<Com
|
||||||
vModel={formModel.value[item.field]}
|
vModel={formModel.value[item.field]}
|
||||||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
||||||
|
@ -242,6 +243,8 @@ export default defineComponent({
|
||||||
>
|
>
|
||||||
{{ ...slotsMap }}
|
{{ ...slotsMap }}
|
||||||
</Com>
|
</Com>
|
||||||
|
</>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -81,5 +81,9 @@ export function getColumns(provider: string) {
|
||||||
return zhipuColumns;
|
return zhipuColumns;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return openaiColumns;
|
return [...openaiColumns, {
|
||||||
|
label: '操作',
|
||||||
|
field: 'action',
|
||||||
|
width: '150',
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,3 +196,9 @@ export const LLMProviders: any[] = [
|
||||||
models: ['claude-3-opus', 'claude-3-opus-20240229', 'claude-3-sonnet', 'claude-3-haiku'],
|
models: ['claude-3-opus', 'claude-3-opus-20240229', 'claude-3-sonnet', 'claude-3-haiku'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
export enum ModelTypeEnum {
|
||||||
|
CHAT = 'CHAT',
|
||||||
|
EMBEDDING = 'EMBEDDING',
|
||||||
|
TEXT_IMAGE = 'TEXT_IMAGE',
|
||||||
|
WEB_SEARCH = 'WEB_SEARCH',
|
||||||
|
}
|
|
@ -1,25 +1,55 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {Form} from '@/components/Form'
|
import {Form} from '@/components/Form'
|
||||||
import {ref} from 'vue'
|
import {nextTick, ref} from 'vue'
|
||||||
import {FormSchema} from "@/types/form";
|
import {FormSchema} from "@/types/form";
|
||||||
import {getSchemas} from "@/views/ai/model/chatModel/composables/schemas";
|
import {getSchemas} from "@/views/ai/model/chatModel/composables/schemas";
|
||||||
|
import {ModelApi} from '@/api/new-ai/model';
|
||||||
|
import {ElMessage} from 'element-plus';
|
||||||
|
|
||||||
|
const emit = defineEmits(['reload'])
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const formData = ref({})
|
const formData = ref({})
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const schemas = ref([])
|
const schemas = ref([])
|
||||||
|
const isEdit = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
|
formData.value = {}
|
||||||
visible.value = false
|
visible.value = false
|
||||||
formRef.value.clearForm()
|
schemas.value = []
|
||||||
|
nextTick(() => {
|
||||||
|
formRef.value?.clearForm()
|
||||||
|
formRef.value?.setSchema(schemas.value)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const show = async (data: object) => {
|
|
||||||
|
const show = async (data: any = {}) => {
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
isEdit.value = !!data.id
|
||||||
await nextTick()
|
await nextTick()
|
||||||
formRef.value.setValues(data)
|
formRef.value.setValues(data)
|
||||||
schemas.value = (getSchemas(data.provider) as FormSchema[]).splice(1);
|
schemas.value = getSchemas(data.provider).slice(1);
|
||||||
formRef.value.setSchema(schemas.value)
|
formRef.value.setSchema(schemas.value)
|
||||||
// models.value = getModels(data.provider, LLMProviders)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const form = formRef.value.getElFormRef()
|
||||||
|
await form.validate()
|
||||||
|
const values = formRef.value.formModel
|
||||||
|
loading.value = true
|
||||||
|
const api = isEdit.value ? ModelApi.updateModel : ModelApi.createModel
|
||||||
|
await api(values).finally(() => loading.value = false)
|
||||||
|
ElMessage.success(isEdit.value ? '更新模型成功' : '创建模型成功');
|
||||||
|
close();
|
||||||
|
emit('reload');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save model:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show,
|
show,
|
||||||
close
|
close
|
||||||
|
@ -27,15 +57,14 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="visible" draggable title="编辑" width="800px" @close="close">
|
<el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="visible" draggable :title="isEdit ? '编辑模型' : '新增模型'" width="800px" @close="close">
|
||||||
<Form ref="formRef" :model="formData" :schema="schemas" />
|
<Form ref="formRef" :model="formData" :schema="schemas"/>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button type="primary">确认</el-button>
|
<el-button :loading="loading" type="primary" @click="handleSubmit">确认</el-button>
|
||||||
<el-button @click="close">取消</el-button>
|
<el-button :loading="loading" @click="close">取消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -3,14 +3,12 @@
|
||||||
import {Table} from '@/components/Table';
|
import {Table} from '@/components/Table';
|
||||||
import {Delete, Edit, Plus} from '@element-plus/icons-vue';
|
import {Delete, Edit, Plus} from '@element-plus/icons-vue';
|
||||||
import editCom from './edit.vue';
|
import editCom from './edit.vue';
|
||||||
import {computed, h, nextTick, reactive, ref} from 'vue';
|
import {computed, h, nextTick, reactive, ref, watch, onMounted} from 'vue';
|
||||||
import {getColumns} from './composables/columns.ts';
|
import {getColumns} from './composables/columns';
|
||||||
import {LLMProviders} from './composables/consts.ts';
|
import {LLMProviders, ModelTypeEnum} from './composables/consts';
|
||||||
// import { del, list as getModels } from '@/api/aigc/model';
|
import {ModelApi} from '@/api/new-ai/model';
|
||||||
import {ElMessage, ElMessageBox} from 'element-plus';
|
import {ElMessage, ElMessageBox} from 'element-plus';
|
||||||
import {FormSchema} from "@/types/form";
|
import {getModels, ProviderEnum} from './composables/provider';
|
||||||
// import { ModelTypeEnum } from '@/api/models';
|
|
||||||
import {getModels, ProviderEnum} from './composables/provider.ts';
|
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
provider: ProviderEnum.OPENAI
|
provider: ProviderEnum.OPENAI
|
||||||
|
@ -19,129 +17,101 @@ const message = ElMessage;
|
||||||
const dialog = ElMessageBox;
|
const dialog = ElMessageBox;
|
||||||
const actionRef = ref();
|
const actionRef = ref();
|
||||||
const editRef = ref();
|
const editRef = ref();
|
||||||
const tableData = ref([
|
const tableData = ref([]);
|
||||||
{
|
const loading = ref(false);
|
||||||
name: '1111'
|
// 获取模型列表
|
||||||
},
|
const loadData = async () => {
|
||||||
{
|
loading.value = true;
|
||||||
name: '1111'
|
try {
|
||||||
},
|
const res = await ModelApi.getModelList({
|
||||||
{
|
provider: formData.value.provider,
|
||||||
name: '1111'
|
type: ModelTypeEnum.CHAT
|
||||||
},
|
}).finally(() => {
|
||||||
{
|
loading.value = false;
|
||||||
name: '1111'
|
});
|
||||||
},
|
console.log(res)
|
||||||
{
|
tableData.value = res;
|
||||||
name: '1111'
|
} catch (error) {
|
||||||
},
|
console.error('Failed to load models:', error);
|
||||||
{
|
message.error('获取模型列表失败');
|
||||||
name: '1111'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '1111'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '1111'
|
|
||||||
}, {
|
|
||||||
name: '1111'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '1111'
|
|
||||||
}
|
}
|
||||||
])
|
};
|
||||||
// const actionColumn = reactive({
|
|
||||||
// width: 100,
|
// 监听 provider 变化重新加载数据
|
||||||
// title: '操作',
|
watch(() => formData.value.provider, () => {
|
||||||
// key: 'action',
|
loadData();
|
||||||
// fixed: 'right',
|
});
|
||||||
// align: 'center',
|
|
||||||
// render(record: any) {
|
// 初始加载
|
||||||
// return h(TableAction as any, {
|
onMounted(() => {
|
||||||
// style: 'text',
|
loadData();
|
||||||
// actions: [
|
});
|
||||||
// {
|
|
||||||
// type: 'info',
|
|
||||||
// icon: Edit,
|
|
||||||
// onClick: handleEdit.bind(null, record),
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// type: 'error',
|
|
||||||
// icon: Delete,
|
|
||||||
// onClick: handleDel.bind(null, record),
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
const columns = computed(() => {
|
const columns = computed(() => {
|
||||||
nextTick();
|
nextTick();
|
||||||
return getColumns(formData.value.provider);
|
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 addModel() {
|
async function addModel() {
|
||||||
console.log(formData.value.provider);
|
console.log(formData.value.provider);
|
||||||
editRef.value.show({provider: formData.value.provider});
|
editRef.value.show({provider: formData.value.provider, type: ModelTypeEnum.CHAT });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEdit(record: any) {
|
function handleEdit(record: any) {
|
||||||
editRef.value.show(record);
|
editRef.value.show(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadTable() {
|
async function reloadTable() {
|
||||||
actionRef.value.reload();
|
await loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDel(record: any) {
|
async function handleDel(record: any) {
|
||||||
dialog.warning({
|
dialog.confirm('确定要删除该模型吗?', '警告', {
|
||||||
title: '警告',
|
|
||||||
message: `你确定删除 [${record.name}] 模型吗?删除之后不可再用该模型对话`,
|
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '不确定',
|
cancelButtonText: '取消',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
await del(record.id);
|
try {
|
||||||
reloadTable();
|
await ModelApi.deleteModel(record.id);
|
||||||
message.success('模型删除成功');
|
await reloadTable();
|
||||||
|
message.success('模型删除成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete model:', error);
|
||||||
|
message.error('删除模型失败');
|
||||||
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// 取消删除
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- <content-wrap>-->
|
|
||||||
<!-- <el-button v-for="(item,index) in LLMProviders" :key="index" @click="formData.provider = item.model">{{ item.name }}</el-button>-->
|
|
||||||
<!-- </content-wrap>-->
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<div class="flex children">
|
<div class="flex children">
|
||||||
<el-scrollbar class="h-full w-300px pl-10px pr-20px">
|
<el-scrollbar class="h-full w-300px pl-10px pr-20px">
|
||||||
<div
|
<div
|
||||||
v-for="(item,index) in LLMProviders" :key="index"
|
v-for="(item,index) in LLMProviders" :key="index"
|
||||||
:class="{active: formData.provider === item.model}" class="menu"
|
:class="{active: formData.provider === item.model}" class="menu"
|
||||||
@click="formData.provider = item.model">
|
@click="formData.provider = item.model">
|
||||||
<span>{{ item.name }}</span>
|
<span>{{ item.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<div class="h-full flex-1 px-20px">
|
<div class="h-full flex-1 px-20px" v-loading="loading">
|
||||||
<el-alert
|
<el-alert
|
||||||
class="w-full mb-10px min-alert"
|
class="w-full mb-10px min-alert"
|
||||||
title="对于完全适配OpenAI接口格式的模型都可在OpenAI中配置(只需要定义BaseUrl)"
|
title="对于完全适配OpenAI接口格式的模型都可在OpenAI中配置(只需要定义BaseUrl)"
|
||||||
type="warning"
|
type="warning"
|
||||||
show-icon
|
show-icon
|
||||||
/>
|
/>
|
||||||
<el-button :icon="Plus" class="my-10px" type="primary" @click="addModel">新增模型</el-button>
|
<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"/>
|
<Table class="table-wrapper" height="100%" border :columns="columns" :data="tableData" :pagination="false" >
|
||||||
<editCom ref="editRef" @reload="reloadTable"/>
|
<template #action="{row}">
|
||||||
</div>
|
<el-button text :icon="Edit" @click="handleEdit(row)"/>
|
||||||
</div>
|
<el-button text :icon="Delete" type="danger" @click="handleDel(row)"/>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
<editCom ref="editRef" @reload="reloadTable"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
import {FormSchema} from "@/types/form";
|
import {FormSchema} from "@/types/form";
|
||||||
import {ElTag} from "element-plus";
|
import {ElTag} from "element-plus";
|
||||||
|
import {EmbedStoreApi} from "@/api/new-ai/embed-store";
|
||||||
|
|
||||||
export enum ProviderEnum {
|
export enum ProviderEnum {
|
||||||
Redis = 'REDIS',
|
Redis = 'REDIS',
|
||||||
PgVector = 'PGVECTOR',
|
PgVector = 'PGVECTOR',
|
||||||
Milvus = 'MILVUS',
|
Milvus = 'MILVUS',
|
||||||
}
|
}
|
||||||
|
export const ProviderConst = [
|
||||||
|
{ label: 'Redis', value: ProviderEnum.Redis },
|
||||||
|
{ label: 'PgVector', value: ProviderEnum.PgVector },
|
||||||
|
{ label: 'Milvus', value: ProviderEnum.Milvus },
|
||||||
|
];
|
||||||
export default function () {
|
export default function () {
|
||||||
const ProviderConst = [
|
const ProviderConst = [
|
||||||
{ label: 'Redis', value: ProviderEnum.Redis },
|
{ label: 'Redis', value: ProviderEnum.Redis },
|
||||||
{ label: 'PgVector', value: ProviderEnum.PgVector },
|
{ label: 'PgVector', value: ProviderEnum.PgVector },
|
||||||
{ label: 'Milvus', value: ProviderEnum.Milvus },
|
{ label: 'Milvus', value: ProviderEnum.Milvus },
|
||||||
];
|
];
|
||||||
|
|
||||||
function getProviderLabel(value: any) {
|
function getProviderLabel(value: any) {
|
||||||
const arr = ProviderConst.filter((i) => i.value === value);
|
const arr = ProviderConst.filter((i) => i.value === value);
|
||||||
if (arr === undefined || arr.length === 0) {
|
if (arr === undefined || arr.length === 0) {
|
||||||
|
@ -22,6 +27,7 @@ export default function () {
|
||||||
}
|
}
|
||||||
return arr[0].label;
|
return arr[0].label;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shema = ref<FormSchema[]>([
|
const shema = ref<FormSchema[]>([
|
||||||
{
|
{
|
||||||
label: '模型名称',
|
label: '模型名称',
|
||||||
|
@ -32,6 +38,7 @@ export default function () {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
const columns = ref<object[]>([
|
const columns = ref<object[]>([
|
||||||
{
|
{
|
||||||
label: '数据库别名',
|
label: '数据库别名',
|
||||||
|
@ -59,7 +66,7 @@ export default function () {
|
||||||
label: '向量纬度',
|
label: '向量纬度',
|
||||||
field: 'dimension',
|
field: 'dimension',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: '80',
|
width: '100',
|
||||||
render(row) {
|
render(row) {
|
||||||
return h(
|
return h(
|
||||||
ElTag,
|
ElTag,
|
||||||
|
@ -76,7 +83,6 @@ export default function () {
|
||||||
label: '数据库地址',
|
label: '数据库地址',
|
||||||
field: 'host',
|
field: 'host',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: '110',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '数据库端口',
|
label: '数据库端口',
|
||||||
|
@ -85,32 +91,46 @@ export default function () {
|
||||||
width: '100',
|
width: '100',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '数据库用户名',
|
label: '数据库名称',
|
||||||
field: 'username',
|
field: 'database',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
{
|
]);
|
||||||
label: '数据库密码',
|
|
||||||
field: 'password',
|
|
||||||
align: 'center',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '数据库名',
|
|
||||||
field: 'databaseName',
|
|
||||||
align: 'center',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '表名称',
|
|
||||||
field: 'tableName',
|
|
||||||
align: 'center',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
const tableData = ref([])
|
const tableData = ref([])
|
||||||
|
const editRef = ref()
|
||||||
|
const searchParams = ref({})
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await EmbedStoreApi.getEmbedStorePage(searchParams.value);
|
||||||
|
tableData.value = res.data.list;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load embed stores:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开编辑对话框
|
||||||
|
const open = () => {
|
||||||
|
editRef.value.show({});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理搜索
|
||||||
|
const handleSearch = (values: any) => {
|
||||||
|
searchParams.value = values;
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ProviderConst,
|
|
||||||
getProviderLabel,
|
|
||||||
shema,
|
shema,
|
||||||
columns,
|
columns,
|
||||||
tableData
|
tableData,
|
||||||
|
editRef,
|
||||||
|
open,
|
||||||
|
loadData,
|
||||||
|
handleSearch,
|
||||||
|
ProviderConst,
|
||||||
|
getProviderLabel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {Form} from '@/components/Form'
|
||||||
|
import {nextTick, ref} from 'vue'
|
||||||
|
import {FormSchema} from "@/types/form";
|
||||||
|
import {EmbedStoreApi} from "@/api/new-ai/embed-store";
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
import {ProviderConst} from './composables'
|
||||||
|
|
||||||
|
const emit = defineEmits(['reload'])
|
||||||
|
const visible = ref(false)
|
||||||
|
const formData = ref({})
|
||||||
|
const formRef = ref()
|
||||||
|
const isEdit = ref(false)
|
||||||
|
|
||||||
|
const schemas = ref<FormSchema[]>([
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
label: '数据库别名',
|
||||||
|
component: 'Input',
|
||||||
|
formItemProps: {
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'provider',
|
||||||
|
label: '供应商',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: ProviderConst,
|
||||||
|
},
|
||||||
|
formItemProps: {
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'dimension',
|
||||||
|
label: '向量纬度',
|
||||||
|
component: 'InputNumber',
|
||||||
|
formItemProps: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
min: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'host',
|
||||||
|
label: '数据库地址',
|
||||||
|
component: 'Input',
|
||||||
|
formItemProps: {
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'port',
|
||||||
|
label: '数据库端口',
|
||||||
|
component: 'InputNumber',
|
||||||
|
formItemProps: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
min: 1,
|
||||||
|
max: 65535,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'database',
|
||||||
|
label: '数据库名称',
|
||||||
|
component: 'Input',
|
||||||
|
formItemProps: {
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'username',
|
||||||
|
label: '数据库用户名',
|
||||||
|
component: 'Input',
|
||||||
|
formItemProps: {
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'password',
|
||||||
|
label: '数据库密码',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
type: 'password',
|
||||||
|
showPassword: true,
|
||||||
|
},
|
||||||
|
formItemProps: {
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
visible.value = false
|
||||||
|
formRef.value.clearForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
const show = async (data: any = {}) => {
|
||||||
|
visible.value = true
|
||||||
|
isEdit.value = !!data.id
|
||||||
|
await nextTick()
|
||||||
|
formRef.value.setValues(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const form = formRef.value.getElFormRef()
|
||||||
|
await form.validate()
|
||||||
|
const values = formRef.value.formModel
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
await EmbedStoreApi.updateEmbedStore(values);
|
||||||
|
ElMessage.success('更新向量数据库成功');
|
||||||
|
} else {
|
||||||
|
await EmbedStoreApi.createEmbedStore(values);
|
||||||
|
ElMessage.success('创建向量数据库成功');
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
emit('reload');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save embed store:', error);
|
||||||
|
ElMessage.error(isEdit.value ? '更新向量数据库失败' : '创建向量数据库失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
close
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog v-model="visible" draggable :title="isEdit ? '编辑向量数据库' : '新增向量数据库'" width="800px" @close="close">
|
||||||
|
<Form ref="formRef" :model="formData" :schema="schemas" />
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确认</el-button>
|
||||||
|
<el-button @click="close">取消</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
</style>
|
|
@ -1,13 +1,21 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {Plus} from "@element-plus/icons-vue";
|
import {Plus} from "@element-plus/icons-vue";
|
||||||
import {Table} from "@/components/Table";
|
import {Table} from "@/components/Table";
|
||||||
|
import {Search} from "@/components/Search";
|
||||||
import useEmbedStore from './composables'
|
import useEmbedStore from './composables'
|
||||||
const { shema, columns, tableData } = useEmbedStore()
|
import Edit from './edit.vue'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
|
const { shema, columns, tableData, editRef, open, loadData, handleSearch } = useEmbedStore()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<content-wrap>
|
<content-wrap>
|
||||||
<Search :schema="shema"/>
|
<Search :schema="shema" @search="handleSearch" @reset="handleSearch"/>
|
||||||
</content-wrap>
|
</content-wrap>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-alert
|
<el-alert
|
||||||
|
@ -16,8 +24,9 @@ const { shema, columns, tableData } = useEmbedStore()
|
||||||
type="info"
|
type="info"
|
||||||
show-icon
|
show-icon
|
||||||
/>
|
/>
|
||||||
<el-button class="my-10px" type="primary" :icon="Plus">新增向量数据库</el-button>
|
<el-button class="my-10px" type="primary" :icon="Plus" @click="open">新增向量数据库</el-button>
|
||||||
<Table height="cacl(100% - 400px)" border :columns="columns" :data="tableData.concat(tableData)" :pagination="false"/>
|
<Table height="calc(100% - 400px)" border :columns="columns" :data="tableData" :pagination="false"/>
|
||||||
|
<Edit ref="editRef" @reload="loadData" />
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import {ElTag} from "element-plus";
|
import {ref, watch} from "vue";
|
||||||
import {ref} from "vue";
|
|
||||||
import {ProviderEnum} from "@/views/ai/model/chatModel/composables/provider";
|
import {ProviderEnum} from "@/views/ai/model/chatModel/composables/provider";
|
||||||
|
import {ModelApi} from "@/api/new-ai/model";
|
||||||
|
import {ElTag, ElMessageBox as dialog, ElMessage as message} from "element-plus";
|
||||||
|
import { ModelTypeEnum } from "../../chatModel/composables/consts";
|
||||||
|
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
const formData = ref({
|
|
||||||
provider: ProviderEnum.OPENAI
|
|
||||||
})
|
|
||||||
const editRef = ref()
|
const editRef = ref()
|
||||||
|
const formData = ref({
|
||||||
|
provider: ProviderEnum.OPENAI,
|
||||||
|
type: ModelTypeEnum.EMBEDDING
|
||||||
|
});
|
||||||
|
const tableData = ref([])
|
||||||
|
|
||||||
const baseColumns = [
|
const baseColumns = [
|
||||||
{
|
{
|
||||||
label: '模型别名',
|
label: '模型别名',
|
||||||
|
@ -19,7 +25,7 @@ export default function () {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '向量纬度',
|
label: '向量纬度',
|
||||||
field: 'dimension',
|
field: 'dimension',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: '100',
|
width: '100',
|
||||||
render(row) {
|
render(row) {
|
||||||
|
@ -41,17 +47,60 @@ export default function () {
|
||||||
{
|
{
|
||||||
label: 'Base Url',
|
label: 'Base Url',
|
||||||
field: 'baseUrl',
|
field: 'baseUrl',
|
||||||
},
|
}, {label: '操作', field: 'action', width: 150}
|
||||||
];
|
];
|
||||||
const tableData = ref([])
|
|
||||||
|
// 加载数据
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await ModelApi.getModelList({ provider: formData.value.provider, type: ModelTypeEnum.EMBEDDING });
|
||||||
|
tableData.value = res;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load embedding models:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听供应商变化
|
||||||
|
watch(() => formData.value.provider, () => {
|
||||||
|
loadData();
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
const open = () => {
|
const open = () => {
|
||||||
editRef.value.show({provider: formData.value.provider});
|
editRef.value.show({provider: formData.value.provider, type: ModelTypeEnum.EMBEDDING});
|
||||||
|
}
|
||||||
|
function handleEdit(record: any) {
|
||||||
|
editRef.value.show(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadTable() {
|
||||||
|
await loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDel(record: any) {
|
||||||
|
dialog.confirm('确定要删除该模型吗?', '警告', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
await ModelApi.deleteModel(record.id);
|
||||||
|
await reloadTable();
|
||||||
|
message.success('模型删除成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete model:', error);
|
||||||
|
message.error('删除模型失败');
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
baseColumns,
|
baseColumns,
|
||||||
tableData,
|
tableData,
|
||||||
formData,
|
formData,
|
||||||
editRef,
|
editRef,
|
||||||
open
|
open,
|
||||||
|
loadData,
|
||||||
|
handleDel,
|
||||||
|
handleEdit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,59 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {Form} from '@/components/Form'
|
import {Form} from '@/components/Form'
|
||||||
import {ref} from 'vue'
|
import {nextTick, ref} from 'vue'
|
||||||
import {FormSchema} from "@/types/form";
|
import {FormSchema} from "@/types/form";
|
||||||
import {getSchemas} from "@/views/ai/model/embedding/composables/schemas";
|
import {getSchemas} from "@/views/ai/model/embedding/composables/schemas";
|
||||||
|
import {ModelApi} from "@/api/new-ai/model";
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
|
||||||
|
const emit = defineEmits(['reload'])
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const formData = ref({})
|
const formData = ref({})
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const schemas = ref([])
|
const schemas = ref([])
|
||||||
|
const isEdit = ref(false)
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
formRef.value.clearForm()
|
formRef.value.clearForm()
|
||||||
|
formData.value = {}
|
||||||
|
schemas.value = []
|
||||||
|
nextTick(() => {
|
||||||
|
formRef.value?.clearForm()
|
||||||
|
formRef.value?.setSchema(schemas.value)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const show = async (data: object) => {
|
|
||||||
|
const show = async (data: any = {}) => {
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
isEdit.value = !!data.id
|
||||||
await nextTick()
|
await nextTick()
|
||||||
formRef.value.setValues(data)
|
formRef.value.setValues(data)
|
||||||
schemas.value = (getSchemas(data.provider) as FormSchema[]).splice(1);
|
schemas.value = getSchemas(data.provider).slice(1);
|
||||||
formRef.value.setSchema(schemas.value)
|
formRef.value.setSchema(schemas.value)
|
||||||
// models.value = getModels(data.provider, LLMProviders)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const form = formRef.value.getElFormRef()
|
||||||
|
await form.validate()
|
||||||
|
const values = formRef.value.formModel
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
await ModelApi.updateModel(values);
|
||||||
|
ElMessage.success('更新向量模型成功');
|
||||||
|
} else {
|
||||||
|
await ModelApi.createModel(values);
|
||||||
|
ElMessage.success('创建向量模型成功');
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
emit('reload');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save model:', error);
|
||||||
|
ElMessage.error(isEdit.value ? '更新向量模型失败' : '创建向量模型失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show,
|
show,
|
||||||
close
|
close
|
||||||
|
@ -27,15 +61,14 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="visible" draggable title="编辑" width="800px" @close="close">
|
<el-dialog v-model="visible" draggable :title="isEdit ? '编辑向量模型' : '新增向量模型'" width="800px" @close="close">
|
||||||
<Form ref="formRef" :model="formData" :schema="schemas" />
|
<Form ref="formRef" :model="formData" :schema="schemas" />
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button type="primary">确认</el-button>
|
<el-button type="primary" @click="handleSubmit">确认</el-button>
|
||||||
<el-button @click="close">取消</el-button>
|
<el-button @click="close">取消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {Plus, Delete, Edit} from "@element-plus/icons-vue";
|
||||||
import {Plus} from "@element-plus/icons-vue";
|
|
||||||
import usePage from './composables/index'
|
import usePage from './composables/index'
|
||||||
import Edit from "@/views/ai/model/embedding/edit.vue";
|
import EditCom from "@/views/ai/model/embedding/edit.vue";
|
||||||
import {LLMProviders} from "@/views/ai/model/embedding/composables/consts";
|
import {LLMProviders} from "@/views/ai/model/embedding/composables/consts";
|
||||||
import {Table} from "@/components/Table";
|
import {Table} from "@/components/Table";
|
||||||
const { baseColumns: columns, tableData, formData, editRef, open} = usePage()
|
|
||||||
|
const { baseColumns: columns, tableData, formData, editRef, open, loadData, handleEdit, handleDel} = usePage()
|
||||||
|
|
||||||
|
const handleReload = () => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -27,10 +31,15 @@ const { baseColumns: columns, tableData, formData, editRef, open} = usePage()
|
||||||
show-icon
|
show-icon
|
||||||
/>
|
/>
|
||||||
<el-button class="my-10px" type="primary" :icon="Plus" @click="open">新增向量模型</el-button>
|
<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"/>
|
<Table class="table-wrapper" height="100%" border :columns="columns" :data="tableData" :pagination="false">
|
||||||
|
<template #action="{row}">
|
||||||
|
<el-button :icon="Edit" text @click="handleEdit(row)"/>
|
||||||
|
<el-button :icon="Delete" text type="danger" @click="handleDel(row)"/>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Edit ref="editRef" />
|
<EditCom ref="editRef" @reload="handleReload" />
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,51 +1,55 @@
|
||||||
|
import {computed, nextTick, ref, watch} from "vue";
|
||||||
import {ProviderEnum} from "@/views/ai/model/image/composables/consts";
|
import {ProviderEnum} from "@/views/ai/model/image/composables/consts";
|
||||||
|
import {ModelApi} from "@/api/new-ai/model";
|
||||||
|
import { ModelTypeEnum } from "../../chatModel/composables/consts";
|
||||||
|
import {ElTag, ElMessageBox as dialog, ElMessage as message} from "element-plus";
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
|
const editRef = ref()
|
||||||
const editRef= ref()
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
provider: ProviderEnum.OPENAI,
|
provider: ProviderEnum.OPENAI,
|
||||||
|
type: ModelTypeEnum.TEXT_IMAGE
|
||||||
});
|
});
|
||||||
const tableData = ref([])
|
const tableData = ref([])
|
||||||
|
|
||||||
const baseColumns = [
|
const baseColumns = [
|
||||||
{
|
{
|
||||||
label: '模型别名',
|
label: '模型别名',
|
||||||
field: 'name',
|
field: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '模型版本',
|
label: '模型版本',
|
||||||
field: 'model',
|
field: 'model',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const openaiColumns = [
|
const openaiColumns = [
|
||||||
...baseColumns,
|
...baseColumns,
|
||||||
{
|
{
|
||||||
label: 'Api Key',
|
label: 'Api Key',
|
||||||
field: 'apiKey',
|
field: 'apiKey',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const azureOpenaiColumns = [
|
const azureOpenaiColumns = [
|
||||||
...baseColumns,
|
...baseColumns,
|
||||||
{
|
{
|
||||||
label: 'Api Key',
|
label: 'Api Key',
|
||||||
field: 'apiKey',
|
field: 'apiKey',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Endpoint',
|
label: 'Endpoint',
|
||||||
field: 'endpoint',
|
field: 'endpoint',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Deployment Name',
|
label: 'Deployment Name',
|
||||||
field: 'azureDeploymentName',
|
field: 'azureDeploymentName',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const zhipuColumns = [...baseColumns];
|
const zhipuColumns = [...baseColumns];
|
||||||
|
|
||||||
function getColumns(provider: string) {
|
function getColumns(provider: string) {
|
||||||
console.log(provider);
|
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case ProviderEnum.OPENAI: {
|
case ProviderEnum.OPENAI: {
|
||||||
return openaiColumns;
|
return openaiColumns;
|
||||||
|
@ -56,21 +60,68 @@ export default function () {
|
||||||
case ProviderEnum.ZHIPU: {
|
case ProviderEnum.ZHIPU: {
|
||||||
return zhipuColumns;
|
return zhipuColumns;
|
||||||
}
|
}
|
||||||
|
default: {
|
||||||
|
return baseColumns;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = computed(() => {
|
const columns = computed(() => {
|
||||||
nextTick();
|
nextTick();
|
||||||
return getColumns(formData.value.provider)
|
return [...getColumns(formData.value.provider), {label: '操作', field: 'action', width: 150}]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await ModelApi.getModelList({ provider: formData.value.provider, type: ModelTypeEnum.TEXT_IMAGE });
|
||||||
|
tableData.value = res;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load image models:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听供应商变化
|
||||||
|
watch(() => formData.value.provider, () => {
|
||||||
|
loadData();
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
const open = () => {
|
const open = () => {
|
||||||
editRef.value.show({provider: formData.value.provider});
|
editRef.value.show({provider: formData.value.provider, type: ModelTypeEnum.TEXT_IMAGE});
|
||||||
|
}
|
||||||
|
function handleEdit(record: any) {
|
||||||
|
editRef.value.show(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadTable() {
|
||||||
|
await loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDel(record: any) {
|
||||||
|
dialog.confirm('确定要删除该模型吗?', '警告', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
await ModelApi.deleteModel(record.id);
|
||||||
|
await reloadTable();
|
||||||
|
message.success('模型删除成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete model:', error);
|
||||||
|
message.error('删除模型失败');
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
tableData,
|
tableData,
|
||||||
formData,
|
formData,
|
||||||
editRef,
|
editRef,
|
||||||
open
|
open,
|
||||||
|
loadData,
|
||||||
|
handleDel,
|
||||||
|
handleEdit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,55 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {Form} from '@/components/Form'
|
import {Form} from '@/components/Form'
|
||||||
import {ref} from 'vue'
|
import {nextTick, ref} from 'vue'
|
||||||
import {FormSchema} from "@/types/form";
|
import {FormSchema} from "@/types/form";
|
||||||
import {getSchemas} from "@/views/ai/model/image/composables/schemas";
|
import {getSchemas} from "@/views/ai/model/image/composables/schemas";
|
||||||
|
import {ModelApi} from "@/api/new-ai/model";
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
|
||||||
|
const emit = defineEmits(['reload'])
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const formData = ref({})
|
const formData = ref({})
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const schemas = ref([])
|
const schemas = ref([])
|
||||||
|
const isEdit = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
|
formData.value = {}
|
||||||
visible.value = false
|
visible.value = false
|
||||||
formRef.value.clearForm()
|
schemas.value = []
|
||||||
|
nextTick(() => {
|
||||||
|
formRef.value?.clearForm()
|
||||||
|
formRef.value?.setSchema(schemas.value)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const show = async (data: object) => {
|
|
||||||
|
const show = async (data: any = {}) => {
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
isEdit.value = !!data.id
|
||||||
await nextTick()
|
await nextTick()
|
||||||
formRef.value.setValues(data)
|
formRef.value.setValues(data)
|
||||||
schemas.value = (getSchemas(data.provider) as FormSchema[]);
|
schemas.value = getSchemas(data.provider).slice(1);
|
||||||
// formRef.value.setSchema(schemas.value)
|
formRef.value.setSchema(schemas.value)
|
||||||
// models.value = getModels(data.provider, LLMProviders)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const form = formRef.value.getElFormRef()
|
||||||
|
await form.validate()
|
||||||
|
const values = formRef.value.formModel
|
||||||
|
loading.value = true
|
||||||
|
const api = isEdit.value ? ModelApi.updateModel : ModelApi.createModel
|
||||||
|
await api(values).finally(() => loading.value = false)
|
||||||
|
ElMessage.success(isEdit.value ? '更新模型成功' : '创建模型成功');
|
||||||
|
close();
|
||||||
|
emit('reload');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save model:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show,
|
show,
|
||||||
close
|
close
|
||||||
|
@ -27,15 +57,13 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="visible" draggable title="编辑" width="800px" @close="close">
|
<el-dialog :close-on-click-modal="false" :close-on-press-escape="false" v-model="visible" draggable :title="isEdit ? '编辑模型' : '新增模型'" width="800px" @close="close">
|
||||||
<Form ref="formRef" :model="formData" :schema="schemas" />
|
<Form ref="formRef" :model="formData" :schema="schemas"/>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button type="primary">确认</el-button>
|
<el-button :loading="loading" type="primary" @click="handleSubmit">确认</el-button>
|
||||||
<el-button @click="close">取消</el-button>
|
<el-button :loading="loading" @click="close">取消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import {Plus} from "@element-plus/icons-vue";
|
import {Delete, Plus, Edit } from "@element-plus/icons-vue";
|
||||||
import usePage from './composables/index'
|
import usePage from './composables/index'
|
||||||
import {LLMProviders} from "@/views/ai/model/image/composables/consts";
|
import {LLMProviders} from "@/views/ai/model/image/composables/consts";
|
||||||
import Edit from './edit.vue'
|
import EditCom from './edit.vue'
|
||||||
|
import {Table} from "@/components/Table";
|
||||||
|
|
||||||
const {columns, tableData, formData, editRef, open} = usePage()
|
const {columns, tableData, formData, editRef, open, loadData, handleEdit, handleDel} = usePage()
|
||||||
|
|
||||||
|
const handleReload = () => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -19,20 +24,26 @@ const {columns, tableData, formData, editRef, open} = usePage()
|
||||||
<span>{{ item.name }}</span>
|
<span>{{ item.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<div class="h-full p-20px">
|
<div class="h-full p-20px">
|
||||||
<el-alert
|
<el-alert
|
||||||
class="w-full mb-10px min-alert"
|
class="w-full mb-10px min-alert"
|
||||||
show-icon
|
show-icon
|
||||||
title="鉴于很多模型的文生图效果很差甚至没有,这里只建议使用OpenAI的DALL-E模型"
|
title="鉴于很多模型的文生图效果很差甚至没有,这里只建议使用OpenAI的DALL-E模型"
|
||||||
type="info"
|
type="info"
|
||||||
/>
|
/>
|
||||||
<el-button :icon="Plus" class="my-10px" type="primary" @click="open">新增向量模型
|
<el-button :icon="Plus" class="my-10px" type="primary" @click="open">新增图像模型
|
||||||
</el-button>
|
</el-button>
|
||||||
<Table :columns="columns" :data="tableData.concat(tableData)" :pagination="false" border
|
<Table
|
||||||
class="table-wrapper" height="100%"/>
|
:columns="columns" :data="tableData" :pagination="false" border
|
||||||
|
class="table-wrapper" height="100%">
|
||||||
|
<template #action="{row}">
|
||||||
|
<el-button :icon="Edit" text @click="handleEdit(row)"/>
|
||||||
|
<el-button :icon="Delete" text type="danger" @click="handleDel(row)"/>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Edit ref="editRef"/>
|
<EditCom ref="editRef" @reload="handleReload"/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -51,21 +62,18 @@ const {columns, tableData, formData, editRef, open} = usePage()
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
transition: all .15s;
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 12px 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: #ffffff;
|
|
||||||
background-color: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
&:not(&.active) {
|
background: var(--el-fill-color-light);
|
||||||
background-color: var(--el-color-info-light-7);
|
}
|
||||||
}
|
|
||||||
|
&.active {
|
||||||
|
background: var(--el-color-primary-light-9);
|
||||||
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useTable} from "@/hooks/web/useTable";
|
import {useTable} from "@/hooks/web/useTable";
|
||||||
import {FormSchema} from "@/types/form";
|
import {FormSchema} from "@/types/form";
|
||||||
|
import {ref} from 'vue'
|
||||||
|
import {SliceApi} from '@/api/new-ai/slice'
|
||||||
|
import {ElMessage} from 'element-plus'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
knowledgeData: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const elTableRef = ref()
|
const elTableRef = ref()
|
||||||
const columns = [
|
const columns = [
|
||||||
|
@ -39,16 +50,36 @@ const columns = [
|
||||||
width: 200,
|
width: 200,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
const {tableObject, register, tableMethods} = useTable()
|
const {tableObject, register, tableMethods} = useTable({
|
||||||
|
getListApi: async (params) => {
|
||||||
|
const res = await SliceApi.getSlicePage({
|
||||||
|
...params,
|
||||||
|
knowledgeId: props.knowledgeData.id
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
list: res.data.list || [],
|
||||||
|
total: res.data.total || 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delListApi: async (ids) => {
|
||||||
|
await Promise.all(ids.map(id => SliceApi.deleteSlice(id)))
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
list: 'list',
|
||||||
|
total: 'total'
|
||||||
|
}
|
||||||
|
})
|
||||||
const schema = ref<FormSchema[]>([
|
const schema = ref<FormSchema[]>([
|
||||||
{
|
{
|
||||||
label: '所属文档',
|
label: '所属文档',
|
||||||
field: 'text',
|
field: 'docId',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
style: {
|
style: {
|
||||||
width: '150px'
|
width: '150px'
|
||||||
}
|
},
|
||||||
|
placeholder: '请选择所属文档'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
@ -60,17 +91,29 @@ const pagination = computed(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
register(tableRef.value, elTableRef.value)
|
register(tableRef,elTableRef,)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<content-wrap>
|
<div>
|
||||||
<Search :schema="schema"/>
|
<Table
|
||||||
</content-wrap>
|
ref="tableRef"
|
||||||
<content-wrap>
|
v-model:table="tableObject"
|
||||||
<Table ref="tableRef" :pagination="pagination" :columns="columns"/>
|
:columns="columns"
|
||||||
</content-wrap>
|
:schema="schema"
|
||||||
|
:label-width="100"
|
||||||
|
:search-table-height="true">
|
||||||
|
<template #action="{ row }">
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="tableMethods.delList([row.id], false)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useTable} from "@/hooks/web/useTable";
|
import {useTable} from "@/hooks/web/useTable";
|
||||||
import {FormSchema} from "@/types/form";
|
import {FormSchema} from "@/types/form";
|
||||||
|
import {ref} from 'vue'
|
||||||
|
import {DocsApi} from '@/api/new-ai/docs'
|
||||||
|
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
knowledgeData: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const elTableRef = ref()
|
const elTableRef = ref()
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
label: '文档名称',
|
label: '文档名称',
|
||||||
|
@ -45,40 +57,86 @@ const columns = [
|
||||||
width: 200,
|
width: 200,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
const {tableObject, register, tableMethods} = useTable()
|
|
||||||
|
const {tableObject, register, tableMethods} = useTable({
|
||||||
|
getListApi: async (params) => {
|
||||||
|
const res = await DocsApi.getDocsPage({
|
||||||
|
...params,
|
||||||
|
knowledgeId: props.knowledgeData.id
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
list: res.data.list || [],
|
||||||
|
total: res.data.total || 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delListApi: async (ids) => {
|
||||||
|
await Promise.all(ids.map(id => DocsApi.deleteDocs(id)))
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
list: 'list',
|
||||||
|
total: 'total'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const schema = ref<FormSchema[]>([
|
const schema = ref<FormSchema[]>([
|
||||||
{
|
{
|
||||||
label: '文档名称',
|
label: '文档名称',
|
||||||
field: 'text',
|
field: 'name',
|
||||||
component: 'Select',
|
component: 'Input',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
style: {
|
style: {
|
||||||
width: '150px'
|
width: '150px'
|
||||||
}
|
},
|
||||||
|
placeholder: '请输入文档名称'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
const pagination = computed(() => {
|
|
||||||
return {
|
// 重新向量化
|
||||||
pageSize: tableObject.pageSize,
|
const handleReEmbed = async (row) => {
|
||||||
currentPage: tableObject.currentPage,
|
try {
|
||||||
total: tableObject.total,
|
await ElMessageBox.confirm('确认要重新向量化该文档吗?', '提示', {
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
await DocsApi.reEmbedDocs(row.id)
|
||||||
|
ElMessage.success('重新向量化成功')
|
||||||
|
tableMethods.getList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to re-embed:', error)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
onMounted(() => {
|
|
||||||
register(tableRef.value, elTableRef.value)
|
// 注册表格
|
||||||
})
|
register(tableRef as any ,elTableRef as any)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<content-wrap>
|
<div>
|
||||||
<Search :schema="schema"/>
|
<Table
|
||||||
</content-wrap>
|
ref="tableRef"
|
||||||
<content-wrap>
|
v-model:table="tableObject"
|
||||||
<Table ref="tableRef" :pagination="pagination" :columns="columns"/>
|
:columns="columns"
|
||||||
</content-wrap>
|
:schema="schema"
|
||||||
|
:label-width="100"
|
||||||
|
:search-table-height="true">
|
||||||
|
<template #action="{ row }">
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="handleReEmbed(row)">
|
||||||
|
重新向量化
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="tableMethods.delList([row.id])">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import {ref, onMounted} from 'vue'
|
||||||
import {useRoute, useRouter} from 'vue-router'
|
import {useRoute, useRouter} from 'vue-router'
|
||||||
import {CopyDocument, UploadFilled, Files, Document, Search} from '@element-plus/icons-vue'
|
import {CopyDocument, UploadFilled, Files, Document, Search} from '@element-plus/icons-vue'
|
||||||
import DataCut from "@/views/knowledge/dataset-form/components/data-cut.vue";
|
import DataCut from "@/views/knowledge/dataset-form/components/data-cut.vue";
|
||||||
import DataImport from "@/views/knowledge/dataset-form/components/data-import.vue";
|
import DataImport from "@/views/knowledge/dataset-form/components/data-import.vue";
|
||||||
import DataDocument from "@/views/knowledge/dataset-form/components/data-document.vue";
|
import DataDocument from "@/views/knowledge/dataset-form/components/data-document.vue";
|
||||||
import DataEmbedding from "@/views/knowledge/dataset-form/components/data-embedding.vue";
|
import DataEmbedding from "@/views/knowledge/dataset-form/components/data-embedding.vue";
|
||||||
const Route = useRoute()
|
import {KnowledgeApi} from '@/api/new-ai/knowledge'
|
||||||
const Router = useRouter()
|
import {ElMessage} from 'element-plus'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
const active = ref('1')
|
const active = ref('1')
|
||||||
|
const knowledgeData = ref({})
|
||||||
|
|
||||||
const tabs = ref([
|
const tabs = ref([
|
||||||
{
|
{
|
||||||
label: '数据导入',
|
label: '数据导入',
|
||||||
|
@ -34,6 +40,23 @@ const tabs = ref([
|
||||||
component: DataEmbedding
|
component: DataEmbedding
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const loadKnowledge = async () => {
|
||||||
|
const id = route.query.id as string
|
||||||
|
if (id) {
|
||||||
|
try {
|
||||||
|
const res = await KnowledgeApi.getKnowledge(id)
|
||||||
|
knowledgeData.value = res.data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load knowledge:', error)
|
||||||
|
ElMessage.error('加载知识库失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadKnowledge()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -41,7 +64,7 @@ const tabs = ref([
|
||||||
<div class="flex flex-col mr-30px w-350px">
|
<div class="flex flex-col mr-30px w-350px">
|
||||||
<el-button
|
<el-button
|
||||||
class="w-full mb-10px" plain type="primary"
|
class="w-full mb-10px" plain type="primary"
|
||||||
@click="Router.push({ path: '/ai/console/knowledge' })">知识库列表
|
@click="router.push({ path: '/ai/console/knowledge' })">知识库列表
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-scrollbar class="flex-1">
|
<el-scrollbar class="flex-1">
|
||||||
<div class="py-20px flex items-center border-b-solid border-gray border-1 mb-10px">
|
<div class="py-20px flex items-center border-b-solid border-gray border-1 mb-10px">
|
||||||
|
@ -86,16 +109,22 @@ const tabs = ref([
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style scoped>
|
||||||
.children {
|
.children {
|
||||||
height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - (var(--app-content-padding) * 3)) !important;
|
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 {
|
.icon-bg {
|
||||||
height: 100%;
|
border-radius: 8px;
|
||||||
background-color: #ffffff;
|
}
|
||||||
padding: 20px;
|
|
||||||
box-sizing: border-box;
|
:deep(.el-tabs__nav) {
|
||||||
}
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tabs__item) {
|
||||||
|
padding: 15px !important;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="knowledge-base-container">
|
<div class="knowledge-base-container">
|
||||||
<div class="card-container">
|
<div class="card-container">
|
||||||
<el-card class="create-card" shadow="hover">
|
<el-card class="create-card" shadow="hover" @click="toDatasetForm">
|
||||||
<div class="create-content">
|
<div class="create-content">
|
||||||
<el-icon class="create-icon"><Plus /></el-icon>
|
<el-icon class="create-icon"><Plus /></el-icon>
|
||||||
<span class="create-text">创建知识库</span>
|
<span class="create-text">创建知识库</span>
|
||||||
|
@ -11,21 +11,21 @@
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card v-for="index in 4" :key="index" class="document-card" shadow="hover"
|
<el-card v-for="item in tableData" :key="item.id" class="document-card" shadow="hover"
|
||||||
@click="toDataset(index)">
|
@click="toDataset(item.id)">
|
||||||
<div class="document-header">
|
<div class="document-header">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Folder/>
|
<Folder/>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span>接口鉴权示例代码.md</span>
|
<span>{{ item.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="document-info">
|
<div class="document-info">
|
||||||
<el-tag size="small">1 文档</el-tag>
|
<el-tag size="small">{{ item.documentCount || 0 }} 文档</el-tag>
|
||||||
<el-tag size="small" type="info">5 千字符</el-tag>
|
<el-tag size="small" type="info">{{ item.characterCount || 0 }} 千字符</el-tag>
|
||||||
<el-tag size="small" type="warning">0 关联应用</el-tag>
|
<el-tag size="small" type="warning">{{ item.appCount || 0 }} 关联应用</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<p class="document-description">
|
<p class="document-description">
|
||||||
useful for when you want to answer queries about the 接口鉴权示例代码.md
|
{{ item.description || '暂无描述' }}
|
||||||
</p>
|
</p>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,94 +47,128 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import {ref} from 'vue'
|
import {ref, onMounted} from 'vue'
|
||||||
import {Folder, Plus} from '@element-plus/icons-vue'
|
import {Plus, Folder} from '@element-plus/icons-vue'
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from 'vue-router'
|
||||||
|
import {KnowledgeApi} from '@/api/new-ai/knowledge'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(10)
|
const pageSize = ref(10)
|
||||||
const total = ref(100) // 假设总共有100条数据
|
const total = ref(0)
|
||||||
|
const tableData = ref([])
|
||||||
|
|
||||||
const handleSizeChange = (val) => {
|
const loadData = async () => {
|
||||||
console.log(`每页 ${val} 条`)
|
try {
|
||||||
|
const res = await KnowledgeApi.getKnowledgePage({
|
||||||
|
pageNo: currentPage.value,
|
||||||
|
pageSize: pageSize.value
|
||||||
|
})
|
||||||
|
tableData.value = res.data.list || []
|
||||||
|
total.value = res.data.total || 0
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load knowledge list:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCurrentChange = (val) => {
|
const handleSizeChange = (val: number) => {
|
||||||
console.log(`当前页: ${val}`)
|
pageSize.value = val
|
||||||
|
loadData()
|
||||||
}
|
}
|
||||||
const toDataset = (index) => {
|
|
||||||
router.push({path: '/ai/console/knowledge/' + index})
|
const handleCurrentChange = (val: number) => {
|
||||||
|
currentPage.value = val
|
||||||
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toDataset = (id: string) => {
|
||||||
|
router.push(`/ai/console/knowledge/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDatasetForm = () => {
|
||||||
|
router.push('/ai/console/knowledge/1')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.knowledge-base-container {
|
.knowledge-base-container {
|
||||||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||||
position: absolute;
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin: 0 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
top: 0;
|
|
||||||
bottom: 40px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-container {
|
.card-container {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: wrap; /* Enable wrapping */
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-bottom: auto; /* Pushes pagination to the bottom */
|
margin-bottom: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.create-card, .document-card {
|
|
||||||
flex: 1 1 360px; /* Allow cards to grow and shrink */
|
|
||||||
min-width: 0;
|
|
||||||
max-width: 400px;
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-card {
|
.create-card {
|
||||||
background-color: rgba(168, 168, 168, 0.22);
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-card:hover {
|
.create-card:hover {
|
||||||
background-color: #fff;
|
transform: translateY(-5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-content {
|
.create-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
justify-content: center;
|
||||||
margin-bottom: 15px;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-icon {
|
.create-icon {
|
||||||
font-size: 24px;
|
font-size: 40px;
|
||||||
color: #409EFF;
|
color: var(--el-color-primary);
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-text {
|
.create-text {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #303133;
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-footer {
|
.create-footer {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #909399;
|
padding: 10px;
|
||||||
line-height: 1.5;
|
border-top: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-header {
|
.document-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-header .el-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-header span {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-info {
|
.document-info {
|
||||||
|
@ -144,15 +178,16 @@ const toDataset = (index) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.document-description {
|
.document-description {
|
||||||
color: #606266;
|
color: var(--el-text-color-secondary);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.5;
|
margin: 0;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-container {
|
.pagination-container {
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|
Loading…
Reference in New Issue