feat: 新增知识库文档查找和打开工具,优化数据库信息展示
This commit is contained in:
parent
1a3ec71b2b
commit
213a542a87
@ -27,4 +27,6 @@
|
||||
- [ ] Config spacy model 的 load
|
||||
- [x] 在工作区的文件编辑的时候,保存和取消的按钮应该是悬浮在编辑框的右上角,而不是在 header 上面
|
||||
- [ ] default enable all build in tools / kbs / skills / mcps / subagents
|
||||
- [ ] 链接 Notion 和 feishu 目前来看,都是支持的
|
||||
- [ ] 链接 Notion 和 feishu 目前来看,都是支持的
|
||||
- [ ] 知识库的权限调整,修改为三个等级,全局共享、部门共享(选择多个部门,默认是自己部门且必须包含自己部门)、指定人可访问(选择多个用户,默认是仅自己,可以添加其他人)。UI 上也需要调整,三个卡片不再是等宽,而是选中的会宽一点,并展示描述以及选择按钮,未选中的则是默认宽度,仅显示标题。对于选中的卡片除了展示描述、按钮之外,还包括“X 个部门可访问”、“X 个用户可访问”的信息展示。全局的就看是所有用户可访问。所以等级的字段配置也要重新设计,不需要考虑兼容,所有知识库都会重新构建。
|
||||
- [ ] databaseinfo 的重构,在左侧展示那个 tab 标签吧,将文件管理(filetable)以及右侧的那些图谱、检索、检索配置、评估之类的,都列为不同的 tab,进入之后默认激活的是 filetable。这样页面布局就好的多。作恶侧边栏除了这些 tab 之外,顶部是和 Skill Detail 那里的 header 一样,
|
||||
@ -23,6 +23,8 @@ import WebSearchTool from './tools/WebSearchTool.vue'
|
||||
import ListKbsTool from './tools/ListKbsTool.vue'
|
||||
import GetMindmapTool from './tools/GetMindmapTool.vue'
|
||||
import QueryKbTool from './tools/QueryKbTool.vue'
|
||||
import FindKbDocumentTool from './tools/FindKbDocumentTool.vue'
|
||||
import OpenKbDocumentTool from './tools/OpenKbDocumentTool.vue'
|
||||
import CalculatorTool from './tools/CalculatorTool.vue'
|
||||
import TodoListTool from './tools/TodoListTool.vue'
|
||||
import TaskTool from './tools/TaskTool.vue'
|
||||
@ -65,6 +67,7 @@ const TOOL_RENDERERS = {
|
||||
cmd: ExecuteTool,
|
||||
edit_file: EditFileTool,
|
||||
execute: ExecuteTool,
|
||||
find_kb_document: FindKbDocumentTool,
|
||||
get_mindmap: GetMindmapTool,
|
||||
glob: GlobTool,
|
||||
grep: GrepTool,
|
||||
@ -74,6 +77,7 @@ const TOOL_RENDERERS = {
|
||||
mysql_describe_table: MysqlDescribeTableTool,
|
||||
mysql_list_tables: MysqlListTablesTool,
|
||||
mysql_query: MysqlQueryTool,
|
||||
open_kb_document: OpenKbDocumentTool,
|
||||
query_kb: QueryKbTool,
|
||||
read_file: ReadFileTool,
|
||||
replace: EditFileTool,
|
||||
|
||||
@ -7,6 +7,8 @@ export { default as WebSearchTool } from './tools/WebSearchTool.vue'
|
||||
export { default as ListKbsTool } from './tools/ListKbsTool.vue'
|
||||
export { default as GetMindmapTool } from './tools/GetMindmapTool.vue'
|
||||
export { default as QueryKbTool } from './tools/QueryKbTool.vue'
|
||||
export { default as FindKbDocumentTool } from './tools/FindKbDocumentTool.vue'
|
||||
export { default as OpenKbDocumentTool } from './tools/OpenKbDocumentTool.vue'
|
||||
export { default as CalculatorTool } from './tools/CalculatorTool.vue'
|
||||
export { default as TodoListTool } from './tools/TodoListTool.vue'
|
||||
export { default as ImageTool } from './tools/ImageTool.vue'
|
||||
|
||||
@ -24,6 +24,7 @@ export const TOOL_ICON_MAP = {
|
||||
cmd: Terminal,
|
||||
edit_file: FilePen,
|
||||
execute: Terminal,
|
||||
find_kb_document: FolderSearch,
|
||||
get_mindmap: Network,
|
||||
glob: FolderSearch,
|
||||
grep: FolderSearch,
|
||||
@ -33,6 +34,7 @@ export const TOOL_ICON_MAP = {
|
||||
mysql_describe_table: Database,
|
||||
mysql_list_tables: Database,
|
||||
mysql_query: Database,
|
||||
open_kb_document: FileText,
|
||||
present_artifacts: FolderOutput,
|
||||
query_kb: BookOpen,
|
||||
read_file: FileText,
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<BaseToolCall :tool-call="toolCall" :hide-params="true">
|
||||
<template #header>
|
||||
<div class="sep-header">
|
||||
<span class="note">Find</span>
|
||||
<span class="separator" v-if="resourceName">|</span>
|
||||
<span class="description" v-if="resourceName">知识库: {{ resourceName }}</span>
|
||||
<span class="separator" v-if="fileId">|</span>
|
||||
<span class="description" v-if="fileId">文件: {{ fileId }}</span>
|
||||
<span class="tag" v-if="patternsLabel" :title="fullPatternsLabel">{{ patternsLabel }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #result="{ resultContent }">
|
||||
<KbDocumentPreview
|
||||
v-if="isPreviewResult(parsedResult(resultContent))"
|
||||
:result="parsedResult(resultContent)"
|
||||
mode="find"
|
||||
/>
|
||||
<div v-else class="plain-result">{{ stringifyResult(resultContent) }}</div>
|
||||
</template>
|
||||
</BaseToolCall>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import BaseToolCall from '../BaseToolCall.vue'
|
||||
import KbDocumentPreview from './KbDocumentPreview.vue'
|
||||
import { useDatabaseStore } from '@/stores/database'
|
||||
|
||||
const props = defineProps({
|
||||
toolCall: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const databaseStore = useDatabaseStore()
|
||||
|
||||
const args = computed(() => {
|
||||
const value = props.toolCall.args || props.toolCall.function?.arguments
|
||||
if (!value) return {}
|
||||
if (typeof value === 'object') return value
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
|
||||
const resourceName = computed(() => databaseStore.getDatabaseNameById(args.value.resource_id))
|
||||
const fileId = computed(() => args.value.file_id || '')
|
||||
const patterns = computed(() => (Array.isArray(args.value.patterns) ? args.value.patterns : []))
|
||||
const fullPatternsLabel = computed(() => patterns.value.join(', '))
|
||||
const patternsLabel = computed(() => {
|
||||
if (patterns.value.length <= 3) return fullPatternsLabel.value
|
||||
return `${patterns.value.slice(0, 3).join(', ')}...`
|
||||
})
|
||||
|
||||
const parsedResult = (content) => {
|
||||
if (typeof content !== 'string') return content
|
||||
try {
|
||||
return JSON.parse(content)
|
||||
} catch {
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
const isPreviewResult = (result) =>
|
||||
result && typeof result === 'object' && Array.isArray(result.windows)
|
||||
|
||||
const stringifyResult = (content) => {
|
||||
const result = parsedResult(content)
|
||||
return typeof result === 'string' ? result : JSON.stringify(result, null, 2)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.plain-result {
|
||||
border: 1px solid var(--gray-150);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
color: var(--gray-700);
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
176
web/src/components/ToolCallingResult/tools/KbDocumentPreview.vue
Normal file
176
web/src/components/ToolCallingResult/tools/KbDocumentPreview.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div class="kb-document-preview">
|
||||
<div class="document-summary">
|
||||
<div class="summary-main">{{ summaryText }}</div>
|
||||
<div v-if="result?.file_id" class="summary-meta">file_id: {{ result.file_id }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="documentWindows.length > 0" class="window-list">
|
||||
<div
|
||||
v-for="(window, windowIndex) in documentWindows"
|
||||
:key="windowKey(window, windowIndex)"
|
||||
class="window-card"
|
||||
>
|
||||
<div class="window-title">
|
||||
<span>{{ windowTitle(window, windowIndex) }}</span>
|
||||
<span v-if="window.matched_lines?.length" class="matched-count">
|
||||
命中 {{ window.matched_lines.length }} 行
|
||||
</span>
|
||||
</div>
|
||||
<pre class="content-preview"><span
|
||||
v-for="(line, lineIndex) in splitContent(window.content)"
|
||||
:key="`${windowKey(window, windowIndex)}-${lineIndex}`"
|
||||
class="content-line"
|
||||
:class="{ matched: isMatchedLine(line, window.matched_lines) }"
|
||||
>{{ line }}</span></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="empty-text">暂无可预览内容</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
result: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'open'
|
||||
}
|
||||
})
|
||||
|
||||
const documentWindows = computed(() => {
|
||||
if (Array.isArray(props.result.windows)) return props.result.windows
|
||||
if (props.result.content) return [props.result]
|
||||
return []
|
||||
})
|
||||
|
||||
const summaryText = computed(() => {
|
||||
if (props.mode === 'find') {
|
||||
const modeText = props.result.match_mode === 'regex' ? '正则' : '关键词'
|
||||
return `${modeText}查找: ${props.result.total_matches || 0} 处匹配,${documentWindows.value.length} 个上下文窗口`
|
||||
}
|
||||
|
||||
const startLine = props.result.start_line || 0
|
||||
const endLine = props.result.end_line || 0
|
||||
const totalLines = props.result.total_lines || 0
|
||||
const moreText = props.result.has_more_after ? `,下一段 offset ${props.result.next_offset}` : ''
|
||||
return `打开文档: 第 ${startLine}-${endLine} 行 / 共 ${totalLines} 行${moreText}`
|
||||
})
|
||||
|
||||
const splitContent = (content = '') => String(content).split('\n')
|
||||
|
||||
const lineNumber = (line) => {
|
||||
const match = String(line).match(/^\s*(\d+)\t/)
|
||||
return match ? Number(match[1]) : null
|
||||
}
|
||||
|
||||
const isMatchedLine = (line, matchedLines = []) => {
|
||||
const number = lineNumber(line)
|
||||
return number !== null && matchedLines.includes(number)
|
||||
}
|
||||
|
||||
const windowKey = (window, index) => `${window.start_line || 0}-${window.end_line || 0}-${index}`
|
||||
|
||||
const windowTitle = (window, index) => {
|
||||
const startLine = window.start_line || 0
|
||||
const endLine = window.end_line || 0
|
||||
if (startLine || endLine) return `窗口 ${index + 1}: 第 ${startLine}-${endLine} 行`
|
||||
return `窗口 ${index + 1}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.kb-document-preview {
|
||||
border: 1px solid var(--gray-150);
|
||||
border-radius: 8px;
|
||||
background: var(--gray-0);
|
||||
overflow: hidden;
|
||||
|
||||
.document-summary {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
background: var(--gray-25);
|
||||
border-bottom: 1px solid var(--gray-100);
|
||||
font-size: 12px;
|
||||
color: var(--gray-700);
|
||||
|
||||
.summary-main {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.summary-meta {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
}
|
||||
|
||||
.window-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.window-card {
|
||||
border-bottom: 1px solid var(--gray-100);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.window-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
color: var(--gray-700);
|
||||
background: var(--gray-10);
|
||||
|
||||
.matched-count {
|
||||
color: var(--main-700);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.content-preview {
|
||||
margin: 0;
|
||||
padding: 10px 12px;
|
||||
max-height: 360px;
|
||||
overflow: auto;
|
||||
background: var(--gray-0);
|
||||
color: var(--gray-700);
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.content-line {
|
||||
display: block;
|
||||
min-height: 1.6em;
|
||||
|
||||
&.matched {
|
||||
background: var(--main-50);
|
||||
color: var(--main-800);
|
||||
}
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
padding: 14px;
|
||||
text-align: center;
|
||||
color: var(--gray-600);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<BaseToolCall :tool-call="toolCall" :hide-params="true">
|
||||
<template #header>
|
||||
<div class="sep-header">
|
||||
<span class="note">Open</span>
|
||||
<span class="separator" v-if="resourceName">|</span>
|
||||
<span class="description" v-if="resourceName">知识库: {{ resourceName }}</span>
|
||||
<span class="separator" v-if="fileId">|</span>
|
||||
<span class="description" v-if="fileId">文件: {{ fileId }}</span>
|
||||
<span class="tag" v-if="lineLabel">{{ lineLabel }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #result="{ resultContent }">
|
||||
<KbDocumentPreview
|
||||
v-if="isPreviewResult(parsedResult(resultContent))"
|
||||
:result="parsedResult(resultContent)"
|
||||
mode="open"
|
||||
/>
|
||||
<div v-else class="plain-result">{{ stringifyResult(resultContent) }}</div>
|
||||
</template>
|
||||
</BaseToolCall>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import BaseToolCall from '../BaseToolCall.vue'
|
||||
import KbDocumentPreview from './KbDocumentPreview.vue'
|
||||
import { useDatabaseStore } from '@/stores/database'
|
||||
|
||||
const props = defineProps({
|
||||
toolCall: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const databaseStore = useDatabaseStore()
|
||||
|
||||
const args = computed(() => {
|
||||
const value = props.toolCall.args || props.toolCall.function?.arguments
|
||||
if (!value) return {}
|
||||
if (typeof value === 'object') return value
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
|
||||
const resourceName = computed(() => databaseStore.getDatabaseNameById(args.value.resource_id))
|
||||
const fileId = computed(() => args.value.file_id || '')
|
||||
const lineLabel = computed(() => {
|
||||
if (args.value.line) return `Line ${args.value.line}`
|
||||
if (args.value.offset !== undefined) return `Offset ${args.value.offset}`
|
||||
return ''
|
||||
})
|
||||
|
||||
const parsedResult = (content) => {
|
||||
if (typeof content !== 'string') return content
|
||||
try {
|
||||
return JSON.parse(content)
|
||||
} catch {
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
const isPreviewResult = (result) =>
|
||||
result && typeof result === 'object' && typeof result.content === 'string'
|
||||
|
||||
const stringifyResult = (content) => {
|
||||
const result = parsedResult(content)
|
||||
return typeof result === 'string' ? result : JSON.stringify(result, null, 2)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.plain-result {
|
||||
border: 1px solid var(--gray-150);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
color: var(--gray-700);
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
@ -3,8 +3,8 @@
|
||||
<template #header>
|
||||
<div class="sep-header">
|
||||
<span class="note">{{ operationLabel }}</span>
|
||||
<span class="separator" v-if="kbName">|</span>
|
||||
<span class="description" v-if="kbName">知识库: {{ kbName }}</span>
|
||||
<span class="separator" v-if="resourceLabel">|</span>
|
||||
<span class="description" v-if="resourceLabel">知识库: {{ resourceLabel }}</span>
|
||||
<span class="separator" v-if="queryText">|</span>
|
||||
<span class="description">{{ queryText }}</span>
|
||||
</div>
|
||||
@ -93,6 +93,7 @@
|
||||
import { computed } from 'vue'
|
||||
import BaseToolCall from '../BaseToolCall.vue'
|
||||
import KbResultGroupedList from '@/components/sources/KbResultGroupedList.vue'
|
||||
import { useDatabaseStore } from '@/stores/database'
|
||||
|
||||
const props = defineProps({
|
||||
toolCall: {
|
||||
@ -101,6 +102,8 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const databaseStore = useDatabaseStore()
|
||||
|
||||
const args = computed(() => {
|
||||
const value = props.toolCall.args || props.toolCall.function?.arguments
|
||||
if (!value) return {}
|
||||
@ -116,7 +119,9 @@ const toolName = computed(() => props.toolCall.name || props.toolCall.function?.
|
||||
|
||||
const operationLabel = computed(() => `${toolName.value} 搜索`)
|
||||
|
||||
const kbName = computed(() => args.value.kb_name || '')
|
||||
const resourceLabel = computed(
|
||||
() => args.value.kb_name || databaseStore.getDatabaseNameById(args.value.resource_id)
|
||||
)
|
||||
const queryText = computed(() => args.value.query_text || '')
|
||||
|
||||
const EMPTY_RESULT = Object.freeze({
|
||||
@ -133,7 +138,9 @@ const normalizeChunks = (payload) => {
|
||||
if (Array.isArray(payload)) return payload
|
||||
if (!payload || typeof payload !== 'object') return []
|
||||
|
||||
if (Array.isArray(payload.results)) return payload.results
|
||||
if (Array.isArray(payload.chunks)) return payload.chunks
|
||||
if (Array.isArray(payload.data?.results)) return payload.data.results
|
||||
if (Array.isArray(payload.data?.chunks)) return payload.data.chunks
|
||||
|
||||
return []
|
||||
|
||||
@ -97,7 +97,31 @@ const resolveChunks = (input) => {
|
||||
}
|
||||
|
||||
const normalizedChunks = computed(() =>
|
||||
resolveChunks(props.chunks).filter((item) => item && typeof item === 'object' && item.content)
|
||||
resolveChunks(props.chunks)
|
||||
.filter((item) => item && typeof item === 'object' && item.content)
|
||||
.map((item) => {
|
||||
const metadata = item.metadata && typeof item.metadata === 'object' ? item.metadata : {}
|
||||
const source =
|
||||
metadata.source ||
|
||||
metadata.file_name ||
|
||||
metadata.filename ||
|
||||
metadata.title ||
|
||||
item.file_name ||
|
||||
item.filename ||
|
||||
item.file_id ||
|
||||
item.resource_id ||
|
||||
'未知来源'
|
||||
|
||||
return {
|
||||
...item,
|
||||
score: typeof item.score === 'number' ? item.score : metadata.score,
|
||||
metadata: {
|
||||
...metadata,
|
||||
source,
|
||||
chunk_id: metadata.chunk_id || item.id
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const fileGroupList = computed(() => {
|
||||
|
||||
@ -527,6 +527,22 @@ export const useDatabaseStore = defineStore('database', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function getDatabaseNameById(id) {
|
||||
const normalizedId = String(id || '').trim()
|
||||
if (!normalizedId) return ''
|
||||
|
||||
const matchedDatabase = databases.value.find(
|
||||
(item) => String(item.db_id || '').trim() === normalizedId
|
||||
)
|
||||
if (matchedDatabase?.name) return matchedDatabase.name
|
||||
|
||||
if (String(database.value?.db_id || '').trim() === normalizedId) {
|
||||
return database.value?.name || ''
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
return {
|
||||
databases,
|
||||
database,
|
||||
@ -554,6 +570,7 @@ export const useDatabaseStore = defineStore('database', () => {
|
||||
startAutoRefresh,
|
||||
stopAutoRefresh,
|
||||
toggleAutoRefresh,
|
||||
selectAllFailedFiles
|
||||
selectAllFailedFiles,
|
||||
getDatabaseNameById
|
||||
}
|
||||
})
|
||||
|
||||
@ -16,7 +16,7 @@ export const brandIcons = {
|
||||
|
||||
export const getKbTypeLabel = (type) => {
|
||||
const labels = {
|
||||
milvus: 'CommonRAG',
|
||||
milvus: 'Yuxi',
|
||||
dify: 'Dify',
|
||||
notion: 'Notion'
|
||||
}
|
||||
@ -40,3 +40,24 @@ export const getKbTypeColor = (type) => {
|
||||
}
|
||||
return colors[type] || 'blue'
|
||||
}
|
||||
|
||||
const READ_ONLY_KB_TYPES = new Set(['dify', 'notion'])
|
||||
|
||||
export const isReadOnlyDatabase = (database, kbTypes = {}) => {
|
||||
const kbType = (typeof database === 'string' ? database : database?.kb_type || 'milvus').toLowerCase()
|
||||
|
||||
if (database?.supports_documents !== undefined) {
|
||||
return database.supports_documents === false
|
||||
}
|
||||
if (kbTypes[kbType]?.supports_documents !== undefined) {
|
||||
return kbTypes[kbType].supports_documents === false
|
||||
}
|
||||
return READ_ONLY_KB_TYPES.has(kbType)
|
||||
}
|
||||
|
||||
export const kbUtils = {
|
||||
getKbTypeLabel,
|
||||
getKbTypeIcon,
|
||||
getKbTypeColor,
|
||||
isReadOnlyDatabase
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onUnmounted, computed } from 'vue'
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useDatabaseStore } from '@/stores/database'
|
||||
import { useTaskerStore } from '@/stores/tasker'
|
||||
@ -157,6 +157,7 @@ import RAGEvaluationTab from '@/components/RAGEvaluationTab.vue'
|
||||
import EvaluationBenchmarks from '@/components/EvaluationBenchmarks.vue'
|
||||
import SearchConfigModal from '@/components/SearchConfigModal.vue'
|
||||
import SearchConfigPanel from '@/components/SearchConfigPanel.vue'
|
||||
import { kbUtils } from '@/utils/kb_utils'
|
||||
|
||||
const route = useRoute()
|
||||
const store = useDatabaseStore()
|
||||
@ -168,7 +169,9 @@ const state = computed(() => store.state)
|
||||
const isCurrentDatabaseLoaded = computed(() => database.value?.db_id === databaseId.value)
|
||||
const kbType = computed(() => (isCurrentDatabaseLoaded.value ? database.value.kb_type?.toLowerCase() : ''))
|
||||
const isMilvus = computed(() => kbType.value === 'milvus')
|
||||
const isConnector = computed(() => Boolean(kbType.value) && !isMilvus.value)
|
||||
const isConnector = computed(
|
||||
() => isCurrentDatabaseLoaded.value && kbUtils.isReadOnlyDatabase(database.value)
|
||||
)
|
||||
|
||||
// 计算待解析文件数量(status: 'uploaded')
|
||||
const pendingParseCount = computed(() => {
|
||||
@ -237,9 +240,7 @@ const isDragging = ref(false)
|
||||
|
||||
watch(
|
||||
() => [databaseId.value, isMilvus.value],
|
||||
([newDbId, isMilvusType], oldValue = []) => {
|
||||
const [oldDbId] = oldValue
|
||||
|
||||
([newDbId, isMilvusType]) => {
|
||||
if (!newDbId) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -220,7 +220,7 @@ import ExtensionCardGrid from '@/components/extensions/ExtensionCardGrid.vue'
|
||||
import InfoCard from '@/components/shared/InfoCard.vue'
|
||||
import dayjs, { parseToShanghai } from '@/utils/time'
|
||||
import AiTextarea from '@/components/AiTextarea.vue'
|
||||
import { getKbTypeLabel, getKbTypeIcon, getKbTypeColor } from '@/utils/kb_utils'
|
||||
import { getKbTypeLabel, getKbTypeIcon, getKbTypeColor, kbUtils } from '@/utils/kb_utils'
|
||||
import { CHUNK_PRESET_OPTIONS, getChunkPresetDescription } from '@/utils/chunk_presets'
|
||||
|
||||
const route = useRoute()
|
||||
@ -450,10 +450,13 @@ const handleCreateDatabase = async () => {
|
||||
}
|
||||
|
||||
const cardSubtitle = (database) => {
|
||||
const parts = [`${database.row_count || 0} 文件`]
|
||||
const parts = []
|
||||
if (database.created_at) {
|
||||
parts.push(formatCreatedTime(database.created_at))
|
||||
}
|
||||
if (!kbUtils.isReadOnlyDatabase(database)) {
|
||||
parts.push(`${database.row_count || 0} 文件`)
|
||||
}
|
||||
return parts.join(' · ')
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user