2024-07-16 18:14:27 +08:00
|
|
|
|
<template>
|
2025-06-27 01:55:04 +08:00
|
|
|
|
<div class="database-container layout-container">
|
2025-05-26 20:39:31 +08:00
|
|
|
|
<HeaderComponent title="文档知识库" :loading="state.loading">
|
2024-09-14 02:46:44 +08:00
|
|
|
|
<template #actions>
|
2025-06-27 01:55:04 +08:00
|
|
|
|
<a-button type="primary" @click="state.openNewDatabaseModel=true">
|
2025-05-28 15:16:07 +08:00
|
|
|
|
新建知识库
|
|
|
|
|
|
</a-button>
|
2024-09-14 02:46:44 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</HeaderComponent>
|
|
|
|
|
|
|
2025-07-21 18:18:47 +08:00
|
|
|
|
<a-modal :open="state.openNewDatabaseModel" title="新建知识库" @ok="createDatabase" @cancel="cancelCreateDatabase" class="new-database-modal" width="800px">
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 知识库类型选择 -->
|
2025-11-25 22:34:44 +08:00
|
|
|
|
<h3>知识库类型<span style="color: var(--color-error-500)">*</span></h3>
|
2025-07-26 03:36:54 +08:00
|
|
|
|
<div class="kb-type-cards">
|
|
|
|
|
|
<div
|
2025-12-03 15:26:58 +08:00
|
|
|
|
v-for="(typeInfo, typeKey) in orderedKbTypes"
|
2025-07-26 03:36:54 +08:00
|
|
|
|
:key="typeKey"
|
|
|
|
|
|
class="kb-type-card"
|
|
|
|
|
|
:class="{ active: newDatabase.kb_type === typeKey }"
|
2025-12-03 15:26:58 +08:00
|
|
|
|
:data-type="typeKey"
|
2025-07-26 03:36:54 +08:00
|
|
|
|
@click="handleKbTypeChange(typeKey)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<component :is="getKbTypeIcon(typeKey)" class="type-icon" />
|
|
|
|
|
|
<span class="type-title">{{ getKbTypeLabel(typeKey) }}</span>
|
2025-12-03 15:26:58 +08:00
|
|
|
|
<a-tooltip
|
|
|
|
|
|
v-if="typeKey === 'chroma'"
|
|
|
|
|
|
title="Chroma 已标记为弃用状态,建议使用 Milvus 替代。同时会在下个正式版本中移除。"
|
|
|
|
|
|
placement="top"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="deprecated-badge">弃用</span>
|
|
|
|
|
|
</a-tooltip>
|
2025-07-21 18:18:47 +08:00
|
|
|
|
</div>
|
2025-07-26 03:36:54 +08:00
|
|
|
|
<div class="card-description">{{ typeInfo.description }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-07-21 18:18:47 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 类型说明 -->
|
2025-07-26 03:36:54 +08:00
|
|
|
|
<!-- <div class="kb-type-guide" v-if="newDatabase.kb_type">
|
2025-07-21 18:18:47 +08:00
|
|
|
|
<a-alert
|
|
|
|
|
|
:message="getKbTypeDescription(newDatabase.kb_type)"
|
|
|
|
|
|
:type="getKbTypeAlertType(newDatabase.kb_type)"
|
|
|
|
|
|
show-icon
|
|
|
|
|
|
style="margin: 12px 0;"
|
|
|
|
|
|
/>
|
2025-07-26 03:36:54 +08:00
|
|
|
|
</div> -->
|
2025-07-21 18:18:47 +08:00
|
|
|
|
|
2025-11-25 22:34:44 +08:00
|
|
|
|
<h3>知识库名称<span style="color: var(--color-error-500)">*</span></h3>
|
2025-07-21 18:18:47 +08:00
|
|
|
|
<a-input v-model:value="newDatabase.name" placeholder="新建知识库名称" size="large" />
|
|
|
|
|
|
|
2025-06-27 01:55:04 +08:00
|
|
|
|
<h3>嵌入模型</h3>
|
2025-12-07 17:17:20 +08:00
|
|
|
|
<EmbeddingModelSelector
|
|
|
|
|
|
v-model:value="newDatabase.embed_model_name"
|
|
|
|
|
|
style="width: 100%;"
|
|
|
|
|
|
size="large"
|
|
|
|
|
|
placeholder="请选择嵌入模型"
|
|
|
|
|
|
/>
|
2025-07-26 03:36:54 +08:00
|
|
|
|
|
2025-08-16 20:07:12 +08:00
|
|
|
|
<!-- 仅对 LightRAG 提供语言选择和LLM选择 -->
|
|
|
|
|
|
<div v-if="newDatabase.kb_type === 'lightrag'">
|
|
|
|
|
|
<h3 style="margin-top: 20px;">语言</h3>
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="newDatabase.language"
|
|
|
|
|
|
:options="languageOptions"
|
|
|
|
|
|
style="width: 100%;"
|
|
|
|
|
|
size="large"
|
|
|
|
|
|
:dropdown-match-select-width="false"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<h3 style="margin-top: 20px;">语言模型 (LLM)</h3>
|
|
|
|
|
|
<p style="color: var(--gray-700); font-size: 14px;">可以在设置中配置语言模型</p>
|
|
|
|
|
|
<ModelSelectorComponent
|
2025-10-14 02:27:15 +08:00
|
|
|
|
:model_spec="llmModelSpec"
|
|
|
|
|
|
placeholder="请选择模型"
|
2025-08-16 20:07:12 +08:00
|
|
|
|
@select-model="handleLLMSelect"
|
2025-10-14 02:27:15 +08:00
|
|
|
|
size="large"
|
2025-08-16 20:07:12 +08:00
|
|
|
|
style="width: 100%; height: 60px;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-07-21 18:18:47 +08:00
|
|
|
|
|
2025-04-11 11:54:45 +08:00
|
|
|
|
<h3 style="margin-top: 20px;">知识库描述</h3>
|
|
|
|
|
|
<p style="color: var(--gray-700); font-size: 14px;">在智能体流程中,这里的描述会作为工具的描述。智能体会根据知识库的标题和描述来选择合适的工具。所以这里描述的越详细,智能体越容易选择到合适的工具。</p>
|
2025-12-18 21:01:25 +08:00
|
|
|
|
<AiTextarea
|
|
|
|
|
|
v-model="newDatabase.description"
|
|
|
|
|
|
:name="newDatabase.name"
|
2025-04-11 11:54:45 +08:00
|
|
|
|
placeholder="新建知识库描述"
|
2025-12-03 15:26:58 +08:00
|
|
|
|
:auto-size="{ minRows: 3, maxRows: 10 }"
|
2024-07-16 18:14:27 +08:00
|
|
|
|
/>
|
2025-10-26 15:08:43 +08:00
|
|
|
|
|
|
|
|
|
|
<h3 style="margin-top: 20px;">隐私设置</h3>
|
|
|
|
|
|
<div class="privacy-config">
|
|
|
|
|
|
<a-switch
|
|
|
|
|
|
v-model:checked="newDatabase.is_private"
|
|
|
|
|
|
checked-children="私有"
|
|
|
|
|
|
un-checked-children="公开"
|
|
|
|
|
|
size="default"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span style="margin-left: 12px;">设置为私有知识库</span>
|
|
|
|
|
|
<a-tooltip title="在部分智能体的设计中,可以根据隐私标志来决定启用什么模型和策略。例如,对于私有知识库,可以选择更严格的数据处理和访问控制策略,以保护敏感信息的安全性和隐私性。">
|
|
|
|
|
|
<InfoCircleOutlined style="margin-left: 8px; color: var(--gray-500); cursor: help;" />
|
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
|
</div>
|
2025-11-06 19:47:22 +08:00
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="['chroma', 'milvus'].includes(newDatabase.kb_type)"
|
|
|
|
|
|
class="reranker-config"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="reranker-row">
|
|
|
|
|
|
<div class="reranker-title">
|
|
|
|
|
|
<span>启用重排序</span>
|
|
|
|
|
|
<a-tooltip title="向量检索后使用交叉编码模型对候选文档重新排序,提升召回质量。">
|
|
|
|
|
|
<QuestionCircleOutlined class="hint-icon" />
|
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-switch
|
|
|
|
|
|
v-model:checked="newDatabase.reranker.enabled"
|
|
|
|
|
|
:disabled="rerankerOptions.length === 0"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<transition name="fade">
|
|
|
|
|
|
<div v-if="newDatabase.reranker.enabled" class="reranker-form">
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<label>重排序模型</label>
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="newDatabase.reranker.model"
|
|
|
|
|
|
:options="rerankerOptions"
|
|
|
|
|
|
placeholder="选择重排序模型"
|
|
|
|
|
|
:disabled="rerankerOptions.length === 0"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<p class="field-hint" v-if="rerankerOptions.length === 0">
|
|
|
|
|
|
暂无可用模型,请在系统配置中添加。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-grid">
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<label>召回数量</label>
|
|
|
|
|
|
<a-input-number
|
|
|
|
|
|
v-model:value="newDatabase.reranker.recall_top_k"
|
|
|
|
|
|
:min="10"
|
|
|
|
|
|
:max="200"
|
|
|
|
|
|
:step="5"
|
|
|
|
|
|
style="width: 100%;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<p class="field-hint">向量检索阶段保留的候选数量</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-field">
|
|
|
|
|
|
<label>最终返回数</label>
|
|
|
|
|
|
<a-input-number
|
|
|
|
|
|
v-model:value="newDatabase.reranker.final_top_k"
|
|
|
|
|
|
:min="1"
|
|
|
|
|
|
:max="100"
|
|
|
|
|
|
style="width: 100%;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<p class="field-hint">重排序后返回给前端的文档数量</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</transition>
|
|
|
|
|
|
</div>
|
2024-07-16 18:14:27 +08:00
|
|
|
|
<template #footer>
|
2025-06-27 01:55:04 +08:00
|
|
|
|
<a-button key="back" @click="cancelCreateDatabase">取消</a-button>
|
|
|
|
|
|
<a-button key="submit" type="primary" :loading="state.creating" @click="createDatabase">创建</a-button>
|
2024-07-16 18:14:27 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</a-modal>
|
2025-08-16 20:07:12 +08:00
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
<!-- 加载状态 -->
|
|
|
|
|
|
<div v-if="state.loading" class="loading-container">
|
|
|
|
|
|
<a-spin size="large" />
|
|
|
|
|
|
<p>正在加载知识库...</p>
|
|
|
|
|
|
</div>
|
2025-08-16 20:07:12 +08:00
|
|
|
|
|
2025-12-03 15:26:58 +08:00
|
|
|
|
<!-- 空状态显示 -->
|
|
|
|
|
|
<div v-else-if="!databases || databases.length === 0" class="empty-state">
|
|
|
|
|
|
<h3 class="empty-title">暂无知识库</h3>
|
|
|
|
|
|
<p class="empty-description">创建您的第一个知识库,开始管理文档和知识</p>
|
|
|
|
|
|
<a-button type="primary" size="large" @click="state.openNewDatabaseModel = true">
|
|
|
|
|
|
<template #icon>
|
|
|
|
|
|
<PlusOutlined />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
创建知识库
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
<!-- 数据库列表 -->
|
|
|
|
|
|
<div v-else class="databases">
|
2024-07-16 18:14:27 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-for="database in databases"
|
|
|
|
|
|
:key="database.db_id"
|
|
|
|
|
|
class="database dbcard"
|
|
|
|
|
|
@click="navigateToDatabase(database.db_id)">
|
2025-10-26 15:08:43 +08:00
|
|
|
|
<!-- 私有知识库锁定图标 -->
|
|
|
|
|
|
<LockOutlined
|
|
|
|
|
|
v-if="database.metadata?.is_private"
|
|
|
|
|
|
class="private-lock-icon"
|
|
|
|
|
|
title="私有知识库"
|
|
|
|
|
|
/>
|
2024-07-16 18:14:27 +08:00
|
|
|
|
<div class="top">
|
2025-07-21 18:18:47 +08:00
|
|
|
|
<div class="icon">
|
|
|
|
|
|
<component :is="getKbTypeIcon(database.kb_type || 'lightrag')" />
|
|
|
|
|
|
</div>
|
2024-07-16 18:14:27 +08:00
|
|
|
|
<div class="info">
|
|
|
|
|
|
<h3>{{ database.name }}</h3>
|
2025-07-26 03:36:54 +08:00
|
|
|
|
<p>
|
|
|
|
|
|
<span>{{ database.files ? Object.keys(database.files).length : 0 }} 文件</span>
|
|
|
|
|
|
<span class="created-time-inline" v-if="database.created_at">
|
2025-12-01 16:26:52 +08:00
|
|
|
|
{{ formatCreatedTime(database.created_at) }}
|
2025-07-26 03:36:54 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
</p>
|
2024-07-16 18:14:27 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-07-26 03:36:54 +08:00
|
|
|
|
<!-- <a-tooltip :title="database.description || '暂无描述'">
|
2025-05-15 23:06:19 +08:00
|
|
|
|
<p class="description">{{ database.description || '暂无描述' }}</p>
|
2025-07-26 03:36:54 +08:00
|
|
|
|
</a-tooltip> -->
|
|
|
|
|
|
<p class="description">{{ database.description || '暂无描述' }}</p>
|
2024-07-22 00:00:54 +08:00
|
|
|
|
<div class="tags">
|
2025-06-27 01:55:04 +08:00
|
|
|
|
<a-tag color="blue" v-if="database.embed_info?.name">{{ database.embed_info.name }}</a-tag>
|
2025-08-16 20:07:12 +08:00
|
|
|
|
<!-- <a-tag color="green" v-if="database.embed_info?.dimension">{{ database.embed_info.dimension }}</a-tag> -->
|
2025-07-21 18:18:47 +08:00
|
|
|
|
<a-tag
|
|
|
|
|
|
:color="getKbTypeColor(database.kb_type || 'lightrag')"
|
|
|
|
|
|
class="kb-type-tag"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ getKbTypeLabel(database.kb_type || 'lightrag') }}
|
|
|
|
|
|
</a-tag>
|
2025-10-26 15:08:43 +08:00
|
|
|
|
</div>
|
2025-07-26 03:36:54 +08:00
|
|
|
|
|
2024-07-16 18:14:27 +08:00
|
|
|
|
<!-- <button @click="deleteDatabase(database.collection_name)">删除</button> -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2025-06-27 01:55:04 +08:00
|
|
|
|
import { ref, onMounted, reactive, watch, computed } from 'vue'
|
2024-07-17 18:52:20 +08:00
|
|
|
|
import { useRouter, useRoute } from 'vue-router';
|
2024-07-31 20:22:05 +08:00
|
|
|
|
import { useConfigStore } from '@/stores/config';
|
2025-06-27 01:55:04 +08:00
|
|
|
|
import { message } from 'ant-design-vue'
|
2025-11-06 21:59:48 +08:00
|
|
|
|
import { Database, Zap, FileDigit, Waypoints, Building2 } from 'lucide-vue-next';
|
2025-12-03 15:26:58 +08:00
|
|
|
|
import { LockOutlined, InfoCircleOutlined, QuestionCircleOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
2025-07-22 17:29:38 +08:00
|
|
|
|
import { databaseApi, typeApi } from '@/apis/knowledge_api';
|
2025-06-27 01:55:04 +08:00
|
|
|
|
import HeaderComponent from '@/components/HeaderComponent.vue';
|
2025-08-16 20:07:12 +08:00
|
|
|
|
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
2025-12-07 17:17:20 +08:00
|
|
|
|
import EmbeddingModelSelector from '@/components/EmbeddingModelSelector.vue';
|
2025-10-13 15:08:54 +08:00
|
|
|
|
import dayjs, { parseToShanghai } from '@/utils/time';
|
2025-12-18 21:01:25 +08:00
|
|
|
|
import AiTextarea from '@/components/AiTextarea.vue';
|
2024-07-16 18:14:27 +08:00
|
|
|
|
|
2024-07-17 18:52:20 +08:00
|
|
|
|
const route = useRoute()
|
2024-07-16 18:14:27 +08:00
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
const databases = ref([])
|
2024-07-31 20:22:05 +08:00
|
|
|
|
const configStore = useConfigStore()
|
2024-07-25 20:30:28 +08:00
|
|
|
|
|
2025-05-26 20:39:31 +08:00
|
|
|
|
const state = reactive({
|
|
|
|
|
|
loading: false,
|
2025-06-27 01:55:04 +08:00
|
|
|
|
creating: false,
|
|
|
|
|
|
openNewDatabaseModel: false,
|
2025-05-26 20:39:31 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-06-27 01:55:04 +08:00
|
|
|
|
|
2025-08-16 20:07:12 +08:00
|
|
|
|
// 语言选项(值使用英文,以保证后端/LightRAG 兼容;标签为中英文方便理解)
|
|
|
|
|
|
const languageOptions = [
|
|
|
|
|
|
{ label: '英语 English', value: 'English' },
|
|
|
|
|
|
{ label: '中文 Chinese', value: 'Chinese' },
|
|
|
|
|
|
{ label: '日语 Japanese', value: 'Japanese' },
|
|
|
|
|
|
{ label: '韩语 Korean', value: 'Korean' },
|
|
|
|
|
|
{ label: '德语 German', value: 'German' },
|
|
|
|
|
|
{ label: '法语 French', value: 'French' },
|
|
|
|
|
|
{ label: '西班牙语 Spanish', value: 'Spanish' },
|
|
|
|
|
|
{ label: '葡萄牙语 Portuguese', value: 'Portuguese' },
|
|
|
|
|
|
{ label: '俄语 Russian', value: 'Russian' },
|
|
|
|
|
|
{ label: '阿拉伯语 Arabic', value: 'Arabic' },
|
|
|
|
|
|
{ label: '印地语 Hindi', value: 'Hindi' },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2025-11-06 19:47:22 +08:00
|
|
|
|
const createEmptyDatabaseForm = () => ({
|
2024-07-16 18:14:27 +08:00
|
|
|
|
name: '',
|
|
|
|
|
|
description: '',
|
2025-06-27 12:22:35 +08:00
|
|
|
|
embed_model_name: configStore.config?.embed_model,
|
2025-12-03 15:26:58 +08:00
|
|
|
|
kb_type: 'milvus',
|
2025-11-06 19:47:22 +08:00
|
|
|
|
is_private: false,
|
|
|
|
|
|
storage: '',
|
2025-08-16 20:07:12 +08:00
|
|
|
|
language: 'English',
|
|
|
|
|
|
llm_info: {
|
|
|
|
|
|
provider: '',
|
|
|
|
|
|
model_name: ''
|
|
|
|
|
|
},
|
2025-11-06 19:47:22 +08:00
|
|
|
|
reranker: {
|
|
|
|
|
|
enabled: false,
|
|
|
|
|
|
model: '',
|
|
|
|
|
|
recall_top_k: 50,
|
|
|
|
|
|
final_top_k: 10,
|
|
|
|
|
|
}
|
2024-07-16 18:14:27 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-11-06 19:47:22 +08:00
|
|
|
|
const newDatabase = reactive(createEmptyDatabaseForm())
|
|
|
|
|
|
|
|
|
|
|
|
const rerankerOptions = computed(() =>
|
|
|
|
|
|
Object.entries(configStore?.config?.reranker_names || {}).map(([value, info]) => ({
|
|
|
|
|
|
label: info?.name || value,
|
|
|
|
|
|
value
|
|
|
|
|
|
}))
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const isVectorKb = computed(() => ['chroma', 'milvus'].includes(newDatabase.kb_type))
|
|
|
|
|
|
|
2025-10-14 02:27:15 +08:00
|
|
|
|
const llmModelSpec = computed(() => {
|
|
|
|
|
|
const provider = newDatabase.llm_info?.provider || ''
|
|
|
|
|
|
const modelName = newDatabase.llm_info?.model_name || ''
|
|
|
|
|
|
if (provider && modelName) {
|
|
|
|
|
|
return `${provider}/${modelName}`
|
|
|
|
|
|
}
|
|
|
|
|
|
return ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-07-21 18:18:47 +08:00
|
|
|
|
// 支持的知识库类型
|
|
|
|
|
|
const supportedKbTypes = ref({})
|
|
|
|
|
|
|
2025-12-03 15:26:58 +08:00
|
|
|
|
// 有序的知识库类型(Chroma 排在最后)
|
|
|
|
|
|
const orderedKbTypes = computed(() => {
|
|
|
|
|
|
const types = { ...supportedKbTypes.value }
|
|
|
|
|
|
const ordered = {}
|
|
|
|
|
|
const chromaData = types.chroma
|
|
|
|
|
|
|
|
|
|
|
|
// 先添加除了 Chroma 之外的所有类型
|
|
|
|
|
|
Object.keys(types).forEach(key => {
|
|
|
|
|
|
if (key !== 'chroma') {
|
|
|
|
|
|
ordered[key] = types[key]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 最后添加 Chroma(如果存在)
|
|
|
|
|
|
if (chromaData) {
|
|
|
|
|
|
ordered.chroma = chromaData
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ordered
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-07-21 18:18:47 +08:00
|
|
|
|
// 加载支持的知识库类型
|
|
|
|
|
|
const loadSupportedKbTypes = async () => {
|
|
|
|
|
|
try {
|
2025-07-22 17:29:38 +08:00
|
|
|
|
const data = await typeApi.getKnowledgeBaseTypes()
|
2025-07-21 18:18:47 +08:00
|
|
|
|
supportedKbTypes.value = data.kb_types
|
|
|
|
|
|
console.log('支持的知识库类型:', supportedKbTypes.value)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载知识库类型失败:', error)
|
|
|
|
|
|
// 如果加载失败,设置默认类型
|
|
|
|
|
|
supportedKbTypes.value = {
|
|
|
|
|
|
lightrag: {
|
|
|
|
|
|
description: "基于图检索的知识库,支持实体关系构建和复杂查询",
|
|
|
|
|
|
class_name: "LightRagKB"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 19:47:22 +08:00
|
|
|
|
// 重排序模型信息现在直接从 configStore.config.reranker_names 获取,无需单独加载
|
|
|
|
|
|
|
2024-07-17 18:52:20 +08:00
|
|
|
|
const loadDatabases = () => {
|
2025-05-26 20:39:31 +08:00
|
|
|
|
state.loading = true
|
2024-09-01 21:14:23 +08:00
|
|
|
|
// loadGraph()
|
2025-07-22 17:29:38 +08:00
|
|
|
|
databaseApi.getDatabases()
|
2024-07-16 18:14:27 +08:00
|
|
|
|
.then(data => {
|
|
|
|
|
|
console.log(data)
|
2025-07-26 03:36:54 +08:00
|
|
|
|
// 按照创建时间排序,最新的在前面
|
|
|
|
|
|
databases.value = data.databases.sort((a, b) => {
|
2025-10-13 15:08:54 +08:00
|
|
|
|
const timeA = parseToShanghai(a.created_at)
|
|
|
|
|
|
const timeB = parseToShanghai(b.created_at)
|
|
|
|
|
|
if (!timeA && !timeB) return 0
|
|
|
|
|
|
if (!timeA) return 1
|
|
|
|
|
|
if (!timeB) return -1
|
|
|
|
|
|
return timeB.valueOf() - timeA.valueOf() // 降序排列,最新的在前面
|
2025-07-26 03:36:54 +08:00
|
|
|
|
})
|
2025-05-26 20:39:31 +08:00
|
|
|
|
state.loading = false
|
2025-05-02 23:56:59 +08:00
|
|
|
|
})
|
|
|
|
|
|
.catch(error => {
|
|
|
|
|
|
console.error('加载数据库列表失败:', error);
|
|
|
|
|
|
if (error.message.includes('权限')) {
|
|
|
|
|
|
message.error('需要管理员权限访问知识库')
|
2025-05-26 20:39:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
state.loading = false
|
2025-05-02 23:56:59 +08:00
|
|
|
|
})
|
2024-07-16 18:14:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-27 01:55:04 +08:00
|
|
|
|
const resetNewDatabase = () => {
|
2025-11-06 19:47:22 +08:00
|
|
|
|
Object.assign(newDatabase, createEmptyDatabaseForm())
|
2025-06-27 01:55:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const cancelCreateDatabase = () => {
|
|
|
|
|
|
state.openNewDatabaseModel = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-21 18:18:47 +08:00
|
|
|
|
// 知识库类型相关工具方法
|
|
|
|
|
|
const getKbTypeLabel = (type) => {
|
|
|
|
|
|
const labels = {
|
|
|
|
|
|
lightrag: 'LightRAG',
|
|
|
|
|
|
chroma: 'Chroma',
|
|
|
|
|
|
milvus: 'Milvus'
|
|
|
|
|
|
}
|
|
|
|
|
|
return labels[type] || type
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getKbTypeIcon = (type) => {
|
|
|
|
|
|
const icons = {
|
2025-07-26 03:36:54 +08:00
|
|
|
|
lightrag: Waypoints,
|
|
|
|
|
|
chroma: FileDigit,
|
|
|
|
|
|
milvus: Building2
|
2025-07-21 18:18:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
return icons[type] || Database
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getKbTypeColor = (type) => {
|
|
|
|
|
|
const colors = {
|
|
|
|
|
|
lightrag: 'purple',
|
|
|
|
|
|
chroma: 'orange',
|
|
|
|
|
|
milvus: 'red'
|
|
|
|
|
|
}
|
|
|
|
|
|
return colors[type] || 'blue'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-26 03:36:54 +08:00
|
|
|
|
// 格式化创建时间
|
|
|
|
|
|
const formatCreatedTime = (createdAt) => {
|
|
|
|
|
|
if (!createdAt) return ''
|
2025-10-13 15:08:54 +08:00
|
|
|
|
const parsed = parseToShanghai(createdAt)
|
|
|
|
|
|
if (!parsed) return ''
|
2025-07-26 03:36:54 +08:00
|
|
|
|
|
2025-10-13 15:08:54 +08:00
|
|
|
|
const today = dayjs().startOf('day')
|
|
|
|
|
|
const createdDay = parsed.startOf('day')
|
|
|
|
|
|
const diffInDays = today.diff(createdDay, 'day')
|
2025-07-26 03:36:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (diffInDays === 0) {
|
|
|
|
|
|
return '今天创建'
|
2025-10-13 15:08:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (diffInDays === 1) {
|
2025-07-26 03:36:54 +08:00
|
|
|
|
return '昨天创建'
|
2025-10-13 15:08:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (diffInDays < 7) {
|
2025-07-26 03:36:54 +08:00
|
|
|
|
return `${diffInDays} 天前创建`
|
2025-10-13 15:08:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (diffInDays < 30) {
|
2025-07-26 03:36:54 +08:00
|
|
|
|
const weeks = Math.floor(diffInDays / 7)
|
|
|
|
|
|
return `${weeks} 周前创建`
|
2025-10-13 15:08:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (diffInDays < 365) {
|
2025-07-26 03:36:54 +08:00
|
|
|
|
const months = Math.floor(diffInDays / 30)
|
|
|
|
|
|
return `${months} 个月前创建`
|
|
|
|
|
|
}
|
2025-10-13 15:08:54 +08:00
|
|
|
|
const years = Math.floor(diffInDays / 365)
|
|
|
|
|
|
return `${years} 年前创建`
|
2025-07-26 03:36:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-21 18:18:47 +08:00
|
|
|
|
// 处理知识库类型改变
|
|
|
|
|
|
const handleKbTypeChange = (type) => {
|
|
|
|
|
|
console.log('知识库类型改变:', type)
|
2025-07-26 03:36:54 +08:00
|
|
|
|
resetNewDatabase()
|
|
|
|
|
|
newDatabase.kb_type = type
|
2025-11-06 19:47:22 +08:00
|
|
|
|
if (!['chroma', 'milvus'].includes(type)) {
|
|
|
|
|
|
newDatabase.reranker.enabled = false
|
|
|
|
|
|
}
|
2025-07-21 18:18:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-16 20:07:12 +08:00
|
|
|
|
// 处理LLM选择
|
2025-10-14 02:27:15 +08:00
|
|
|
|
const handleLLMSelect = (spec) => {
|
|
|
|
|
|
console.log('LLM选择:', spec)
|
|
|
|
|
|
if (typeof spec !== 'string' || !spec) return
|
|
|
|
|
|
|
|
|
|
|
|
const index = spec.indexOf('/')
|
|
|
|
|
|
const provider = index !== -1 ? spec.slice(0, index) : ''
|
|
|
|
|
|
const modelName = index !== -1 ? spec.slice(index + 1) : ''
|
|
|
|
|
|
|
|
|
|
|
|
newDatabase.llm_info.provider = provider
|
|
|
|
|
|
newDatabase.llm_info.model_name = modelName
|
2025-08-16 20:07:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-16 18:14:27 +08:00
|
|
|
|
const createDatabase = () => {
|
2025-06-27 01:55:04 +08:00
|
|
|
|
if (!newDatabase.name?.trim()) {
|
2024-07-16 18:14:27 +08:00
|
|
|
|
message.error('数据库名称不能为空')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-05-02 23:56:59 +08:00
|
|
|
|
|
2025-07-21 18:18:47 +08:00
|
|
|
|
if (!newDatabase.kb_type) {
|
|
|
|
|
|
message.error('请选择知识库类型')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-27 01:55:04 +08:00
|
|
|
|
state.creating = true
|
|
|
|
|
|
|
|
|
|
|
|
const requestData = {
|
|
|
|
|
|
database_name: newDatabase.name.trim(),
|
|
|
|
|
|
description: newDatabase.description?.trim() || '',
|
|
|
|
|
|
embed_model_name: newDatabase.embed_model_name || configStore.config.embed_model,
|
2025-07-21 18:18:47 +08:00
|
|
|
|
kb_type: newDatabase.kb_type,
|
2025-10-26 15:08:43 +08:00
|
|
|
|
additional_params: {
|
|
|
|
|
|
is_private: newDatabase.is_private || false
|
|
|
|
|
|
}
|
2025-07-21 18:18:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加类型特有的配置
|
|
|
|
|
|
if (newDatabase.kb_type === 'chroma' || newDatabase.kb_type === 'milvus') {
|
2025-11-06 19:47:22 +08:00
|
|
|
|
if (newDatabase.storage) {
|
|
|
|
|
|
requestData.additional_params.storage = newDatabase.storage
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (newDatabase.reranker.enabled) {
|
|
|
|
|
|
if (!newDatabase.reranker.model) {
|
|
|
|
|
|
message.error('请选择重排序模型')
|
|
|
|
|
|
state.creating = false
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
requestData.additional_params.reranker_config = {
|
|
|
|
|
|
enabled: true,
|
|
|
|
|
|
model: newDatabase.reranker.model,
|
|
|
|
|
|
recall_top_k: Number(newDatabase.reranker.recall_top_k) || 50,
|
|
|
|
|
|
final_top_k: Number(newDatabase.reranker.final_top_k) || 10,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-27 01:55:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-16 20:07:12 +08:00
|
|
|
|
if (newDatabase.kb_type === 'lightrag') {
|
|
|
|
|
|
requestData.additional_params.language = newDatabase.language || 'English'
|
|
|
|
|
|
// 添加LLM信息到请求数据
|
|
|
|
|
|
if (newDatabase.llm_info.provider && newDatabase.llm_info.model_name) {
|
|
|
|
|
|
requestData.llm_info = {
|
|
|
|
|
|
provider: newDatabase.llm_info.provider,
|
|
|
|
|
|
model_name: newDatabase.llm_info.model_name
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-22 17:29:38 +08:00
|
|
|
|
databaseApi.createDatabase(requestData)
|
2025-06-27 01:55:04 +08:00
|
|
|
|
.then(data => {
|
|
|
|
|
|
console.log('创建成功:', data)
|
|
|
|
|
|
loadDatabases()
|
|
|
|
|
|
resetNewDatabase()
|
|
|
|
|
|
message.success('创建成功')
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(error => {
|
|
|
|
|
|
console.error('创建数据库失败:', error)
|
|
|
|
|
|
message.error(error.message || '创建失败')
|
|
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
|
|
|
|
|
state.creating = false
|
|
|
|
|
|
state.openNewDatabaseModel = false
|
2025-05-04 21:04:01 +08:00
|
|
|
|
})
|
2024-07-16 18:14:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const navigateToDatabase = (databaseId) => {
|
|
|
|
|
|
router.push({ path: `/database/${databaseId}` });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-06 19:47:22 +08:00
|
|
|
|
watch(() => newDatabase.reranker.enabled, (enabled) => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
enabled &&
|
|
|
|
|
|
!newDatabase.reranker.model &&
|
|
|
|
|
|
rerankerOptions.value.length > 0
|
|
|
|
|
|
) {
|
|
|
|
|
|
newDatabase.reranker.model = rerankerOptions.value[0].value
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
watch(rerankerOptions, (options) => {
|
|
|
|
|
|
if (!newDatabase.reranker.enabled || options.length === 0) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const exists = options.some(option => option.value === newDatabase.reranker.model)
|
|
|
|
|
|
if (!exists) {
|
|
|
|
|
|
newDatabase.reranker.model = options[0].value
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
watch(isVectorKb, (isVector) => {
|
|
|
|
|
|
if (!isVector) {
|
|
|
|
|
|
newDatabase.reranker.enabled = false
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => newDatabase.reranker.final_top_k,
|
|
|
|
|
|
(value) => {
|
|
|
|
|
|
if (!newDatabase.reranker.enabled) return
|
|
|
|
|
|
if (value > newDatabase.reranker.recall_top_k) {
|
|
|
|
|
|
newDatabase.reranker.recall_top_k = value
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2024-07-17 18:52:20 +08:00
|
|
|
|
watch(() => route.path, (newPath, oldPath) => {
|
|
|
|
|
|
if (newPath === '/database') {
|
|
|
|
|
|
loadDatabases();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2024-07-16 18:14:27 +08:00
|
|
|
|
onMounted(() => {
|
2025-07-21 18:18:47 +08:00
|
|
|
|
loadSupportedKbTypes()
|
2024-07-17 18:52:20 +08:00
|
|
|
|
loadDatabases()
|
2025-11-06 19:47:22 +08:00
|
|
|
|
// 重排序模型信息现在直接从 configStore 获取,无需单独加载
|
2024-07-16 18:14:27 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
2025-07-21 18:18:47 +08:00
|
|
|
|
.new-database-modal {
|
|
|
|
|
|
.kb-type-guide {
|
|
|
|
|
|
margin: 12px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-26 15:08:43 +08:00
|
|
|
|
.privacy-config {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 19:47:22 +08:00
|
|
|
|
.reranker-config {
|
|
|
|
|
|
border: 1px solid var(--gray-200);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
margin-top: 16px;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
background: var(--gray-25);
|
2025-11-06 19:47:22 +08:00
|
|
|
|
|
|
|
|
|
|
.reranker-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
|
2025-11-25 22:34:44 +08:00
|
|
|
|
&:last-child {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 19:47:22 +08:00
|
|
|
|
.reranker-title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: var(--gray-800);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hint-icon {
|
|
|
|
|
|
color: var(--gray-500);
|
|
|
|
|
|
cursor: help;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reranker-form {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
.form-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-field {
|
|
|
|
|
|
label {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.field-hint {
|
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--gray-500);
|
2025-11-25 22:34:44 +08:00
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
|
}
|
2025-11-06 19:47:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-26 03:36:54 +08:00
|
|
|
|
.kb-type-cards {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin: 16px 0;
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.kb-type-card {
|
2025-11-23 01:39:44 +08:00
|
|
|
|
border: 2px solid var(--gray-150);
|
2025-07-26 03:36:54 +08:00
|
|
|
|
border-radius: 12px;
|
2025-11-27 13:49:09 +08:00
|
|
|
|
padding: 16px;
|
2025-07-26 03:36:54 +08:00
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.3s ease;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
background: var(--gray-0);
|
2025-07-26 03:36:54 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: var(--main-color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-03 15:26:58 +08:00
|
|
|
|
&.active {
|
|
|
|
|
|
border-color: var(--main-color);
|
|
|
|
|
|
background: var(--main-10);
|
|
|
|
|
|
.type-icon { color: var(--main-color); }
|
2025-07-26 03:36:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.type-icon {
|
|
|
|
|
|
width: 24px;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
color: var(--main-color);
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-title {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--gray-800);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-description {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--gray-600);
|
|
|
|
|
|
line-height: 1.5;
|
2025-11-27 13:49:09 +08:00
|
|
|
|
margin-bottom: 0;
|
2025-10-26 15:08:43 +08:00
|
|
|
|
// min-height: 40px;
|
2025-07-26 03:36:54 +08:00
|
|
|
|
}
|
2025-12-03 15:26:58 +08:00
|
|
|
|
|
|
|
|
|
|
.deprecated-badge {
|
|
|
|
|
|
background: var(--color-error-100);
|
|
|
|
|
|
color: var(--color-error-600);
|
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
padding: 2px 6px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
|
cursor: help;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background: var(--color-error-200);
|
|
|
|
|
|
color: var(--color-error-700);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-26 03:36:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-21 18:18:47 +08:00
|
|
|
|
.chunk-config {
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
padding: 12px 16px;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
background-color: var(--gray-25);
|
2025-07-21 18:18:47 +08:00
|
|
|
|
border-radius: 6px;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
border: 1px solid var(--gray-150);
|
2025-07-21 18:18:47 +08:00
|
|
|
|
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
color: var(--gray-800);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chunk-params {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.param-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
label {
|
|
|
|
|
|
min-width: 80px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.param-hint {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--gray-500);
|
|
|
|
|
|
margin-left: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.database-container {
|
|
|
|
|
|
.databases {
|
|
|
|
|
|
.database {
|
|
|
|
|
|
.top {
|
|
|
|
|
|
.info {
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
|
|
|
|
|
|
.kb-type-tag {
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-07-16 18:14:27 +08:00
|
|
|
|
.database-actions, .document-actions {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.databases {
|
2024-09-14 02:46:44 +08:00
|
|
|
|
padding: 20px;
|
2024-09-28 00:41:02 +08:00
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
2024-07-16 18:14:27 +08:00
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.database, .graphbase {
|
2025-11-23 01:39:44 +08:00
|
|
|
|
background: linear-gradient(145deg, var(--gray-0) 0%, var(--gray-10) 100%);
|
|
|
|
|
|
box-shadow: 0px 1px 2px 0px var(--shadow-2);
|
|
|
|
|
|
border: 1px solid var(--gray-100);
|
2025-11-18 21:21:20 +08:00
|
|
|
|
transition: none;
|
|
|
|
|
|
position: relative;
|
2024-07-25 20:30:28 +08:00
|
|
|
|
}
|
2024-07-16 18:14:27 +08:00
|
|
|
|
|
|
|
|
|
|
.dbcard, .database {
|
2024-09-28 00:41:02 +08:00
|
|
|
|
width: 100%;
|
2025-12-07 23:38:20 +08:00
|
|
|
|
padding: 16px;
|
2025-11-18 21:21:20 +08:00
|
|
|
|
border-radius: 16px;
|
2025-12-07 23:38:20 +08:00
|
|
|
|
height: 156px;
|
2024-07-16 18:14:27 +08:00
|
|
|
|
cursor: pointer;
|
2025-07-26 03:36:54 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2025-10-26 15:08:43 +08:00
|
|
|
|
position: relative; // 为绝对定位的锁定图标提供参考
|
2025-11-18 21:21:20 +08:00
|
|
|
|
overflow: hidden;
|
2025-10-26 15:08:43 +08:00
|
|
|
|
|
|
|
|
|
|
.private-lock-icon {
|
|
|
|
|
|
position: absolute;
|
2025-11-18 21:21:20 +08:00
|
|
|
|
top: 20px;
|
|
|
|
|
|
right: 20px;
|
|
|
|
|
|
color: var(--gray-600);
|
2025-11-23 01:39:44 +08:00
|
|
|
|
background: linear-gradient(135deg, var(--gray-0) 0%, var(--gray-100) 100%);
|
2025-10-26 15:08:43 +08:00
|
|
|
|
font-size: 12px;
|
2025-11-18 21:21:20 +08:00
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 6px;
|
|
|
|
|
|
z-index: 2;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
box-shadow: 0px 2px 4px var(--shadow-2);
|
|
|
|
|
|
border: 1px solid var(--gray-100);
|
2025-10-26 15:08:43 +08:00
|
|
|
|
}
|
2024-07-16 18:14:27 +08:00
|
|
|
|
|
2025-11-23 01:39:44 +08:00
|
|
|
|
|
2024-07-16 18:14:27 +08:00
|
|
|
|
.top {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-11-18 21:21:20 +08:00
|
|
|
|
height: 54px;
|
|
|
|
|
|
margin-bottom: 14px;
|
2024-07-16 18:14:27 +08:00
|
|
|
|
|
|
|
|
|
|
.icon {
|
2025-11-18 21:21:20 +08:00
|
|
|
|
width: 54px;
|
|
|
|
|
|
height: 54px;
|
|
|
|
|
|
font-size: 26px;
|
|
|
|
|
|
margin-right: 14px;
|
2024-07-16 18:14:27 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
2025-11-18 21:21:20 +08:00
|
|
|
|
background: var(--main-30);
|
|
|
|
|
|
border-radius: 12px;
|
2025-11-25 22:34:44 +08:00
|
|
|
|
border: 1px solid var(--gray-150);
|
2024-08-25 20:29:24 +08:00
|
|
|
|
color: var(--main-color);
|
2025-11-18 21:21:20 +08:00
|
|
|
|
position: relative;
|
2024-07-16 18:14:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.info {
|
|
|
|
|
|
h3, p {
|
|
|
|
|
|
margin: 0;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: var(--gray-10000);
|
2024-07-16 18:14:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-25 20:29:24 +08:00
|
|
|
|
h3 {
|
2025-11-18 21:21:20 +08:00
|
|
|
|
font-size: 17px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
letter-spacing: -0.02em;
|
|
|
|
|
|
line-height: 1.4;
|
2024-08-25 20:29:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-16 18:14:27 +08:00
|
|
|
|
p {
|
2025-11-18 21:21:20 +08:00
|
|
|
|
color: var(--gray-700);
|
|
|
|
|
|
font-size: 13px;
|
2025-07-26 03:36:54 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
2025-11-18 21:21:20 +08:00
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
font-weight: 400;
|
2025-07-26 03:36:54 +08:00
|
|
|
|
|
|
|
|
|
|
.created-time-inline {
|
|
|
|
|
|
color: var(--gray-500);
|
2025-11-18 21:21:20 +08:00
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
background: var(--gray-50);
|
|
|
|
|
|
padding: 2px 6px;
|
|
|
|
|
|
border-radius: 4px;
|
2025-07-26 03:36:54 +08:00
|
|
|
|
}
|
2024-07-16 18:14:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.description {
|
2025-11-18 21:21:20 +08:00
|
|
|
|
color: var(--gray-600);
|
2024-07-16 18:14:27 +08:00
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
display: -webkit-box;
|
2025-12-01 16:26:52 +08:00
|
|
|
|
line-clamp: 1;
|
|
|
|
|
|
-webkit-line-clamp: 1;
|
2024-07-16 18:14:27 +08:00
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
|
text-overflow: ellipsis;
|
2025-11-18 21:21:20 +08:00
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
flex: 1;
|
2024-07-16 18:14:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-31 20:22:05 +08:00
|
|
|
|
.database-empty {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
flex-direction: column;
|
2024-09-25 13:46:51 +08:00
|
|
|
|
color: var(--gray-900);
|
2024-07-31 20:22:05 +08:00
|
|
|
|
}
|
2024-09-14 02:46:44 +08:00
|
|
|
|
|
2025-12-03 15:26:58 +08:00
|
|
|
|
.empty-state {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding: 100px 20px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
|
|
|
|
.empty-title {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--gray-900);
|
|
|
|
|
|
margin: 0 0 12px 0;
|
|
|
|
|
|
letter-spacing: -0.02em;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-description {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: var(--gray-600);
|
|
|
|
|
|
margin: 0 0 32px 0;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
max-width: 320px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ant-btn {
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
padding: 0 24px;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-14 02:46:44 +08:00
|
|
|
|
.database-container {
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
2025-06-27 12:22:35 +08:00
|
|
|
|
|
2025-08-05 18:00:45 +08:00
|
|
|
|
.loading-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
height: 300px;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-27 12:22:35 +08:00
|
|
|
|
.new-database-modal {
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-07-16 18:14:27 +08:00
|
|
|
|
</style>
|