Skip to content

Commit dc78bb4

Browse files
feat: hit-test
1 parent 0b97408 commit dc78bb4

10 files changed

Lines changed: 76 additions & 30 deletions

File tree

‎ui/src/api/API_README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ Workspace、System 资源管理和 System 共享资源均在各自 `knowledge/kn
242242
请求使用 `KnowledgeHitTestPayload`,包含 `query_text`、`image_list`、`top_number`、`similarity` 和
243243
`search_mode`;检索模式复用 `KNOWLEDGE_SEARCH_MODE`。文本和图片至少提供一项,图片仅支持向量或混合检索。
244244
结果使用 `KnowledgeHitTestResult`,保留分段、得分及图片命中信息。Workspace 共享暂无对应接口,不回退其他范围。
245+
结果的可选字段 `document_image_id` 有值且 `document_resource_type` 为 `DOCUMENT_RESOURCE_TYPE.IMAGE` 时展示图片向量标签,否则展示文本向量标签。
245246

246247
### System 共享知识库
247248

‎ui/src/api/types/knowledge.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import type LogicFlow from '@logicflow/core'
44
import { KNOWLEDGE_SEARCH_MODE, KNOWLEDGE_SYNC_TYPE, KNOWLEDGE_TYPE } from '@/api/enums'
55
import type { DefaultModelSettingPayload } from './model'
6-
import type { DocumentTaskState } from './document'
6+
import type { DocumentResourceType, DocumentTaskState } from './document'
77
import type { ParagraphItem } from './paragraph'
88

99
/** 生成关联问题共用的模型与提示词配置。 */
@@ -190,6 +190,8 @@ export interface KnowledgeHitTestAsset {
190190

191191
/** 召回测试返回的分段与命中得分。 */
192192
export interface KnowledgeHitTestResult extends ParagraphItem {
193+
document_image_id?: string | null
194+
document_resource_type?: DocumentResourceType | null
193195
knowledge_id: string
194196
knowledge_name: string
195197
document_name: string

‎ui/src/conversation-panel/left-sidebar/conversation-list/index.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const conversationTime = (conversation: Conversation) => relativeTimeFormat(conv
3232
<span>新建对话</span>
3333
</MkListItem>
3434
<div class="flex-between pl-2 mt-4">
35-
<div class="text-N600 font-medium">历史对话</div>
35+
<h6 class="text-N600">历史对话</h6>
3636

3737
<!-- // TODO: 全部清空对话 -->
3838
<el-button text>

‎ui/src/conversation-panel/left-sidebar/portal-conversation-list/index.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const handleDeleteConversation = (group: PortalApplicationGroup, conversation: C
7070
<span>新建对话</span>
7171
</MkListItem>
7272
<div class="flex-between pl-2 mt-4">
73-
<div class="text-N600 font-medium">历史对话</div>
73+
<h6 class="text-N600 font-medium">历史对话</h6>
7474

7575
<!-- // TODO: 全部清空对话 -->
7676
<el-button text>

‎ui/src/layout/sidebar/ResourceDetailMenu.vue‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ watch(
3535
<template #label>
3636
<span class="flex-align-center min-w-0 flex-1 gap-2">
3737
<MkIcon v-if="menuItem.icon" :name="menuItem.icon" :size="18" />
38-
<span class="min-w-0 flex-1 truncate" :title="menuItem.label">{{ menuItem.label }}</span>
38+
<span class="min-w-0 flex-1 truncate">{{ menuItem.label }}</span>
3939
</span>
4040
</template>
4141
<div class="space-y-1 pt-1">
4242
<template v-for="child in menuItem.children" :key="child.name">
4343
<!-- 切换二级菜单页面 -->
4444
<MkListItem :active="child.name === props.activeName" @click="emit('select', child)">
45-
<span class="ml-6 min-w-0 flex-1 truncate" :title="child.label">{{ child.label }}</span>
45+
<span class="ml-6 min-w-0 flex-1 truncate">{{ child.label }}</span>
4646
</MkListItem>
4747
</template>
4848
</div>
@@ -51,7 +51,7 @@ watch(
5151
<MkListItem v-else :active="menuItem.name === props.activeName" @click="emit('select', menuItem)">
5252
<template #default="{ active }">
5353
<MkIcon v-if="menuItem.icon" class="mr-2" :name="active ? (menuItem.activeIcon ?? menuItem.icon) : menuItem.icon" :size="18" />
54-
<span class="min-w-0 flex-1 truncate" :title="menuItem.label">{{ menuItem.label }}</span>
54+
<span class="min-w-0 flex-1 truncate">{{ menuItem.label }}</span>
5555
</template>
5656
</MkListItem>
5757
</template>

‎ui/src/views/VIEW_README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ System 资源管理或 System 共享 Knowledge API 的 `postKnowledgeHitTest`;
153153
输入区通过 `ImageUploadCard` 展示图片进度、失败重试和删除,不提供点击预览。上传中或存在失败图片时禁测试,
154154
失败图片可直接重新上传或移除。移除与卸载释放本地预览并取消未完成上传。
155155
测试期间冻结参数与输入;结果使用 `MkSourceCard` 展示分段、得分、文档名称及命中图片,Markdown 只读。
156+
点击结果卡片按需挂载 `ParagraphDialog`,按命中分段的知识库和文档 ID 加载详情,关闭动画后卸载;保存正文后同步当前结果,保留检索得分。
156157
“本次查询”展示成功请求的文本与图片快照,图片位于文本上方,缩略图自动换行并支持预览,不使用横向滚动。
157158
初始与无命中分别展示空态;请求失败由请求层提示,保留输入与已有结果供重试。切换知识库或资源范围通过路由 key 重建召回测试页,隔离查询与结果状态。
158159

‎ui/src/views/home/components/HomeStatistics.vue‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ watch(() => [props.workspaceId, props.applicationId], loadMonitoring, { immediat
103103
<h2>
104104
{{ metric.id === 'tokens' ? formatTokenNumber(metric.series[0]?.total) : numberFormat(metric.series[0]?.total) }}
105105
</h2>
106-
<span v-if="metric.id === 'activeUsers'" class="-ml-1 font-medium text-danger" title="新增用户">
107-
+{{ numberFormat(metric.series[1]?.total) }}
108-
</span>
106+
<h6 v-if="metric.id === 'activeUsers'" class="-ml-1 text-danger" title="新增用户">+{{ numberFormat(metric.series[1]?.total) }}</h6>
109107
</template>
110108
</div>
111109
</el-card>

‎ui/src/views/knowledge-detail/hit-test/index.vue‎

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
<script setup lang="ts">
2-
import { reactive, ref, useTemplateRef } from 'vue'
2+
import { computed, nextTick, reactive, ref, useTemplateRef } from 'vue'
33
import type { FormInstance } from 'element-plus'
44
import type { ResourceDetailPageProps } from '@/layout/ResourceDetailLayout.vue'
5-
import { KNOWLEDGE_SEARCH_MODE, KNOWLEDGE_TYPE } from '@/api/enums'
5+
import { DOCUMENT_RESOURCE_TYPE, KNOWLEDGE_SEARCH_MODE } from '@/api/enums'
66
import { KNOWLEDGE_SEARCH_MODE_OPTIONS } from '@/constants/knowledge'
7-
import type { KnowledgeHitTestPayload, KnowledgeHitTestResult } from '@/api/types'
7+
import type { KnowledgeHitTestPayload, KnowledgeHitTestResult, ParagraphItem } from '@/api/types'
88
import { useKnowledgeDetailContext } from '@/views/knowledge-detail/context'
9+
import ParagraphDialog from '@/views/document-detail/paragraph-dialog/ParagraphDialog.vue'
910
import emptyHitTestImage from '@/assets/empty/hit-test.png'
11+
import fileImageIcon from '@/assets/file-type/file-image-icon.svg'
1012
import HitTestInput from './components/HitTestInput.vue'
13+
import { getFileUrl } from '@/utils/common'
14+
import { getFileIconUrl } from '@/utils/icon'
1115
1216
defineOptions({ name: 'HitTestView' })
1317
defineProps<ResourceDetailPageProps>()
@@ -30,10 +34,31 @@ function changeSearchMode() {
3034
}
3135
3236
// 仅展示本次提交的查询快照,失败保留输入和检索参数供重试。
33-
const resultParagraphs = ref<KnowledgeHitTestResult[]>([])
37+
const hitTestResults = ref<KnowledgeHitTestResult[]>([])
38+
const resultParagraphs = computed(() =>
39+
hitTestResults.value.map((paragraph) => ({
40+
...paragraph,
41+
isImgaeDoc: Boolean(paragraph.document_image_id) && paragraph.document_resource_type === DOCUMENT_RESOURCE_TYPE.IMAGE,
42+
})),
43+
)
3444
const hasTestResult = ref(false)
3545
const submittedQuery = ref<Required<Pick<KnowledgeHitTestPayload, 'query_text' | 'image_list'>>>({ query_text: '', image_list: [] })
3646
47+
// 点击召回分段后挂载详情弹窗,保存正文时同步当前结果。
48+
const selectedParagraph = ref<KnowledgeHitTestResult>()
49+
const paragraphDialogRef = useTemplateRef<InstanceType<typeof ParagraphDialog>>('paragraphDialogRef')
50+
51+
async function handleOpenParagraph(paragraph: KnowledgeHitTestResult) {
52+
if (loading.value || selectedParagraph.value) return
53+
selectedParagraph.value = paragraph
54+
await nextTick()
55+
paragraphDialogRef.value?.open(paragraph)
56+
}
57+
58+
function handleParagraphSuccess(paragraph: ParagraphItem) {
59+
hitTestResults.value = hitTestResults.value.map((result) => (result.id === paragraph.id ? { ...result, ...paragraph } : result))
60+
}
61+
3762
function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text' | 'image_list'>>) {
3863
if (loading.value) return
3964
searchFormRef.value?.validate((valid) => {
@@ -47,7 +72,7 @@ function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text
4772
return resourceApi.knowledge
4873
.postKnowledgeHitTest(knowledgeId.value, payload)
4974
.then((paragraphs) => {
50-
resultParagraphs.value = paragraphs
75+
hitTestResults.value = paragraphs
5176
submittedQuery.value = query
5277
hasTestResult.value = true
5378
})
@@ -79,7 +104,7 @@ function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text
79104
<el-radio-group v-model="searchForm.search_mode" @change="changeSearchMode" class="space-y-2">
80105
<template v-for="searchMode in KNOWLEDGE_SEARCH_MODE_OPTIONS" :key="searchMode.value">
81106
<el-card shadow="never" class="w-full!" :class="{ 'border-primary!': searchForm.search_mode === searchMode.value }">
82-
<el-radio :value="searchMode.value" :disabled="loading" class="mk-card-radio">
107+
<el-radio :value="searchMode.value" class="mk-card-radio">
83108
<h6>{{ searchMode.label }}</h6>
84109
<span class="block text-sm text-N600">{{ searchMode.description }}</span>
85110
</el-radio>
@@ -128,7 +153,6 @@ function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text
128153
:file-count-limit="knowledge?.file_count_limit"
129154
:file-size-limit="knowledge?.file_size_limit"
130155
:loading="loading"
131-
:allow-images="knowledge?.type === KNOWLEDGE_TYPE.BASE"
132156
@submit="testKnowledge"
133157
/>
134158
</el-scrollbar>
@@ -140,37 +164,47 @@ function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text
140164
</div>
141165
<template v-else>
142166
<section class="mb-6">
143-
<h4 class="mb-4">本次查询</h4>
167+
<h4 class="mb-2">本次查询</h4>
144168
<div v-if="submittedQuery.image_list.length" class="flex flex-wrap gap-2">
145169
<template v-for="image in submittedQuery.image_list" :key="image.file_id">
146170
<!-- 预览本次查询图片 -->
147171
<el-image :src="image.url" :alt="image.name" fit="cover" class="size-12" />
148172
</template>
149173
</div>
150-
<p v-if="submittedQuery.query_text" class="mt-3 whitespace-pre-wrap break-words">{{ submittedQuery.query_text }}</p>
174+
<p v-if="submittedQuery.query_text" class="mt-2 whitespace-pre-wrap break-words">{{ submittedQuery.query_text }}</p>
151175
</section>
152-
<h4 class="mb-4">查询结果</h4>
176+
<h4 class="mb-3">查询结果</h4>
153177
<div v-if="!resultParagraphs.length" class="flex-center flex-1">
154178
<MkEmpty type="search" description="未召回相关知识,请调整检索参数内容" />
155179
</div>
156180
<div v-else class="space-y-3">
157181
<template v-for="(paragraph, paragraphIndex) in resultParagraphs" :key="paragraph.id">
158-
<MkSourceCard :title="paragraph.title || '-'">
159-
<template #icon>
160-
<span>
161-
<span>{{ `#${paragraphIndex + 1}` }}</span>
182+
<!-- 查看召回分段详情 -->
183+
<MkSourceCard :title="paragraph.title || '-'" class="cursor-pointer" @click="handleOpenParagraph(paragraph)">
184+
<template #title="{ title }">
185+
<span class="flex-align-center min-w-0 gap-2">
186+
<h6 class="shrink-0">{{ `#${paragraphIndex + 1}` }}</h6>
162187
<el-divider direction="vertical" />
188+
<h6 class="min-w-0 truncate" :title="title">{{ title }}</h6>
163189
</span>
164190
</template>
165191
<template #tag>
166-
<span class="shrink-0 text-primary">{{ paragraph.similarity.toFixed(3) }}</span>
192+
<el-tag size="small" type="info">{{ paragraph.isImgaeDoc ? '图片向量' : '文本向量' }}</el-tag>
193+
<h6 class="shrink-0 text-primary">{{ paragraph.similarity.toFixed(3) }}</h6>
167194
</template>
195+
<!-- 图片随卡片点击打开分段详情 -->
196+
<el-image
197+
v-if="paragraph.isImgaeDoc && paragraph.document_image_id"
198+
:src="getFileUrl(paragraph.document_image_id)"
199+
:alt="paragraph.title || ''"
200+
fit="contain"
201+
class="mb-2"
202+
/>
203+
<MdPreview :model-value="paragraph.content" no-img-zoom-in />
168204

