|
25 | 25 | :options="state.dbOptions" |
26 | 26 | @change="handleDbChange" |
27 | 27 | :loading="state.loadingDatabases" |
| 28 | + mode="combobox" |
| 29 | + placeholder="选择或输入KB ID" |
28 | 30 | /> |
29 | 31 | </div> |
30 | 32 | <!-- <a-button type="default" @click="openLink('http://localhost:7474/')" :icon="h(GlobalOutlined)"> |
|
58 | 60 | <div class="actions-left"> |
59 | 61 | <a-input |
60 | 62 | v-model:value="state.searchInput" |
61 | | - :placeholder="isNeo4j ? '输入要查询的实体' : '输入要查询的实体 (*为全部)'" |
| 63 | + placeholder="输入要查询的实体 (*为全部)" |
62 | 64 | style="width: 300px" |
63 | 65 | @keydown.enter="onSearch" |
64 | 66 | allow-clear |
|
125 | 127 | <a-modal |
126 | 128 | :open="state.showModal" title="上传文件" |
127 | 129 | @ok="addDocumentByFile" |
128 | | - @cancel="() => state.showModal = false" |
| 130 | + @cancel="handleModalCancel" |
129 | 131 | ok-text="添加到图数据库" cancel-text="取消" |
130 | | - :confirm-loading="state.processing"> |
| 132 | + :confirm-loading="state.processing" |
| 133 | + :ok-button-props="{ disabled: !hasValidFile }"> |
131 | 134 | <div class="upload"> |
132 | 135 | <div class="note"> |
133 | 136 | <p>上传的文件内容参考 test/data/A_Dream_of_Red_Mansions_tiny.jsonl 中的格式:</p> |
|
177 | 180 | </div> |
178 | 181 | </div> |
179 | 182 | </a-modal> |
180 | | - |
181 | | - <!-- 说明弹窗 --> |
182 | | - <a-modal |
183 | | - :open="state.showInfoModal" |
184 | | - title="图数据库说明" |
185 | | - @cancel="() => state.showInfoModal = false" |
186 | | - :footer="null" |
187 | | - width="600px" |
188 | | - > |
189 | | - <div class="info-content" v-if="isNeo4j"> |
190 | | - <p>本页面展示的是 Neo4j 图数据库中的知识图谱信息。</p> |
191 | | - <p>具体展示内容包括:</p> |
192 | | - <ul> |
193 | | - <li>带有 <code>Entity</code> 标签的节点</li> |
194 | | - <li>带有 <code>RELATION</code> 类型的关系边</li> |
195 | | - </ul> |
196 | | - <p>注意:</p> |
197 | | - <ul> |
198 | | - <li>这里仅展示用户上传的实体和关系,不包含知识库中自动创建的图谱。</li> |
199 | | - <li>查询逻辑基于 <code>graphbase.py</code> 中的 <code>get_sample_nodes</code> 方法实现。</li> |
200 | | - </ul> |
201 | | - </div> |
202 | | - <div class="info-content" v-else> |
203 | | - <p>本页面展示的是 LightRAG 知识库生成的图谱信息。</p> |
204 | | - <p>数据来源于选定的知识库实例。</p> |
205 | | - <p>支持通过实体名称进行模糊搜索,输入 "*" 可查看采样全图。</p> |
206 | | - </div> |
207 | | - </a-modal> |
208 | 183 | </div> |
209 | 184 | </template> |
210 | 185 |
|
@@ -252,7 +227,21 @@ const state = reactive({ |
252 | 227 | lightragStats: null, |
253 | 228 | }) |
254 | 229 |
|
255 | | -const isNeo4j = computed(() => state.selectedDbId === 'neo4j'); |
| 230 | +const isNeo4j = computed(() => { |
| 231 | + // 当 selectedDbId 是 'neo4j' 时,或者以 'kb_' 开头时,我们认为它是 Neo4j 驱动的 |
| 232 | + // 但是对于 kb_ 开头的,我们可能想要使用 LightRAGInfoPanel 的展示风格(因为它有 stats) |
| 233 | + // 或者复用 GraphInfoPanel。 |
| 234 | + // GraphInfoPanel 是为 'neo4j' 全局图设计的,包含了 model matching 检查等。 |
| 235 | + // KBs (kb_*) 使用 Neo4j 存储,但逻辑上更接近 LightRAG 的"知识库"概念。 |
| 236 | + // 为了让 LightRAGInfoPanel 能够展示 stats (get_stats 已经更新支持 kb_), |
| 237 | + // 我们这里让 kb_ ID 返回 false,这样会进入 LightRAGInfoPanel 分支。 |
| 238 | + return state.selectedDbId === 'neo4j'; |
| 239 | +}); |
| 240 | +
|
| 241 | +// 检查是否有有效的已上传文件 |
| 242 | +const hasValidFile = computed(() => { |
| 243 | + return fileList.value.some(file => file.status === 'done' && file.response?.file_path); |
| 244 | +}); |
256 | 245 |
|
257 | 246 | // 计算未索引节点数量 |
258 | 247 | const unindexedCount = computed(() => { |
@@ -293,7 +282,7 @@ const handleDbChange = () => { |
293 | 282 | if (isNeo4j.value) { |
294 | 283 | loadGraphInfo(); |
295 | 284 | } else { |
296 | | - // Also load stats for LightRAG |
| 285 | + // Also load stats for LightRAG or KB |
297 | 286 | loadLightRAGStats(); |
298 | 287 | } |
299 | 288 | loadSampleNodes(); |
@@ -323,13 +312,37 @@ const loadGraphInfo = () => { |
323 | 312 | } |
324 | 313 |
|
325 | 314 | const addDocumentByFile = () => { |
| 315 | + // 使用计算属性验证文件 |
| 316 | + if (!hasValidFile.value) { |
| 317 | + message.error('请先等待文件上传完成') |
| 318 | + return |
| 319 | + } |
| 320 | +
|
326 | 321 | state.processing = true |
327 | | - const files = fileList.value.filter(file => file.status === 'done').map(file => file.response.file_path) |
328 | | - neo4jApi.addEntities(files[0]) |
| 322 | +
|
| 323 | + // 获取已上传的文件路径 |
| 324 | + const uploadedFile = fileList.value.find(file => file.status === 'done' && file.response?.file_path); |
| 325 | + const filePath = uploadedFile?.response?.file_path; |
| 326 | +
|
| 327 | + // 再次验证文件路径 |
| 328 | + if (!filePath) { |
| 329 | + message.error('文件路径获取失败,请重新上传文件') |
| 330 | + state.processing = false |
| 331 | + return |
| 332 | + } |
| 333 | +
|
| 334 | + neo4jApi.addEntities(filePath) |
329 | 335 | .then((data) => { |
330 | 336 | if (data.status === 'success') { |
331 | 337 | message.success(data.message); |
332 | 338 | state.showModal = false; |
| 339 | + // 清空文件列表 |
| 340 | + fileList.value = []; |
| 341 | + // 刷新图谱数据 |
| 342 | + setTimeout(() => { |
| 343 | + loadGraphInfo(); |
| 344 | + loadSampleNodes(); |
| 345 | + }, 500); |
333 | 346 | } else { |
334 | 347 | throw new Error(data.message); |
335 | 348 | } |
@@ -372,11 +385,6 @@ const onSearch = () => { |
372 | 385 | // 可选:提示模型不一致 |
373 | 386 | } |
374 | 387 |
|
375 | | - if (!state.searchInput && isNeo4j.value) { |
376 | | - message.error('请输入要查询的实体') |
377 | | - return |
378 | | - } |
379 | | -
|
380 | 388 | state.searchLoading = true |
381 | 389 |
|
382 | 390 | unifiedApi.getSubgraph({ |
@@ -409,16 +417,35 @@ onMounted(async () => { |
409 | 417 | loadSampleNodes(); |
410 | 418 | }); |
411 | 419 |
|
412 | | -const handleFileUpload = (event) => { |
413 | | - console.log(event) |
414 | | - console.log(fileList.value) |
| 420 | +const handleFileUpload = ({ file, fileList: newFileList }) => { |
| 421 | + // 更新文件列表 |
| 422 | + fileList.value = newFileList; |
| 423 | +
|
| 424 | + // 如果上传失败,显示错误信息 |
| 425 | + if (file.status === 'error') { |
| 426 | + message.error(`文件上传失败: ${file.name}`); |
| 427 | + } |
| 428 | +
|
| 429 | + // 如果上传成功,显示成功信息 |
| 430 | + if (file.status === 'done' && file.response?.file_path) { |
| 431 | + message.success(`文件上传成功: ${file.name}`); |
| 432 | + } |
| 433 | +
|
| 434 | + console.log('File upload status:', file.status, file.name); |
| 435 | + console.log('File list:', fileList.value); |
415 | 436 | } |
416 | 437 |
|
417 | 438 | const handleDrop = (event) => { |
418 | 439 | console.log(event) |
419 | 440 | console.log(fileList.value) |
420 | 441 | } |
421 | 442 |
|
| 443 | +const handleModalCancel = () => { |
| 444 | + state.showModal = false; |
| 445 | + // 重置文件列表 |
| 446 | + fileList.value = []; |
| 447 | +}; |
| 448 | +
|
422 | 449 | const graphStatusClass = computed(() => { |
423 | 450 | if (state.loadingGraphInfo) return 'loading'; |
424 | 451 | return graphInfo.value?.status === 'open' ? 'open' : 'closed'; |
|
0 commit comments