fetch: 页面移植

会话模型、向量模型、文生图模型接口对接
This commit is contained in:
杨谢雨 2025-02-27 17:16:12 +08:00
parent eeb8d31f99
commit 2fd76aa5b9
24 changed files with 1226 additions and 317 deletions

61
src/api/new-ai/docs.ts Normal file
View File

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

View File

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

View File

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

View File

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

40
src/api/new-ai/model.ts Normal file
View File

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

56
src/api/new-ai/slice.ts Normal file
View File

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

View File

@ -191,7 +191,7 @@ export default defineComponent({
) {
slotsMap.default = () => renderOptions(item)
}
const formItemSlots: Recordable = setFormItemSlots(slots, item.field)
// labelMessage使
if (item?.labelMessage) {
@ -230,6 +230,7 @@ export default defineComponent({
return slots[item.field] ? (
getSlot(slots, item.field, formModel.value)
) : (
<>
<Com
vModel={formModel.value[item.field]}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
@ -242,6 +243,8 @@ export default defineComponent({
>
{{ ...slotsMap }}
</Com>
</>
)
}
}}

View File

@ -81,5 +81,9 @@ export function getColumns(provider: string) {
return zhipuColumns;
}
}
return openaiColumns;
return [...openaiColumns, {
label: '操作',
field: 'action',
width: '150',
}];
}

View File

@ -196,3 +196,9 @@ export const LLMProviders: any[] = [
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',
}

View File

@ -1,25 +1,55 @@
<script setup lang="ts">
import {Form} from '@/components/Form'
import {ref} from 'vue'
import {nextTick, ref} from 'vue'
import {FormSchema} from "@/types/form";
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 formData = ref({})
const formRef = ref()
const schemas = ref([])
const isEdit = ref(false)
const loading = ref(false)
const close = () => {
formData.value = {}
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
isEdit.value = !!data.id
await nextTick()
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)
// 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({
show,
close
@ -27,15 +57,14 @@ defineExpose({
</script>
<template>
<el-dialog v-model="visible" draggable title="编辑" width="800px" @close="close">
<Form ref="formRef" :model="formData" :schema="schemas" />
<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"/>
<template #footer>
<el-button type="primary">确认</el-button>
<el-button @click="close">取消</el-button>
<el-button :loading="loading" type="primary" @click="handleSubmit">确认</el-button>
<el-button :loading="loading" @click="close">取消</el-button>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
</style>

View File

@ -3,14 +3,12 @@
import {Table} from '@/components/Table';
import {Delete, Edit, Plus} from '@element-plus/icons-vue';
import editCom from './edit.vue';
import {computed, h, nextTick, reactive, ref} from 'vue';
import {getColumns} from './composables/columns.ts';
import {LLMProviders} from './composables/consts.ts';
// import { del, list as getModels } from '@/api/aigc/model';
import {computed, h, nextTick, reactive, ref, watch, onMounted} from 'vue';
import {getColumns} from './composables/columns';
import {LLMProviders, ModelTypeEnum} from './composables/consts';
import {ModelApi} from '@/api/new-ai/model';
import {ElMessage, ElMessageBox} from 'element-plus';
import {FormSchema} from "@/types/form";
// import { ModelTypeEnum } from '@/api/models';
import {getModels, ProviderEnum} from './composables/provider.ts';
import {getModels, ProviderEnum} from './composables/provider';
const formData = ref({
provider: ProviderEnum.OPENAI
@ -19,129 +17,101 @@ const message = ElMessage;
const dialog = ElMessageBox;
const actionRef = ref();
const editRef = ref();
const tableData = ref([
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
},
{
name: '1111'
}, {
name: '1111'
},
{
name: '1111'
const tableData = ref([]);
const loading = ref(false);
//
const loadData = async () => {
loading.value = true;
try {
const res = await ModelApi.getModelList({
provider: formData.value.provider,
type: ModelTypeEnum.CHAT
}).finally(() => {
loading.value = false;
});
console.log(res)
tableData.value = res;
} catch (error) {
console.error('Failed to load models:', error);
message.error('获取模型列表失败');
}
])
// 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),
// },
// ],
// });
// },
// });
};
// provider
watch(() => formData.value.provider, () => {
loadData();
});
//
onMounted(() => {
loadData();
});
const columns = computed(() => {
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 addModel() {
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) {
editRef.value.show(record);
}
function reloadTable() {
actionRef.value.reload();
async function reloadTable() {
await loadData();
}
function handleDel(record: any) {
dialog.warning({
title: '警告',
message: `你确定删除 [${record.name}] 模型吗?删除之后不可再用该模型对话`,
async function handleDel(record: any) {
dialog.confirm('确定要删除该模型吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '不确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
await del(record.id);
reloadTable();
message.success('模型删除成功');
try {
await ModelApi.deleteModel(record.id);
await reloadTable();
message.success('模型删除成功');
} catch (error) {
console.error('Failed to delete model:', error);
message.error('删除模型失败');
}
}).catch(() => {
//
});
}
</script>
<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>
<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>
<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" v-loading="loading">
<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" :pagination="false" >
<template #action="{row}">
<el-button text :icon="Edit" @click="handleEdit(row)"/>
<el-button text :icon="Delete" type="danger" @click="handleDel(row)"/>
</template>
</Table>
<editCom ref="editRef" @reload="reloadTable"/>
</div>
</div>
</ContentWrap>
</template>

View File

@ -1,20 +1,25 @@
import {ref} from "vue";
import {FormSchema} from "@/types/form";
import {ElTag} from "element-plus";
import {EmbedStoreApi} from "@/api/new-ai/embed-store";
export enum ProviderEnum {
Redis = 'REDIS',
PgVector = 'PGVECTOR',
Milvus = 'MILVUS',
}
export const ProviderConst = [
{ label: 'Redis', value: ProviderEnum.Redis },
{ label: 'PgVector', value: ProviderEnum.PgVector },
{ label: 'Milvus', value: ProviderEnum.Milvus },
];
export default function () {
const ProviderConst = [
{ label: 'Redis', value: ProviderEnum.Redis },
{ label: 'PgVector', value: ProviderEnum.PgVector },
{ label: 'Milvus', value: ProviderEnum.Milvus },
];
function getProviderLabel(value: any) {
const arr = ProviderConst.filter((i) => i.value === value);
if (arr === undefined || arr.length === 0) {
@ -22,6 +27,7 @@ export default function () {
}
return arr[0].label;
}
const shema = ref<FormSchema[]>([
{
label: '模型名称',
@ -32,6 +38,7 @@ export default function () {
},
}
])
const columns = ref<object[]>([
{
label: '数据库别名',
@ -59,7 +66,7 @@ export default function () {
label: '向量纬度',
field: 'dimension',
align: 'center',
width: '80',
width: '100',
render(row) {
return h(
ElTag,
@ -76,7 +83,6 @@ export default function () {
label: '数据库地址',
field: 'host',
align: 'center',
width: '110',
},
{
label: '数据库端口',
@ -85,32 +91,46 @@ export default function () {
width: '100',
},
{
label: '数据库用户名',
field: 'username',
label: '数据库',
field: 'database',
align: 'center',
},
{
label: '数据库密码',
field: 'password',
align: 'center',
},
{
label: '数据库名',
field: 'databaseName',
align: 'center',
},
{
label: '表名称',
field: 'tableName',
align: 'center',
},
])
]);
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 {
ProviderConst,
getProviderLabel,
shema,
columns,
tableData
tableData,
editRef,
open,
loadData,
handleSearch,
ProviderConst,
getProviderLabel
}
}

View File

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

View File

@ -1,13 +1,21 @@
<script setup lang="ts">
import {Plus} from "@element-plus/icons-vue";
import {Table} from "@/components/Table";
import {Search} from "@/components/Search";
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>
<template>
<content-wrap>
<Search :schema="shema"/>
<Search :schema="shema" @search="handleSearch" @reset="handleSearch"/>
</content-wrap>
<ContentWrap>
<el-alert
@ -16,8 +24,9 @@ const { shema, columns, tableData } = useEmbedStore()
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"/>
<el-button class="my-10px" type="primary" :icon="Plus" @click="open">新增向量数据库</el-button>
<Table height="calc(100% - 400px)" border :columns="columns" :data="tableData" :pagination="false"/>
<Edit ref="editRef" @reload="loadData" />
</ContentWrap>
</template>

View File

@ -1,12 +1,18 @@
import {ElTag} from "element-plus";
import {ref} from "vue";
import {ref, watch} from "vue";
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 () {
const formData = ref({
provider: ProviderEnum.OPENAI
})
const editRef = ref()
const formData = ref({
provider: ProviderEnum.OPENAI,
type: ModelTypeEnum.EMBEDDING
});
const tableData = ref([])
const baseColumns = [
{
label: '模型别名',
@ -19,7 +25,7 @@ export default function () {
},
{
label: '向量纬度',
field: 'dimension',
field: 'dimension',
align: 'center',
width: '100',
render(row) {
@ -41,17 +47,60 @@ export default function () {
{
label: 'Base Url',
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 = () => {
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 {
baseColumns,
tableData,
formData,
editRef,
open
open,
loadData,
handleDel,
handleEdit
}
}

View File

@ -1,25 +1,59 @@
<script setup lang="ts">
import {Form} from '@/components/Form'
import {ref} from 'vue'
import {nextTick, ref} from 'vue'
import {FormSchema} from "@/types/form";
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 formData = ref({})
const formRef = ref()
const schemas = ref([])
const isEdit = ref(false)
const close = () => {
visible.value = false
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
isEdit.value = !!data.id
await nextTick()
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)
// 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({
show,
close
@ -27,15 +61,14 @@ defineExpose({
</script>
<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" />
<template #footer>
<el-button type="primary">确认</el-button>
<el-button type="primary" @click="handleSubmit">确认</el-button>
<el-button @click="close">取消</el-button>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
</style>

View File

@ -1,11 +1,15 @@
<script setup lang="ts">
import {Plus} from "@element-plus/icons-vue";
import {Plus, Delete, Edit} from "@element-plus/icons-vue";
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 {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>
<template>
@ -27,10 +31,15 @@ const { baseColumns: columns, tableData, formData, editRef, open} = usePage()
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"/>
<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>
<Edit ref="editRef" />
<EditCom ref="editRef" @reload="handleReload" />
</ContentWrap>
</template>

View File

@ -1,51 +1,55 @@
import {computed, nextTick, ref, watch} from "vue";
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 () {
const editRef= ref()
const editRef = ref()
const formData = ref({
provider: ProviderEnum.OPENAI,
type: ModelTypeEnum.TEXT_IMAGE
});
const tableData = ref([])
const baseColumns = [
const baseColumns = [
{
label: '模型别名',
field: 'name',
label: '模型别名',
field: 'name',
},
{
label: '模型版本',
field: 'model',
label: '模型版本',
field: 'model',
},
];
const openaiColumns = [
const openaiColumns = [
...baseColumns,
{
label: 'Api Key',
field: 'apiKey',
label: 'Api Key',
field: 'apiKey',
},
];
const azureOpenaiColumns = [
const azureOpenaiColumns = [
...baseColumns,
{
label: 'Api Key',
field: 'apiKey',
label: 'Api Key',
field: 'apiKey',
},
{
label: 'Endpoint',
field: 'endpoint',
label: 'Endpoint',
field: 'endpoint',
},
{
label: 'Deployment Name',
field: 'azureDeploymentName',
label: 'Deployment Name',
field: 'azureDeploymentName',
},
];
const zhipuColumns = [...baseColumns];
const zhipuColumns = [...baseColumns];
function getColumns(provider: string) {
console.log(provider);
switch (provider) {
case ProviderEnum.OPENAI: {
return openaiColumns;
@ -56,21 +60,68 @@ export default function () {
case ProviderEnum.ZHIPU: {
return zhipuColumns;
}
default: {
return baseColumns;
}
}
return [];
}
const columns = computed(() => {
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 = () => {
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 {
columns,
tableData,
formData,
editRef,
open
open,
loadData,
handleDel,
handleEdit
}
}

View File

@ -1,25 +1,55 @@
<script setup lang="ts">
import {Form} from '@/components/Form'
import {ref} from 'vue'
import {nextTick, ref} from 'vue'
import {FormSchema} from "@/types/form";
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 formData = ref({})
const formRef = ref()
const schemas = ref([])
const isEdit = ref(false)
const loading = ref(false)
const close = () => {
formData.value = {}
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
isEdit.value = !!data.id
await nextTick()
formRef.value.setValues(data)
schemas.value = (getSchemas(data.provider) as FormSchema[]);
// formRef.value.setSchema(schemas.value)
// models.value = getModels(data.provider, LLMProviders)
schemas.value = getSchemas(data.provider).slice(1);
formRef.value.setSchema(schemas.value)
}
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({
show,
close
@ -27,15 +57,13 @@ defineExpose({
</script>
<template>
<el-dialog v-model="visible" draggable title="编辑" width="800px" @close="close">
<Form ref="formRef" :model="formData" :schema="schemas" />
<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"/>
<template #footer>
<el-button type="primary">确认</el-button>
<el-button @click="close">取消</el-button>
<el-button :loading="loading" type="primary" @click="handleSubmit">确认</el-button>
<el-button :loading="loading" @click="close">取消</el-button>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
</style>

View File

@ -1,11 +1,16 @@
<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 {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>
<template>
@ -19,20 +24,26 @@ const {columns, tableData, formData, editRef, open} = usePage()
<span>{{ item.name }}</span>
</div>
</el-scrollbar>
<div class="h-full p-20px">
<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 :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%"/>
<Table
: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>
<Edit ref="editRef"/>
<EditCom ref="editRef" @reload="handleReload"/>
</ContentWrap>
</template>
@ -51,21 +62,18 @@ const {columns, tableData, formData, editRef, open} = usePage()
}
.menu {
transition: all .15s;
padding: 10px;
margin-bottom: 10px;
border-radius: 6px;
cursor: pointer;
padding: 12px 10px;
border-radius: 5px;
margin-bottom: 20px;
&.active {
color: #ffffff;
background-color: var(--el-color-primary);
}
&:hover {
&:not(&.active) {
background-color: var(--el-color-info-light-7);
}
background: var(--el-fill-color-light);
}
&.active {
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
}
</style>

View File

@ -1,6 +1,17 @@
<script setup lang="ts">
import {useTable} from "@/hooks/web/useTable";
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 elTableRef = ref()
const columns = [
@ -39,16 +50,36 @@ const columns = [
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[]>([
{
label: '所属文档',
field: 'text',
field: 'docId',
component: 'Select',
componentProps: {
style: {
width: '150px'
}
},
placeholder: '请选择所属文档'
}
}
])
@ -60,17 +91,29 @@ const pagination = computed(() => {
}
})
onMounted(() => {
register(tableRef.value, elTableRef.value)
register(tableRef,elTableRef,)
})
</script>
<template>
<content-wrap>
<Search :schema="schema"/>
</content-wrap>
<content-wrap>
<Table ref="tableRef" :pagination="pagination" :columns="columns"/>
</content-wrap>
<div>
<Table
ref="tableRef"
v-model:table="tableObject"
:columns="columns"
: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>
<style scoped lang="scss">

View File

@ -1,8 +1,20 @@
<script setup lang="ts">
import {useTable} from "@/hooks/web/useTable";
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 elTableRef = ref()
const columns = [
{
label: '文档名称',
@ -45,40 +57,86 @@ const columns = [
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[]>([
{
label: '文档名称',
field: 'text',
component: 'Select',
field: 'name',
component: 'Input',
componentProps: {
style: {
width: '150px'
}
},
placeholder: '请输入文档名称'
}
}
])
const pagination = computed(() => {
return {
pageSize: tableObject.pageSize,
currentPage: tableObject.currentPage,
total: tableObject.total,
//
const handleReEmbed = async (row) => {
try {
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>
<template>
<content-wrap>
<Search :schema="schema"/>
</content-wrap>
<content-wrap>
<Table ref="tableRef" :pagination="pagination" :columns="columns"/>
</content-wrap>
<div>
<Table
ref="tableRef"
v-model:table="tableObject"
:columns="columns"
: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>
<style scoped lang="scss">
<style scoped>
</style>

View File

@ -1,13 +1,19 @@
<script lang="ts" setup>
import {ref, onMounted} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import {CopyDocument, UploadFilled, Files, Document, Search} from '@element-plus/icons-vue'
import DataCut from "@/views/knowledge/dataset-form/components/data-cut.vue";
import DataImport from "@/views/knowledge/dataset-form/components/data-import.vue";
import DataDocument from "@/views/knowledge/dataset-form/components/data-document.vue";
import DataEmbedding from "@/views/knowledge/dataset-form/components/data-embedding.vue";
const Route = useRoute()
const Router = useRouter()
import {KnowledgeApi} from '@/api/new-ai/knowledge'
import {ElMessage} from 'element-plus'
const route = useRoute()
const router = useRouter()
const active = ref('1')
const knowledgeData = ref({})
const tabs = ref([
{
label: '数据导入',
@ -34,6 +40,23 @@ const tabs = ref([
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>
<template>
@ -41,7 +64,7 @@ const tabs = ref([
<div class="flex flex-col mr-30px w-350px">
<el-button
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-scrollbar class="flex-1">
<div class="py-20px flex items-center border-b-solid border-gray border-1 mb-10px">
@ -86,16 +109,22 @@ const tabs = ref([
</div>
</template>
<style lang="scss" scoped>
<style 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 {
height: 100%;
background-color: #ffffff;
padding: 20px;
box-sizing: border-box;
}
.icon-bg {
border-radius: 8px;
}
:deep(.el-tabs__nav) {
width: 100%;
}
:deep(.el-tabs__item) {
padding: 15px !important;
text-align: left;
width: 100%;
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="knowledge-base-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">
<el-icon class="create-icon"><Plus /></el-icon>
<span class="create-text">创建知识库</span>
@ -11,21 +11,21 @@
</div>
</el-card>
<el-card v-for="index in 4" :key="index" class="document-card" shadow="hover"
@click="toDataset(index)">
<el-card v-for="item in tableData" :key="item.id" class="document-card" shadow="hover"
@click="toDataset(item.id)">
<div class="document-header">
<el-icon>
<Folder/>
</el-icon>
<span>接口鉴权示例代码.md</span>
<span>{{ item.name }}</span>
</div>
<div class="document-info">
<el-tag size="small">1 文档</el-tag>
<el-tag size="small" type="info">5 千字符</el-tag>
<el-tag size="small" type="warning">0 关联应用</el-tag>
<el-tag size="small">{{ item.documentCount || 0 }} 文档</el-tag>
<el-tag size="small" type="info">{{ item.characterCount || 0 }} 千字符</el-tag>
<el-tag size="small" type="warning">{{ item.appCount || 0 }} 关联应用</el-tag>
</div>
<p class="document-description">
useful for when you want to answer queries about the 接口鉴权示例代码.md
{{ item.description || '暂无描述' }}
</p>
</el-card>
</div>
@ -47,94 +47,128 @@
</div>
</template>
<script setup>
import {ref} from 'vue'
import {Folder, Plus} from '@element-plus/icons-vue'
import {useRouter} from "vue-router";
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import {Plus, Folder} from '@element-plus/icons-vue'
import {useRouter} from 'vue-router'
import {KnowledgeApi} from '@/api/new-ai/knowledge'
const router = useRouter()
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(100) // 100
const total = ref(0)
const tableData = ref([])
const handleSizeChange = (val) => {
console.log(`每页 ${val}`)
const loadData = async () => {
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) => {
console.log(`当前页: ${val}`)
const handleSizeChange = (val: number) => {
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>
<style scoped>
.knowledge-base-container {
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
position: absolute;
padding: 20px;
margin: 0 auto;
display: flex;
flex-direction: column;
top: 0;
bottom: 40px;
width: 100%;
}
.card-container {
display: flex;
flex-wrap: wrap; /* Enable wrapping */
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-bottom: auto; /* Pushes pagination to the bottom */
}
.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;
margin-bottom: 20px;
}
.create-card {
background-color: rgba(168, 168, 168, 0.22);
cursor: pointer;
transition: all 0.3s;
}
.create-card:hover {
background-color: #fff;
transform: translateY(-5px);
}
.create-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin-bottom: 15px;
justify-content: center;
padding: 20px 0;
}
.create-icon {
font-size: 24px;
color: #409EFF;
font-size: 40px;
color: var(--el-color-primary);
margin-bottom: 10px;
}
.create-text {
font-size: 18px;
font-weight: bold;
color: #303133;
color: var(--el-color-primary);
}
.create-footer {
text-align: center;
color: var(--el-text-color-secondary);
font-size: 14px;
color: #909399;
line-height: 1.5;
padding: 10px;
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 {
display: flex;
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-weight: bold;
margin-bottom: 15px;
}
.document-info {
@ -144,15 +178,16 @@ const toDataset = (index) => {
}
.document-description {
color: #606266;
color: var(--el-text-color-secondary);
font-size: 14px;
line-height: 1.5;
margin: 0;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.pagination-container {
position: absolute;
width: 100%;
bottom: 0;
display: flex;
justify-content: center;
margin-top: 20px;