169-
<el-scrollbar max-height="280">
170-
<MdPreview :model-value="paragraph.content" />
171-
</el-scrollbar>
172205
<template #footer>
173-
<div class="text-sm text-N600">
206+
<div class="flex-align-center min-w-0 gap-2 text-sm text-N600">
207+
<img :src="paragraph.isImgaeDoc ? fileImageIcon : getFileIconUrl(paragraph.document_name)" alt="" class="size-4 shrink-0" />
174208
<span class="truncate" :title="paragraph.document_name">{{ paragraph.document_name }}</span>
175209
</div>
176210
</template>
@@ -181,4 +215,14 @@ function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text
181215
</el-scrollbar>
182216
</section>
183217
</div>
218+
<ParagraphDialog
219+
v-if="selectedParagraph"
220+
ref="paragraphDialogRef"
221+
title="分段详情"
222+
:knowledge-id="selectedParagraph.knowledge_id"
223+
:document-id="selectedParagraph.document_id"
224+
:source-file-id="selectedParagraph.document_image_id || undefined"
225+
@success="handleParagraphSuccess"
226+
@closed="selectedParagraph = undefined"
227+
/>
184228
</template>

‎ui/src/views/model/create-model/SelectProviderDrawer.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ defineExpose({ open })
8585
<el-card shadow="hover" @click="handleProviderSelect(provider)">
8686
<div class="flex">
8787
<span class="h-6 w-6 shrink-0" :innerHTML="provider.icon" />
88-
<span class="ml-3 font-medium">{{ provider.name }}</span>
88+
<h6 class="ml-3">{{ provider.name }}</h6>
8989
</div>
9090
</el-card>
9191
</template>

‎ui/src/views/system/chat-management/portal-setting/components/PortalPreview.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ defineProps<{ name: string; logo: string | null }>()
1212
</div>
1313
<div class="flex-align-center gap-2 mk-white-card-sm text-primary">
1414
<MkIcon name="icon_card_filled" :size="18" />
15-
<span class="font-medium">全部智能体</span>
15+
<h6>全部智能体</h6>
1616
</div>
1717
<div class="flex-align-center gap-2 px-2 py-3">
1818
<MkIcon name="icon_new-chat_outlined" :size="18" />

0 commit comments

Comments
 (0)