Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

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

### System 共享知识库

Expand Down
4 changes: 3 additions & 1 deletion ui/src/api/types/knowledge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type LogicFlow from '@logicflow/core'
import { KNOWLEDGE_SEARCH_MODE, KNOWLEDGE_SYNC_TYPE, KNOWLEDGE_TYPE } from '@/api/enums'
import type { DefaultModelSettingPayload } from './model'
import type { DocumentTaskState } from './document'
import type { DocumentResourceType, DocumentTaskState } from './document'
import type { ParagraphItem } from './paragraph'

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

/** 召回测试返回的分段与命中得分。 */
export interface KnowledgeHitTestResult extends ParagraphItem {
document_image_id?: string | null
document_resource_type?: DocumentResourceType | null
knowledge_id: string
knowledge_name: string
document_name: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const conversationTime = (conversation: Conversation) => relativeTimeFormat(conv
<span>新建对话</span>
</MkListItem>
<div class="flex-between pl-2 mt-4">
<div class="text-N600 font-medium">历史对话</div>
<h6 class="text-N600">历史对话</h6>

<!-- // TODO: 全部清空对话 -->
<el-button text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const handleDeleteConversation = (group: PortalApplicationGroup, conversation: C
<span>新建对话</span>
</MkListItem>
<div class="flex-between pl-2 mt-4">
<div class="text-N600 font-medium">历史对话</div>
<h6 class="text-N600 font-medium">历史对话</h6>

<!-- // TODO: 全部清空对话 -->
<el-button text>
Expand Down
6 changes: 3 additions & 3 deletions ui/src/layout/sidebar/ResourceDetailMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ watch(
<template #label>
<span class="flex-align-center min-w-0 flex-1 gap-2">
<MkIcon v-if="menuItem.icon" :name="menuItem.icon" :size="18" />
<span class="min-w-0 flex-1 truncate" :title="menuItem.label">{{ menuItem.label }}</span>
<span class="min-w-0 flex-1 truncate">{{ menuItem.label }}</span>
</span>
</template>
<div class="space-y-1 pt-1">
<template v-for="child in menuItem.children" :key="child.name">
<!-- 切换二级菜单页面 -->
<MkListItem :active="child.name === props.activeName" @click="emit('select', child)">
<span class="ml-6 min-w-0 flex-1 truncate" :title="child.label">{{ child.label }}</span>
<span class="ml-6 min-w-0 flex-1 truncate">{{ child.label }}</span>
</MkListItem>
</template>
</div>
Expand All @@ -51,7 +51,7 @@ watch(
<MkListItem v-else :active="menuItem.name === props.activeName" @click="emit('select', menuItem)">
<template #default="{ active }">
<MkIcon v-if="menuItem.icon" class="mr-2" :name="active ? (menuItem.activeIcon ?? menuItem.icon) : menuItem.icon" :size="18" />
<span class="min-w-0 flex-1 truncate" :title="menuItem.label">{{ menuItem.label }}</span>
<span class="min-w-0 flex-1 truncate">{{ menuItem.label }}</span>
</template>
</MkListItem>
</template>
Expand Down
1 change: 1 addition & 0 deletions ui/src/views/VIEW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ System 资源管理或 System 共享 Knowledge API 的 `postKnowledgeHitTest`;
输入区通过 `ImageUploadCard` 展示图片进度、失败重试和删除,不提供点击预览。上传中或存在失败图片时禁测试,
失败图片可直接重新上传或移除。移除与卸载释放本地预览并取消未完成上传。
测试期间冻结参数与输入;结果使用 `MkSourceCard` 展示分段、得分、文档名称及命中图片,Markdown 只读。
点击结果卡片按需挂载 `ParagraphDialog`,按命中分段的知识库和文档 ID 加载详情,关闭动画后卸载;保存正文后同步当前结果,保留检索得分。
“本次查询”展示成功请求的文本与图片快照,图片位于文本上方,缩略图自动换行并支持预览,不使用横向滚动。
初始与无命中分别展示空态;请求失败由请求层提示,保留输入与已有结果供重试。切换知识库或资源范围通过路由 key 重建召回测试页,隔离查询与结果状态。

Expand Down
4 changes: 1 addition & 3 deletions ui/src/views/home/components/HomeStatistics.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,7 @@ watch(() => [props.workspaceId, props.applicationId], loadMonitoring, { immediat
<h2>
{{ metric.id === 'tokens' ? formatTokenNumber(metric.series[0]?.total) : numberFormat(metric.series[0]?.total) }}
</h2>
<span v-if="metric.id === 'activeUsers'" class="-ml-1 font-medium text-danger" title="新增用户">
+{{ numberFormat(metric.series[1]?.total) }}
</span>
<h6 v-if="metric.id === 'activeUsers'" class="-ml-1 text-danger" title="新增用户">+{{ numberFormat(metric.series[1]?.total) }}</h6>
</template>
</div>
</el-card>
Expand Down
82 changes: 63 additions & 19 deletions ui/src/views/knowledge-detail/hit-test/index.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
<script setup lang="ts">
import { reactive, ref, useTemplateRef } from 'vue'
import { computed, nextTick, reactive, ref, useTemplateRef } from 'vue'
import type { FormInstance } from 'element-plus'
import type { ResourceDetailPageProps } from '@/layout/ResourceDetailLayout.vue'
import { KNOWLEDGE_SEARCH_MODE, KNOWLEDGE_TYPE } from '@/api/enums'
import { DOCUMENT_RESOURCE_TYPE, KNOWLEDGE_SEARCH_MODE } from '@/api/enums'
import { KNOWLEDGE_SEARCH_MODE_OPTIONS } from '@/constants/knowledge'
import type { KnowledgeHitTestPayload, KnowledgeHitTestResult } from '@/api/types'
import type { KnowledgeHitTestPayload, KnowledgeHitTestResult, ParagraphItem } from '@/api/types'
import { useKnowledgeDetailContext } from '@/views/knowledge-detail/context'
import ParagraphDialog from '@/views/document-detail/paragraph-dialog/ParagraphDialog.vue'
import emptyHitTestImage from '@/assets/empty/hit-test.png'
import fileImageIcon from '@/assets/file-type/file-image-icon.svg'
import HitTestInput from './components/HitTestInput.vue'
import { getFileUrl } from '@/utils/common'
import { getFileIconUrl } from '@/utils/icon'

defineOptions({ name: 'HitTestView' })
defineProps<ResourceDetailPageProps>()
Expand All @@ -30,10 +34,31 @@ function changeSearchMode() {
}

// 仅展示本次提交的查询快照,失败保留输入和检索参数供重试。
const resultParagraphs = ref<KnowledgeHitTestResult[]>([])
const hitTestResults = ref<KnowledgeHitTestResult[]>([])
const resultParagraphs = computed(() =>
hitTestResults.value.map((paragraph) => ({
...paragraph,
isImgaeDoc: Boolean(paragraph.document_image_id) && paragraph.document_resource_type === DOCUMENT_RESOURCE_TYPE.IMAGE,
})),
)
const hasTestResult = ref(false)
const submittedQuery = ref<Required<Pick<KnowledgeHitTestPayload, 'query_text' | 'image_list'>>>({ query_text: '', image_list: [] })

// 点击召回分段后挂载详情弹窗,保存正文时同步当前结果。
const selectedParagraph = ref<KnowledgeHitTestResult>()
const paragraphDialogRef = useTemplateRef<InstanceType<typeof ParagraphDialog>>('paragraphDialogRef')

async function handleOpenParagraph(paragraph: KnowledgeHitTestResult) {
if (loading.value || selectedParagraph.value) return
selectedParagraph.value = paragraph
await nextTick()
paragraphDialogRef.value?.open(paragraph)
}

function handleParagraphSuccess(paragraph: ParagraphItem) {
hitTestResults.value = hitTestResults.value.map((result) => (result.id === paragraph.id ? { ...result, ...paragraph } : result))
}

function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text' | 'image_list'>>) {
if (loading.value) return
searchFormRef.value?.validate((valid) => {
Expand All @@ -47,7 +72,7 @@ function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text
return resourceApi.knowledge
.postKnowledgeHitTest(knowledgeId.value, payload)
.then((paragraphs) => {
resultParagraphs.value = paragraphs
hitTestResults.value = paragraphs
submittedQuery.value = query
hasTestResult.value = true
})
Expand Down Expand Up @@ -79,7 +104,7 @@ function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text
<el-radio-group v-model="searchForm.search_mode" @change="changeSearchMode" class="space-y-2">
<template v-for="searchMode in KNOWLEDGE_SEARCH_MODE_OPTIONS" :key="searchMode.value">
<el-card shadow="never" class="w-full!" :class="{ 'border-primary!': searchForm.search_mode === searchMode.value }">
<el-radio :value="searchMode.value" :disabled="loading" class="mk-card-radio">
<el-radio :value="searchMode.value" class="mk-card-radio">
<h6>{{ searchMode.label }}</h6>
<span class="block text-sm text-N600">{{ searchMode.description }}</span>
</el-radio>
Expand Down Expand Up @@ -128,7 +153,6 @@ function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text
:file-count-limit="knowledge?.file_count_limit"
:file-size-limit="knowledge?.file_size_limit"
:loading="loading"
:allow-images="knowledge?.type === KNOWLEDGE_TYPE.BASE"
@submit="testKnowledge"
/>
</el-scrollbar>
Expand All @@ -140,37 +164,47 @@ function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text
</div>
<template v-else>
<section class="mb-6">
<h4 class="mb-4">本次查询</h4>
<h4 class="mb-2">本次查询</h4>
<div v-if="submittedQuery.image_list.length" class="flex flex-wrap gap-2">
<template v-for="image in submittedQuery.image_list" :key="image.file_id">
<!-- 预览本次查询图片 -->
<el-image :src="image.url" :alt="image.name" fit="cover" class="size-12" />
</template>
</div>
<p v-if="submittedQuery.query_text" class="mt-3 whitespace-pre-wrap break-words">{{ submittedQuery.query_text }}</p>
<p v-if="submittedQuery.query_text" class="mt-2 whitespace-pre-wrap break-words">{{ submittedQuery.query_text }}</p>
</section>
<h4 class="mb-4">查询结果</h4>
<h4 class="mb-3">查询结果</h4>
<div v-if="!resultParagraphs.length" class="flex-center flex-1">
<MkEmpty type="search" description="未召回相关知识,请调整检索参数内容" />
</div>
<div v-else class="space-y-3">
<template v-for="(paragraph, paragraphIndex) in resultParagraphs" :key="paragraph.id">
<MkSourceCard :title="paragraph.title || '-'">
<template #icon>
<span>
<span>{{ `#${paragraphIndex + 1}` }}</span>
<!-- 查看召回分段详情 -->
<MkSourceCard :title="paragraph.title || '-'" class="cursor-pointer" @click="handleOpenParagraph(paragraph)">
<template #title="{ title }">
<span class="flex-align-center min-w-0 gap-2">
<h6 class="shrink-0">{{ `#${paragraphIndex + 1}` }}</h6>
<el-divider direction="vertical" />
<h6 class="min-w-0 truncate" :title="title">{{ title }}</h6>
</span>
</template>
<template #tag>
<span class="shrink-0 text-primary">{{ paragraph.similarity.toFixed(3) }}</span>
<el-tag size="small" type="info">{{ paragraph.isImgaeDoc ? '图片向量' : '文本向量' }}</el-tag>
<h6 class="shrink-0 text-primary">{{ paragraph.similarity.toFixed(3) }}</h6>
</template>
<!-- 图片随卡片点击打开分段详情 -->
<el-image
v-if="paragraph.isImgaeDoc && paragraph.document_image_id"
:src="getFileUrl(paragraph.document_image_id)"
:alt="paragraph.title || ''"
fit="contain"
class="mb-2"
/>
<MdPreview :model-value="paragraph.content" no-img-zoom-in />

<el-scrollbar max-height="280">
<MdPreview :model-value="paragraph.content" />
</el-scrollbar>
<template #footer>
<div class="text-sm text-N600">
<div class="flex-align-center min-w-0 gap-2 text-sm text-N600">
<img :src="paragraph.isImgaeDoc ? fileImageIcon : getFileIconUrl(paragraph.document_name)" alt="" class="size-4 shrink-0" />
<span class="truncate" :title="paragraph.document_name">{{ paragraph.document_name }}</span>
</div>
</template>
Expand All @@ -181,4 +215,14 @@ function testKnowledge(query: Required<Pick<KnowledgeHitTestPayload, 'query_text
</el-scrollbar>
</section>
</div>
<ParagraphDialog
v-if="selectedParagraph"
ref="paragraphDialogRef"
title="分段详情"
:knowledge-id="selectedParagraph.knowledge_id"
:document-id="selectedParagraph.document_id"
:source-file-id="selectedParagraph.document_image_id || undefined"
@success="handleParagraphSuccess"
@closed="selectedParagraph = undefined"
/>
</template>
2 changes: 1 addition & 1 deletion ui/src/views/model/create-model/SelectProviderDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ defineExpose({ open })
<el-card shadow="hover" @click="handleProviderSelect(provider)">
<div class="flex">
<span class="h-6 w-6 shrink-0" :innerHTML="provider.icon" />
<span class="ml-3 font-medium">{{ provider.name }}</span>
<h6 class="ml-3">{{ provider.name }}</h6>
</div>
</el-card>
</template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defineProps<{ name: string; logo: string | null }>()
</div>
<div class="flex-align-center gap-2 mk-white-card-sm text-primary">
<MkIcon name="icon_card_filled" :size="18" />
<span class="font-medium">全部智能体</span>
<h6>全部智能体</h6>
</div>
<div class="flex-align-center gap-2 px-2 py-3">
<MkIcon name="icon_new-chat_outlined" :size="18" />
Expand Down
Loading