2024-07-17 18:52:20 +08:00
|
|
|
|
<template>
|
2024-09-06 12:54:17 +08:00
|
|
|
|
<div class="database-empty" v-if="!state.showPage">
|
|
|
|
|
|
<a-empty>
|
|
|
|
|
|
<template #description>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<span> 点击右上角用户头像中的"系统设置"来启用知识图谱。 </span>
|
2024-09-06 12:54:17 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</a-empty>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="graph-container layout-container" v-else>
|
2026-04-06 21:04:19 +08:00
|
|
|
|
<ViewSwitchHeader
|
|
|
|
|
|
title="知识库"
|
|
|
|
|
|
:active-key="knowledgeActiveView"
|
|
|
|
|
|
:items="knowledgeViewItems"
|
|
|
|
|
|
aria-label="知识库视图切换"
|
|
|
|
|
|
>
|
2024-09-14 02:46:44 +08:00
|
|
|
|
<template #actions>
|
2025-12-15 23:25:56 +08:00
|
|
|
|
<div class="db-selector">
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<div class="status-wrapper">
|
|
|
|
|
|
<div class="status-indicator" :class="graphStatusClass"></div>
|
|
|
|
|
|
<span class="status-text">{{ graphStatusText }}</span>
|
|
|
|
|
|
</div>
|
2025-12-15 23:25:56 +08:00
|
|
|
|
<span class="label">知识库: </span>
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="state.selectedDbId"
|
|
|
|
|
|
style="width: 200px"
|
|
|
|
|
|
:options="state.dbOptions"
|
|
|
|
|
|
@change="handleDbChange"
|
|
|
|
|
|
:loading="state.loadingDatabases"
|
2025-12-20 14:51:02 +08:00
|
|
|
|
mode="combobox"
|
|
|
|
|
|
placeholder="选择或输入KB ID"
|
2025-12-15 23:25:56 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- <a-button type="default" @click="openLink('http://localhost:7474/')" :icon="h(GlobalOutlined)">
|
2025-07-23 19:21:45 +08:00
|
|
|
|
Neo4j 浏览器
|
2025-12-15 23:25:56 +08:00
|
|
|
|
</a-button> -->
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<a-button v-if="isNeo4j" type="primary" @click="state.showModal = true"
|
|
|
|
|
|
><UploadOutlined /> 上传文件</a-button
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-button v-else type="primary" @click="state.showUploadTipModal = true"
|
|
|
|
|
|
><UploadOutlined /> 上传文件</a-button
|
|
|
|
|
|
>
|
2025-12-16 00:27:40 +08:00
|
|
|
|
<a-button
|
2026-01-15 06:01:34 +08:00
|
|
|
|
v-if="unindexedCount > 0"
|
2025-12-16 00:27:40 +08:00
|
|
|
|
type="primary"
|
2026-01-15 06:01:34 +08:00
|
|
|
|
@click="indexNodes"
|
|
|
|
|
|
:loading="state.indexing"
|
|
|
|
|
|
>
|
|
|
|
|
|
<SyncOutlined v-if="!state.indexing" /> 为{{ unindexedCount }}个节点添加索引
|
2025-03-08 22:49:22 +08:00
|
|
|
|
</a-button>
|
2024-09-14 02:46:44 +08:00
|
|
|
|
</template>
|
2026-04-06 21:04:19 +08:00
|
|
|
|
</ViewSwitchHeader>
|
2024-09-14 02:46:44 +08:00
|
|
|
|
|
2025-09-03 03:00:25 +08:00
|
|
|
|
<div class="container-outter">
|
2025-09-03 16:06:56 +08:00
|
|
|
|
<GraphCanvas
|
|
|
|
|
|
ref="graphRef"
|
2025-12-15 23:25:56 +08:00
|
|
|
|
:graph-data="graph.graphData"
|
2025-12-20 15:44:37 +08:00
|
|
|
|
:graph-info="formattedGraphInfo"
|
2025-09-11 02:44:35 +08:00
|
|
|
|
:highlight-keywords="[state.searchInput]"
|
2025-12-15 23:25:56 +08:00
|
|
|
|
@node-click="graph.handleNodeClick"
|
|
|
|
|
|
@edge-click="graph.handleEdgeClick"
|
|
|
|
|
|
@canvas-click="graph.handleCanvasClick"
|
2025-09-03 16:06:56 +08:00
|
|
|
|
>
|
|
|
|
|
|
<template #top>
|
|
|
|
|
|
<div class="actions">
|
|
|
|
|
|
<div class="actions-left">
|
|
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="state.searchInput"
|
2025-12-20 14:51:02 +08:00
|
|
|
|
placeholder="输入要查询的实体 (*为全部)"
|
2025-09-03 16:06:56 +08:00
|
|
|
|
style="width: 300px"
|
|
|
|
|
|
@keydown.enter="onSearch"
|
|
|
|
|
|
allow-clear
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #suffix>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<component
|
|
|
|
|
|
:is="state.searchLoading ? LoadingOutlined : SearchOutlined"
|
|
|
|
|
|
@click="onSearch"
|
|
|
|
|
|
/>
|
2025-09-03 16:06:56 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</a-input>
|
|
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="sampleNodeCount"
|
2025-12-15 23:25:56 +08:00
|
|
|
|
placeholder="查询数量"
|
2025-09-03 16:06:56 +08:00
|
|
|
|
style="width: 100px"
|
|
|
|
|
|
@keydown.enter="loadSampleNodes"
|
2025-12-15 23:25:56 +08:00
|
|
|
|
:loading="graph.fetching"
|
2025-09-03 16:06:56 +08:00
|
|
|
|
>
|
|
|
|
|
|
<template #suffix>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<component
|
|
|
|
|
|
:is="graph.fetching ? LoadingOutlined : ReloadOutlined"
|
|
|
|
|
|
@click="loadSampleNodes"
|
|
|
|
|
|
/>
|
2025-09-03 16:06:56 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</a-input>
|
|
|
|
|
|
</div>
|
2025-12-15 23:25:56 +08:00
|
|
|
|
<div class="actions-right">
|
2025-12-16 00:27:40 +08:00
|
|
|
|
<a-button type="default" @click="exportGraphData" :icon="h(ExportOutlined)">
|
|
|
|
|
|
导出数据
|
|
|
|
|
|
</a-button>
|
2025-12-15 23:25:56 +08:00
|
|
|
|
</div>
|
2025-09-03 03:00:25 +08:00
|
|
|
|
</div>
|
2025-09-03 16:06:56 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
<template #content>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<a-empty v-show="graph.graphData.nodes.length === 0" style="padding: 4rem 0" />
|
2025-09-03 16:06:56 +08:00
|
|
|
|
</template>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
</GraphCanvas>
|
|
|
|
|
|
<!-- 详情浮动卡片 -->
|
|
|
|
|
|
<GraphDetailPanel
|
|
|
|
|
|
:visible="graph.showDetailDrawer"
|
|
|
|
|
|
:item="graph.selectedItem"
|
|
|
|
|
|
:type="graph.selectedItemType"
|
|
|
|
|
|
:nodes="graph.graphData.nodes"
|
|
|
|
|
|
@close="graph.handleCanvasClick"
|
|
|
|
|
|
style="width: 380px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2024-09-14 02:46:44 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<a-modal
|
|
|
|
|
|
:open="state.showModal"
|
|
|
|
|
|
title="上传文件"
|
|
|
|
|
|
@ok="addDocumentByFile"
|
|
|
|
|
|
@cancel="handleModalCancel"
|
|
|
|
|
|
ok-text="添加到图数据库"
|
|
|
|
|
|
cancel-text="取消"
|
|
|
|
|
|
:confirm-loading="state.processing"
|
|
|
|
|
|
:ok-button-props="{ disabled: !hasValidFile }"
|
|
|
|
|
|
>
|
2024-09-14 02:46:44 +08:00
|
|
|
|
<div class="upload">
|
2025-08-25 13:57:47 +08:00
|
|
|
|
<div class="note">
|
|
|
|
|
|
<p>上传的文件内容参考 test/data/A_Dream_of_Red_Mansions_tiny.jsonl 中的格式:</p>
|
|
|
|
|
|
</div>
|
2026-01-01 23:39:17 +08:00
|
|
|
|
<div class="upload-config">
|
|
|
|
|
|
<div class="config-row">
|
|
|
|
|
|
<label class="config-label">嵌入模型</label>
|
|
|
|
|
|
<div class="config-field">
|
|
|
|
|
|
<EmbeddingModelSelector
|
|
|
|
|
|
v-model:value="state.embedModelName"
|
|
|
|
|
|
:disabled="!embedModelConfigurable"
|
|
|
|
|
|
:style="{ width: '100%' }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="!embedModelConfigurable" class="config-hint-row">
|
2026-01-01 22:19:48 +08:00
|
|
|
|
* 图数据库已有数据或已设定模型,不可更改
|
|
|
|
|
|
</div>
|
2026-01-01 23:39:17 +08:00
|
|
|
|
<div class="config-row">
|
|
|
|
|
|
<label class="config-label">批处理大小</label>
|
|
|
|
|
|
<div class="config-field">
|
|
|
|
|
|
<a-input-number
|
|
|
|
|
|
v-model:value="state.batchSize"
|
|
|
|
|
|
:min="1"
|
|
|
|
|
|
:max="1000"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<div class="config-hint-row">默认值: 40,范围: 1-1000</div>
|
2026-01-01 22:19:48 +08:00
|
|
|
|
</div>
|
2024-09-14 02:46:44 +08:00
|
|
|
|
<a-upload-dragger
|
|
|
|
|
|
class="upload-dragger"
|
|
|
|
|
|
v-model:fileList="fileList"
|
|
|
|
|
|
name="file"
|
|
|
|
|
|
:fileList="fileList"
|
|
|
|
|
|
:max-count="1"
|
2025-10-17 12:15:40 +08:00
|
|
|
|
accept=".jsonl"
|
|
|
|
|
|
action="/api/knowledge/files/upload?allow_jsonl=true"
|
2025-05-09 23:46:15 +08:00
|
|
|
|
:headers="getAuthHeaders()"
|
2024-09-14 02:46:44 +08:00
|
|
|
|
@change="handleFileUpload"
|
|
|
|
|
|
@drop="handleDrop"
|
|
|
|
|
|
>
|
|
|
|
|
|
<p class="ant-upload-text">点击或者把文件拖拽到这里上传</p>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<p class="ant-upload-hint">目前仅支持上传 jsonl 文件。</p>
|
2024-09-14 02:46:44 +08:00
|
|
|
|
</a-upload-dragger>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-modal>
|
2025-08-24 16:51:23 +08:00
|
|
|
|
|
2025-12-16 00:27:40 +08:00
|
|
|
|
<!-- 上传提示弹窗 -->
|
|
|
|
|
|
<a-modal
|
|
|
|
|
|
:open="state.showUploadTipModal"
|
|
|
|
|
|
title="文件上传说明"
|
2026-01-15 06:01:34 +08:00
|
|
|
|
@cancel="() => (state.showUploadTipModal = false)"
|
2025-12-16 00:27:40 +08:00
|
|
|
|
:footer="null"
|
|
|
|
|
|
width="500px"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="upload-tip-content">
|
|
|
|
|
|
<a-alert
|
|
|
|
|
|
:message="getUploadTipMessage()"
|
|
|
|
|
|
type="info"
|
|
|
|
|
|
show-icon
|
2026-01-15 06:01:34 +08:00
|
|
|
|
style="margin-bottom: 16px"
|
2025-12-16 00:27:40 +08:00
|
|
|
|
/>
|
|
|
|
|
|
<div v-if="!isNeo4j" class="upload-tip-actions">
|
|
|
|
|
|
<p>如需上传文档到当前选中的知识库,请前往对应的知识库详情页面进行操作:</p>
|
|
|
|
|
|
<div class="action-buttons">
|
|
|
|
|
|
<a-button type="primary" @click="goToDatabasePage">
|
2026-01-15 06:01:34 +08:00
|
|
|
|
<DatabaseOutlined /> 前往知识库详情页
|
2025-12-16 00:27:40 +08:00
|
|
|
|
</a-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-modal>
|
2024-09-01 18:10:50 +08:00
|
|
|
|
</div>
|
2024-07-17 18:52:20 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-01-15 06:01:34 +08:00
|
|
|
|
import { computed, onMounted, reactive, ref, h } from 'vue'
|
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
|
import { message } from 'ant-design-vue'
|
|
|
|
|
|
import { useConfigStore } from '@/stores/config'
|
|
|
|
|
|
import {
|
|
|
|
|
|
UploadOutlined,
|
|
|
|
|
|
SyncOutlined,
|
|
|
|
|
|
SearchOutlined,
|
|
|
|
|
|
ReloadOutlined,
|
|
|
|
|
|
LoadingOutlined,
|
|
|
|
|
|
DatabaseOutlined,
|
|
|
|
|
|
ExportOutlined
|
|
|
|
|
|
} from '@ant-design/icons-vue'
|
2026-04-06 21:04:19 +08:00
|
|
|
|
import ViewSwitchHeader from '@/components/ViewSwitchHeader.vue'
|
2026-01-15 06:01:34 +08:00
|
|
|
|
import { neo4jApi, unifiedApi } from '@/apis/graph_api'
|
|
|
|
|
|
import { useUserStore } from '@/stores/user'
|
|
|
|
|
|
import GraphCanvas from '@/components/GraphCanvas.vue'
|
|
|
|
|
|
import GraphDetailPanel from '@/components/GraphDetailPanel.vue'
|
|
|
|
|
|
import EmbeddingModelSelector from '@/components/EmbeddingModelSelector.vue'
|
|
|
|
|
|
import { useGraph } from '@/composables/useGraph'
|
|
|
|
|
|
|
|
|
|
|
|
const configStore = useConfigStore()
|
|
|
|
|
|
const cur_embed_model = computed(() => configStore.config?.embed_model)
|
2026-04-06 21:04:19 +08:00
|
|
|
|
const knowledgeActiveView = 'graph'
|
|
|
|
|
|
const knowledgeViewItems = [
|
|
|
|
|
|
{ key: 'documents', label: '文档知识库', path: '/database' },
|
|
|
|
|
|
{ key: 'graph', label: '知识图谱', path: '/graph' }
|
|
|
|
|
|
]
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const modelMatched = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
!graphInfo?.value?.embed_model_name ||
|
|
|
|
|
|
graphInfo.value.embed_model_name === cur_embed_model.value
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const router = useRouter()
|
2025-09-03 16:06:56 +08:00
|
|
|
|
const graphRef = ref(null)
|
2024-09-06 21:30:49 +08:00
|
|
|
|
const graphInfo = ref(null)
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const fileList = ref([])
|
|
|
|
|
|
const sampleNodeCount = ref(100)
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const graph = reactive(useGraph(graphRef))
|
2024-07-24 18:32:14 +08:00
|
|
|
|
|
|
|
|
|
|
const state = reactive({
|
2024-09-06 21:30:49 +08:00
|
|
|
|
loadingGraphInfo: false,
|
2025-12-15 23:25:56 +08:00
|
|
|
|
loadingDatabases: false,
|
2024-07-24 18:32:14 +08:00
|
|
|
|
searchInput: '',
|
|
|
|
|
|
searchLoading: false,
|
|
|
|
|
|
showModal: false,
|
2025-08-24 16:51:23 +08:00
|
|
|
|
showInfoModal: false,
|
2025-12-16 00:27:40 +08:00
|
|
|
|
showUploadTipModal: false,
|
2025-09-03 16:06:56 +08:00
|
|
|
|
processing: false,
|
2025-03-08 22:49:22 +08:00
|
|
|
|
indexing: false,
|
2025-06-27 01:47:52 +08:00
|
|
|
|
showPage: true,
|
2025-12-15 23:25:56 +08:00
|
|
|
|
selectedDbId: 'neo4j',
|
|
|
|
|
|
dbOptions: [],
|
|
|
|
|
|
lightragStats: null,
|
2026-01-01 22:19:48 +08:00
|
|
|
|
embedModelName: '',
|
2026-01-15 06:01:34 +08:00
|
|
|
|
batchSize: 40
|
2024-07-24 18:32:14 +08:00
|
|
|
|
})
|
2024-07-17 18:52:20 +08:00
|
|
|
|
|
2025-12-20 14:51:02 +08:00
|
|
|
|
const isNeo4j = computed(() => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return state.selectedDbId === 'neo4j'
|
|
|
|
|
|
})
|
2025-12-20 14:51:02 +08:00
|
|
|
|
|
2026-01-01 22:19:48 +08:00
|
|
|
|
const embedModelConfigurable = computed(() => {
|
|
|
|
|
|
return graphInfo.value?.embed_model_configurable ?? true
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-20 14:51:02 +08:00
|
|
|
|
// 检查是否有有效的已上传文件
|
|
|
|
|
|
const hasValidFile = computed(() => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return fileList.value.some((file) => file.status === 'done' && file.response?.file_path)
|
|
|
|
|
|
})
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
2025-03-08 22:49:22 +08:00
|
|
|
|
// 计算未索引节点数量
|
|
|
|
|
|
const unindexedCount = computed(() => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return graphInfo.value?.unindexed_node_count || 0
|
|
|
|
|
|
})
|
2024-09-03 16:37:59 +08:00
|
|
|
|
|
2025-12-20 15:44:37 +08:00
|
|
|
|
const formattedGraphInfo = computed(() => {
|
|
|
|
|
|
if (isNeo4j.value) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
node_count: graphInfo.value?.entity_count || 0,
|
|
|
|
|
|
edge_count: graphInfo.value?.relationship_count || 0
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return {
|
|
|
|
|
|
node_count: state.lightragStats?.total_nodes || 0,
|
|
|
|
|
|
edge_count: state.lightragStats?.total_edges || 0
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-15 23:25:56 +08:00
|
|
|
|
const loadDatabases = async () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.loadingDatabases = true
|
2025-12-15 23:25:56 +08:00
|
|
|
|
try {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const res = await unifiedApi.getGraphs()
|
2025-12-15 23:25:56 +08:00
|
|
|
|
if (res.success && res.data) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.dbOptions = res.data.map((db) => ({
|
2025-12-15 23:25:56 +08:00
|
|
|
|
label: `${db.name} (${db.type})`,
|
|
|
|
|
|
value: db.id,
|
|
|
|
|
|
type: db.type
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}))
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
|
|
|
|
|
// If no selection or invalid selection, select first
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (!state.selectedDbId || !state.dbOptions.find((o) => o.value === state.selectedDbId)) {
|
2025-12-15 23:25:56 +08:00
|
|
|
|
if (state.dbOptions.length > 0) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.selectedDbId = state.dbOptions[0].value
|
2025-12-15 23:25:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error('Failed to load databases:', error)
|
2025-12-15 23:25:56 +08:00
|
|
|
|
} finally {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.loadingDatabases = false
|
2025-12-15 23:25:56 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
|
|
|
|
|
const handleDbChange = () => {
|
|
|
|
|
|
// Clear current data
|
2026-01-15 06:01:34 +08:00
|
|
|
|
graph.clearGraph()
|
|
|
|
|
|
state.searchInput = ''
|
|
|
|
|
|
state.lightragStats = null
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
|
|
|
|
|
if (isNeo4j.value) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
loadGraphInfo()
|
2025-12-15 23:25:56 +08:00
|
|
|
|
} else {
|
2025-12-20 14:51:02 +08:00
|
|
|
|
// Also load stats for LightRAG or KB
|
2026-01-15 06:01:34 +08:00
|
|
|
|
loadLightRAGStats()
|
2025-12-15 23:25:56 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
loadSampleNodes()
|
|
|
|
|
|
}
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
|
|
|
|
|
const loadLightRAGStats = () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
unifiedApi
|
|
|
|
|
|
.getStats(state.selectedDbId)
|
|
|
|
|
|
.then((res) => {
|
|
|
|
|
|
if (res.success) {
|
|
|
|
|
|
state.lightragStats = res.data
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((e) => console.error(e))
|
|
|
|
|
|
}
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
2024-09-06 21:30:49 +08:00
|
|
|
|
const loadGraphInfo = () => {
|
|
|
|
|
|
state.loadingGraphInfo = true
|
2026-01-15 06:01:34 +08:00
|
|
|
|
neo4jApi
|
|
|
|
|
|
.getInfo()
|
|
|
|
|
|
.then((data) => {
|
2024-09-06 12:54:17 +08:00
|
|
|
|
console.log(data)
|
2025-07-22 17:29:38 +08:00
|
|
|
|
graphInfo.value = data.data
|
2026-01-01 22:19:48 +08:00
|
|
|
|
if (graphInfo.value?.embed_model_name) {
|
|
|
|
|
|
state.embedModelName = graphInfo.value.embed_model_name
|
|
|
|
|
|
} else {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
// Default if not set (though backend usually sends default)
|
|
|
|
|
|
state.embedModelName = cur_embed_model.value
|
2026-01-01 22:19:48 +08:00
|
|
|
|
}
|
2024-09-06 21:30:49 +08:00
|
|
|
|
state.loadingGraphInfo = false
|
2024-09-06 12:54:17 +08:00
|
|
|
|
})
|
2026-01-15 06:01:34 +08:00
|
|
|
|
.catch((error) => {
|
2024-09-06 12:54:17 +08:00
|
|
|
|
console.error(error)
|
2025-05-02 23:56:59 +08:00
|
|
|
|
message.error(error.message || '加载图数据库信息失败')
|
2024-09-06 21:30:49 +08:00
|
|
|
|
state.loadingGraphInfo = false
|
2024-09-06 12:54:17 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
2024-07-17 18:52:20 +08:00
|
|
|
|
|
2024-07-24 18:32:14 +08:00
|
|
|
|
const addDocumentByFile = () => {
|
2025-12-20 14:51:02 +08:00
|
|
|
|
// 使用计算属性验证文件
|
|
|
|
|
|
if (!hasValidFile.value) {
|
|
|
|
|
|
message.error('请先等待文件上传完成')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-01 22:19:48 +08:00
|
|
|
|
if (!state.embedModelName) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error('请选择嵌入模型')
|
|
|
|
|
|
return
|
2026-01-01 22:19:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-24 16:51:23 +08:00
|
|
|
|
state.processing = true
|
2025-12-20 14:51:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取已上传的文件路径
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const uploadedFile = fileList.value.find(
|
|
|
|
|
|
(file) => file.status === 'done' && file.response?.file_path
|
|
|
|
|
|
)
|
|
|
|
|
|
const filePath = uploadedFile?.response?.file_path
|
2025-12-20 14:51:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 再次验证文件路径
|
|
|
|
|
|
if (!filePath) {
|
|
|
|
|
|
message.error('文件路径获取失败,请重新上传文件')
|
|
|
|
|
|
state.processing = false
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
neo4jApi
|
|
|
|
|
|
.addEntities(filePath, 'neo4j', state.embedModelName, state.batchSize)
|
2025-05-02 23:56:59 +08:00
|
|
|
|
.then((data) => {
|
|
|
|
|
|
if (data.status === 'success') {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.success(data.message)
|
|
|
|
|
|
state.showModal = false
|
2025-12-20 14:51:02 +08:00
|
|
|
|
// 清空文件列表
|
2026-01-15 06:01:34 +08:00
|
|
|
|
fileList.value = []
|
2025-12-20 14:51:02 +08:00
|
|
|
|
// 刷新图谱数据
|
|
|
|
|
|
setTimeout(() => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
loadGraphInfo()
|
|
|
|
|
|
loadSampleNodes()
|
|
|
|
|
|
}, 500)
|
2025-05-02 23:56:59 +08:00
|
|
|
|
} else {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
throw new Error(data.message)
|
2025-05-02 23:56:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
|
console.error(error)
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error(error.message || '添加文件失败')
|
2025-05-02 23:56:59 +08:00
|
|
|
|
})
|
2026-01-15 06:01:34 +08:00
|
|
|
|
.finally(() => (state.processing = false))
|
|
|
|
|
|
}
|
2024-07-24 18:32:14 +08:00
|
|
|
|
|
2024-09-06 12:54:17 +08:00
|
|
|
|
const loadSampleNodes = () => {
|
2025-12-15 23:25:56 +08:00
|
|
|
|
graph.fetching = true
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
unifiedApi
|
|
|
|
|
|
.getSubgraph({
|
|
|
|
|
|
db_id: state.selectedDbId,
|
|
|
|
|
|
node_label: '*',
|
|
|
|
|
|
max_nodes: sampleNodeCount.value
|
|
|
|
|
|
})
|
2024-09-06 12:54:17 +08:00
|
|
|
|
.then((data) => {
|
2025-12-15 23:25:56 +08:00
|
|
|
|
// Normalize data structure if needed
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const result = data.data
|
|
|
|
|
|
graph.updateGraphData(result.nodes, result.edges)
|
2025-12-15 23:25:56 +08:00
|
|
|
|
console.log(graph.graphData)
|
2024-09-06 12:54:17 +08:00
|
|
|
|
})
|
|
|
|
|
|
.catch((error) => {
|
2025-05-02 23:56:59 +08:00
|
|
|
|
console.error(error)
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error(error.message || '加载节点失败')
|
2024-09-06 12:54:17 +08:00
|
|
|
|
})
|
2026-01-15 06:01:34 +08:00
|
|
|
|
.finally(() => (graph.fetching = false))
|
2024-09-06 12:54:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-24 18:32:14 +08:00
|
|
|
|
const onSearch = () => {
|
2025-02-28 14:14:00 +08:00
|
|
|
|
if (state.searchLoading) {
|
|
|
|
|
|
message.error('请稍后再试')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 23:25:56 +08:00
|
|
|
|
if (isNeo4j.value && graphInfo?.value?.embed_model_name !== cur_embed_model.value) {
|
2025-09-03 16:06:56 +08:00
|
|
|
|
// 可选:提示模型不一致
|
2025-02-28 14:14:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-24 18:32:14 +08:00
|
|
|
|
state.searchLoading = true
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
unifiedApi
|
|
|
|
|
|
.getSubgraph({
|
|
|
|
|
|
db_id: state.selectedDbId,
|
|
|
|
|
|
node_label: state.searchInput || '*',
|
|
|
|
|
|
max_nodes: sampleNodeCount.value
|
|
|
|
|
|
})
|
2024-07-24 18:32:14 +08:00
|
|
|
|
.then((data) => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const result = data.data
|
2025-12-15 23:25:56 +08:00
|
|
|
|
if (!result || !result.nodes || !result.edges) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
throw new Error('返回数据格式不正确')
|
2024-09-14 02:46:44 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
graph.updateGraphData(result.nodes, result.edges)
|
2025-12-15 23:25:56 +08:00
|
|
|
|
if (graph.graphData.nodes.length === 0) {
|
2024-09-09 17:07:03 +08:00
|
|
|
|
message.info('未找到相关实体')
|
|
|
|
|
|
}
|
2024-07-24 18:32:14 +08:00
|
|
|
|
console.log(data)
|
2025-12-15 23:25:56 +08:00
|
|
|
|
console.log(graph.graphData)
|
2024-07-24 18:32:14 +08:00
|
|
|
|
})
|
|
|
|
|
|
.catch((error) => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.error('查询错误:', error)
|
|
|
|
|
|
message.error(`查询出错:${error.message || '未知错误'}`)
|
2024-07-24 18:32:14 +08:00
|
|
|
|
})
|
2026-01-15 06:01:34 +08:00
|
|
|
|
.finally(() => (state.searchLoading = false))
|
|
|
|
|
|
}
|
2024-07-24 18:32:14 +08:00
|
|
|
|
|
2025-12-15 23:25:56 +08:00
|
|
|
|
onMounted(async () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
await loadDatabases()
|
|
|
|
|
|
loadGraphInfo() // Load default (Neo4j) info
|
|
|
|
|
|
loadSampleNodes()
|
|
|
|
|
|
})
|
2024-07-24 18:32:14 +08:00
|
|
|
|
|
2025-12-20 14:51:02 +08:00
|
|
|
|
const handleFileUpload = ({ file, fileList: newFileList }) => {
|
|
|
|
|
|
// 更新文件列表
|
2026-01-15 06:01:34 +08:00
|
|
|
|
fileList.value = newFileList
|
2025-12-20 14:51:02 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果上传失败,显示错误信息
|
|
|
|
|
|
if (file.status === 'error') {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error(`文件上传失败: ${file.name}`)
|
2025-12-20 14:51:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果上传成功,显示成功信息
|
|
|
|
|
|
if (file.status === 'done' && file.response?.file_path) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.success(`文件上传成功: ${file.name}`)
|
2025-12-20 14:51:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
console.log('File upload status:', file.status, file.name)
|
|
|
|
|
|
console.log('File list:', fileList.value)
|
2024-07-24 18:32:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleDrop = (event) => {
|
|
|
|
|
|
console.log(event)
|
|
|
|
|
|
console.log(fileList.value)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-20 14:51:02 +08:00
|
|
|
|
const handleModalCancel = () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.showModal = false
|
2025-12-20 14:51:02 +08:00
|
|
|
|
// 重置文件列表
|
2026-01-15 06:01:34 +08:00
|
|
|
|
fileList.value = []
|
|
|
|
|
|
}
|
2025-12-20 14:51:02 +08:00
|
|
|
|
|
2024-09-14 02:46:44 +08:00
|
|
|
|
const graphStatusClass = computed(() => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (state.loadingGraphInfo) return 'loading'
|
|
|
|
|
|
return graphInfo.value?.status === 'open' ? 'open' : 'closed'
|
|
|
|
|
|
})
|
2024-09-14 02:46:44 +08:00
|
|
|
|
|
|
|
|
|
|
const graphStatusText = computed(() => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
if (state.loadingGraphInfo) return '加载中'
|
|
|
|
|
|
return graphInfo.value?.status === 'open' ? '已连接' : '已关闭'
|
|
|
|
|
|
})
|
2025-02-28 14:14:00 +08:00
|
|
|
|
|
2025-03-08 22:49:22 +08:00
|
|
|
|
// 为未索引节点添加索引
|
|
|
|
|
|
const indexNodes = () => {
|
|
|
|
|
|
// 判断 embed_model_name 是否相同
|
|
|
|
|
|
if (!modelMatched.value) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
message.error(
|
|
|
|
|
|
`向量模型不匹配,无法添加索引,当前向量模型为 ${cur_embed_model.value},图数据库向量模型为 ${graphInfo.value?.embed_model_name}`
|
|
|
|
|
|
)
|
2025-03-08 22:49:22 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-24 16:51:23 +08:00
|
|
|
|
if (state.processing) {
|
2025-03-08 22:49:22 +08:00
|
|
|
|
message.error('后台正在处理,请稍后再试')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.indexing = true
|
|
|
|
|
|
neo4jApi
|
|
|
|
|
|
.indexEntities('neo4j')
|
|
|
|
|
|
.then((data) => {
|
|
|
|
|
|
message.success(data.message || '索引添加成功')
|
2025-05-02 23:56:59 +08:00
|
|
|
|
// 刷新图谱信息
|
2026-01-15 06:01:34 +08:00
|
|
|
|
loadGraphInfo()
|
2025-05-02 23:56:59 +08:00
|
|
|
|
})
|
2026-01-15 06:01:34 +08:00
|
|
|
|
.catch((error) => {
|
|
|
|
|
|
console.error(error)
|
|
|
|
|
|
message.error(error.message || '添加索引失败')
|
2025-05-02 23:56:59 +08:00
|
|
|
|
})
|
|
|
|
|
|
.finally(() => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.indexing = false
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-03-08 22:49:22 +08:00
|
|
|
|
|
2025-11-18 21:21:20 +08:00
|
|
|
|
const exportGraphData = () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const dataStr = JSON.stringify(
|
|
|
|
|
|
{
|
|
|
|
|
|
nodes: graph.graphData.nodes,
|
|
|
|
|
|
edges: graph.graphData.edges,
|
|
|
|
|
|
graphInfo: isNeo4j.value ? graphInfo.value : state.lightragStats,
|
|
|
|
|
|
source: state.selectedDbId,
|
|
|
|
|
|
exportTime: new Date().toISOString()
|
|
|
|
|
|
},
|
|
|
|
|
|
null,
|
|
|
|
|
|
2
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const dataBlob = new Blob([dataStr], { type: 'application/json' })
|
|
|
|
|
|
const url = URL.createObjectURL(dataBlob)
|
|
|
|
|
|
const link = document.createElement('a')
|
|
|
|
|
|
link.href = url
|
|
|
|
|
|
link.download = `graph-data-${state.selectedDbId}-${new Date().toISOString().slice(0, 10)}.json`
|
|
|
|
|
|
document.body.appendChild(link)
|
|
|
|
|
|
link.click()
|
|
|
|
|
|
document.body.removeChild(link)
|
|
|
|
|
|
URL.revokeObjectURL(url)
|
|
|
|
|
|
|
|
|
|
|
|
message.success('图谱数据已导出')
|
|
|
|
|
|
}
|
2025-11-18 21:21:20 +08:00
|
|
|
|
|
2025-11-23 01:39:44 +08:00
|
|
|
|
const getAuthHeaders = () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
|
return userStore.getAuthHeaders()
|
|
|
|
|
|
}
|
2025-11-18 21:21:20 +08:00
|
|
|
|
|
2025-12-15 23:25:56 +08:00
|
|
|
|
const getDatabaseName = () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const selectedDb = state.dbOptions.find((db) => db.value === state.selectedDbId)
|
|
|
|
|
|
return selectedDb ? selectedDb.label : state.selectedDbId
|
|
|
|
|
|
}
|
2025-12-15 23:25:56 +08:00
|
|
|
|
|
2025-12-16 00:27:40 +08:00
|
|
|
|
const getUploadTipMessage = () => {
|
|
|
|
|
|
if (isNeo4j.value) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
return 'Neo4j 图数据库支持通过上传 JSONL 格式文件直接导入实体和关系数据。'
|
2025-12-16 00:27:40 +08:00
|
|
|
|
} else {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const selectedDb = state.dbOptions.find((db) => db.value === state.selectedDbId)
|
|
|
|
|
|
const dbType = selectedDb?.type || '未知'
|
|
|
|
|
|
const dbName = selectedDb?.label || getDatabaseName()
|
|
|
|
|
|
return `当前选择的是 ${dbType.toUpperCase()} 类型的知识库"${dbName}",该类型知识库需要在文档知识库页面上传文档,系统会自动从中提取知识图谱。`
|
2025-12-16 00:27:40 +08:00
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2025-12-16 00:27:40 +08:00
|
|
|
|
|
|
|
|
|
|
const goToDatabasePage = () => {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
state.showUploadTipModal = false
|
2025-12-16 00:27:40 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果不是 Neo4j,需要找到对应的知识库 ID 并跳转
|
|
|
|
|
|
if (!isNeo4j.value) {
|
2026-01-15 06:01:34 +08:00
|
|
|
|
const selectedDb = state.dbOptions.find((db) => db.value === state.selectedDbId)
|
2025-12-16 00:27:40 +08:00
|
|
|
|
if (selectedDb && selectedDb.type !== 'neo4j') {
|
|
|
|
|
|
// 跳转到对应的知识库详情页面
|
2026-01-15 06:01:34 +08:00
|
|
|
|
router.push(`/database/${state.selectedDbId}`)
|
2025-12-16 00:27:40 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 如果找不到对应的数据库,跳转到数据库列表页面
|
2026-01-15 06:01:34 +08:00
|
|
|
|
router.push('/database')
|
2025-12-16 00:27:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
}
|
2024-07-17 18:52:20 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
2024-07-24 18:32:14 +08:00
|
|
|
|
<style lang="less" scoped>
|
2026-04-07 10:50:01 +08:00
|
|
|
|
@graph-header-height: 50px;
|
2025-09-03 03:00:25 +08:00
|
|
|
|
|
2024-09-06 12:54:17 +08:00
|
|
|
|
.graph-container {
|
2024-09-14 02:46:44 +08:00
|
|
|
|
padding: 0;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
background-color: var(--gray-0);
|
2025-09-03 03:00:25 +08:00
|
|
|
|
|
|
|
|
|
|
.header-container {
|
|
|
|
|
|
height: @graph-header-height;
|
|
|
|
|
|
}
|
2024-09-14 02:46:44 +08:00
|
|
|
|
}
|
2024-09-06 12:54:17 +08:00
|
|
|
|
|
2025-12-15 23:25:56 +08:00
|
|
|
|
.db-selector {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
.label {
|
2025-12-16 00:27:40 +08:00
|
|
|
|
font-size: 14px;
|
2025-12-15 23:25:56 +08:00
|
|
|
|
margin-right: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-14 02:46:44 +08:00
|
|
|
|
.status-wrapper {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-right: 16px;
|
|
|
|
|
|
font-size: 14px;
|
2025-11-23 01:39:44 +08:00
|
|
|
|
color: var(--color-text-secondary);
|
2024-09-14 02:46:44 +08:00
|
|
|
|
}
|
2024-09-06 12:54:17 +08:00
|
|
|
|
|
2025-08-24 16:51:23 +08:00
|
|
|
|
.status-text {
|
|
|
|
|
|
margin-left: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-14 02:46:44 +08:00
|
|
|
|
.status-indicator {
|
|
|
|
|
|
width: 12px;
|
|
|
|
|
|
height: 12px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
|
|
|
|
|
|
&.loading {
|
2025-11-25 22:34:44 +08:00
|
|
|
|
background-color: var(--color-warning-500);
|
2024-09-14 02:46:44 +08:00
|
|
|
|
animation: pulse 1.5s infinite ease-in-out;
|
2024-09-06 12:54:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-14 02:46:44 +08:00
|
|
|
|
&.open {
|
2025-11-25 22:34:44 +08:00
|
|
|
|
background-color: var(--color-success-500);
|
2024-09-06 12:54:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-14 02:46:44 +08:00
|
|
|
|
&.closed {
|
2025-11-25 22:34:44 +08:00
|
|
|
|
background-color: var(--color-error-500);
|
2024-09-06 12:54:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-14 02:46:44 +08:00
|
|
|
|
@keyframes pulse {
|
|
|
|
|
|
0% {
|
|
|
|
|
|
transform: scale(0.8);
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
50% {
|
|
|
|
|
|
transform: scale(1.2);
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
|
|
|
transform: scale(0.8);
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-09-06 12:54:17 +08:00
|
|
|
|
|
2025-09-03 03:00:25 +08:00
|
|
|
|
.upload {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
.upload-dragger {
|
|
|
|
|
|
margin: 0px;
|
|
|
|
|
|
}
|
2026-01-01 23:39:17 +08:00
|
|
|
|
|
|
|
|
|
|
.upload-config {
|
|
|
|
|
|
margin: 24px 0;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
background-color: var(--gray-0);
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
.config-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
&:last-of-type {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.config-label {
|
|
|
|
|
|
width: 100px;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: var(--color-text);
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
margin-right: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.config-field {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.config-hint-row {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
padding-left: 116px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--color-text-secondary);
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-03 03:00:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.container-outter {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: calc(100vh - @graph-header-height);
|
|
|
|
|
|
overflow: hidden;
|
2025-10-07 00:15:14 +08:00
|
|
|
|
background: var(--gray-10);
|
2025-09-03 03:00:25 +08:00
|
|
|
|
|
2025-12-20 15:44:37 +08:00
|
|
|
|
.actions {
|
2025-09-03 03:00:25 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin: 20px 0;
|
|
|
|
|
|
padding: 0 24px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
2025-11-23 01:39:44 +08:00
|
|
|
|
|
|
|
|
|
|
.tags {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
2025-09-03 03:00:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-07-24 18:32:14 +08:00
|
|
|
|
.actions {
|
2025-09-03 03:00:25 +08:00
|
|
|
|
top: 0;
|
2024-07-24 18:32:14 +08:00
|
|
|
|
|
2026-01-15 06:01:34 +08:00
|
|
|
|
.actions-left,
|
|
|
|
|
|
.actions-right {
|
2024-09-06 12:54:17 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-03 03:00:25 +08:00
|
|
|
|
:deep(.ant-input) {
|
2026-01-27 21:46:10 +08:00
|
|
|
|
padding: 2px 0px;
|
2024-07-24 18:32:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
button {
|
2025-09-03 03:00:25 +08:00
|
|
|
|
height: 37px;
|
2024-07-24 18:32:14 +08:00
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-16 00:27:40 +08:00
|
|
|
|
|
|
|
|
|
|
.upload-tip-content {
|
|
|
|
|
|
.upload-tip-actions {
|
|
|
|
|
|
p {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
color: var(--color-text-secondary);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-buttons {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-15 06:01:34 +08:00
|
|
|
|
</style>
|