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
9 changes: 7 additions & 2 deletions ui/src/views/VIEW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -697,11 +697,14 @@ Workspace API 内部通过 `getWorkspaceId()` 读取当前路由工作空间。
选中后通过 `footer-batch-actions` 展示批量操作。右侧固定操作列提供启停、向量化、分词索引和更多菜单,
运行或排队中的任务切换为取消入口。Web、飞书提供同步,普通与工作流文档提供原文下载和替换。
单项与批量操作共用页面请求状态,删除和同步先确认,成功后清空选择并刷新,导出和下载保留选择。
文档页及其 Action 的知识库类型统一读取响应式路由参数 `route.params.type`,使用
`KNOWLEDGE_TYPE_KEY` 判断,不从详情或文档数据读取类型,也不通过 `knowledgeType` Prop 传递。
Web 来源地址与选择器仅在单项设置中编辑,批量设置不展示这两个字段。
文档操作统一放在 `document/action-dropdown/`,通过该目录 `index.ts` 导出,页面显式组合各 Action。
文档状态展示、筛选配置和任务运行状态判断统一维护在 `document/status.ts`。
文档任务是否排队或执行中通过其中的 `isDocumentTaskRunning(document, taskType)` 判断,
页面及 Action 不重复解析状态字符串;文档或任务状态缺失时返回 `false`。
`EmbeddingDocumentAction` 负责单项向量化与取消向量化,`BatchEmbeddingAction` 负责批量向量化按钮,`GenerateDocumentQuestionsAction` 负责文档生成问题入口和提交,`SettingDocumentAction` 负责召回及来源设置,
`EmbeddingDocumentAction` 负责单项向量化与取消向量化,`BatchEmbeddingAction` 负责批量向量化按钮,`GenerateQuestionsAction` 负责单项生成问题与取消生成,`BatchGenerateQuestionsAction` 负责批量生成问题,`SettingDocumentAction` 负责召回及来源设置,
`MigrateDocumentAction` 选择目标知识库,`DocumentTagsAction` 添加已有标签和移除文档标签关联;
生成问题复用公共配置弹窗,其余 Action 按下文维护各自专属弹窗。组件接收完整 Document API、知识库 ID、文档 ID 或文档数组及禁用状态,
点击时快照本次操作对象,内部管理弹窗和请求,成功后通过 `refresh` 通知页面刷新,失败保留输入。
Expand All @@ -711,7 +714,9 @@ Workspace API 内部通过 `getWorkspaceId()` 读取当前路由工作空间。
向量化相关文件集中在 `document/action-dropdown/embedding/`,包含两个 Action 和共用的 `DocumentEmbeddingDialog.vue`;
Action 仍通过上层 `action-dropdown/index.ts` 导出,弹窗仅在目录内部使用,管理分段范围与确认事件,
请求和成功关闭由各自 Action 负责;打开时快照文档目标,失败保留范围选择。
生成问题通过 `batch` 切换批量按钮,通过 `display` 区分行内菜单与图标按钮。
生成问题相关 Action 集中在 `document/action-dropdown/generate-questions/`,通过上层 `index.ts` 导出。
`GenerateQuestionsAction` 接收单个 `document`,内部判断任务状态并处理生成与单项取消,通过 `display` 区分菜单与图标按钮。
`BatchGenerateQuestionsAction` 接收 `documentIds`,使用独立的批量生成按钮;批量取消继续使用 `BatchCancelTaskAction`。
单项与批量复用公共 `GenerateQuestionsDialog`,Action 打开时保存知识库、文档 ID 快照,
提交同一文档批量接口并通过 `refresh` 刷新状态;通过 `v-model:loading` 复用页面操作状态。
公共弹窗按需挂载、关闭后卸载,不负责选择提交接口。当前尚无段落页,后续段落入口复用公共
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
<script setup lang="ts">
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import type { FormInstance } from 'element-plus'
import { DOCUMENT_HIT_HANDLING, KNOWLEDGE_TYPE } from '@/api/enums'
import { DOCUMENT_HIT_HANDLING } from '@/api/enums'
import type { DocumentItem, DocumentSettingPayload } from '@/api/types'
import type DocumentApi from '@/api/admin/workspace/knowledge/document'
import { KNOWLEDGE_TYPE_KEY } from '@/constants/knowledge'
import { DOCUMENT_HIT_HANDLING_LABELS } from '@/constants/document'
import { MsgSuccess } from '@/utils/message'

defineOptions({ name: 'SettingDocumentAction' })
const props = defineProps<{ api: typeof DocumentApi; knowledgeId: string; documents: DocumentItem[]; batch?: boolean; disabled?: boolean }>()
const emit = defineEmits<{ refresh: [] }>()
const route = useRoute()
const knowledgeType = computed(() => route.params.type)
const visible = ref(false)
const loading = ref(false)
const formRef = ref<FormInstance>()
Expand Down Expand Up @@ -50,7 +54,7 @@ async function handleSubmit() {
let request: Promise<unknown>
if (document.value) {
data.meta = { ...document.value.meta, allow_download: form.value.allow_download }
if (document.value.type === KNOWLEDGE_TYPE.WEB) {
if (knowledgeType.value === KNOWLEDGE_TYPE_KEY.WEB) {
data.meta.source_url = form.value.source_url.trim()
data.meta.selector = form.value.selector
}
Expand Down Expand Up @@ -84,7 +88,7 @@ function handleClosed() {
<MkAction label="设置" icon="icon_setting" :disabled="disabled || loading || !documents.length" @click="handleOpenDialog" />
<MkDialog v-model="visible" title="文档设置" :show-close="!loading" @closed="handleClosed">
<el-form ref="formRef" :model="form" label-position="top" :disabled="loading" @submit.prevent>
<template v-if="document?.type === KNOWLEDGE_TYPE.WEB">
<template v-if="document && knowledgeType === KNOWLEDGE_TYPE_KEY.WEB">
<el-form-item label="文档地址" prop="source_url" :rules="[{ required: true, whitespace: true, message: '请输入文档地址', trigger: 'blur' }]">
<el-input v-model="form.source_url" />
</el-form-item>
Expand All @@ -109,7 +113,7 @@ function handleClosed() {
align="left"
/>
</el-form-item>
<el-checkbox v-model="form.allow_download">{{ document?.type === KNOWLEDGE_TYPE.WEB ? '允许预览' : '允许下载' }}</el-checkbox>
<el-checkbox v-model="form.allow_download">{{ knowledgeType === KNOWLEDGE_TYPE_KEY.WEB ? '允许预览' : '允许下载' }}</el-checkbox>
</el-form>
<template #footer>
<!-- 取消文档设置 -->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import type DocumentApi from '@/api/admin/workspace/knowledge/document'
import { KNOWLEDGE_TYPE } from '@/api/enums'
import type { DocumentItem, KnowledgeType } from '@/api/types'
import { KNOWLEDGE_TYPE_KEY } from '@/constants/knowledge'
import type { DocumentItem } from '@/api/types'
import { MsgConfirm, MsgInfo, MsgSuccess } from '@/utils/message'

defineOptions({ name: 'SyncDocumentAction' })
const props = defineProps<{
api: typeof DocumentApi
knowledgeId: string
documents: DocumentItem[]
knowledgeType?: KnowledgeType
batch?: boolean
}>()
const route = useRoute()
const knowledgeType = computed(() => route.params.type)
const loading = defineModel<boolean>('loading', { default: false })
const emit = defineEmits<{ refresh: [] }>()

// 单项 Web 同步校验来源地址,确认覆盖后按知识库类型调用同步接口。
function handleSync() {
if (!props.documents.length || loading.value) return
const document = props.batch ? undefined : props.documents[0]
if (document?.type === KNOWLEDGE_TYPE.WEB && !document.meta?.source_url) {
if (document && knowledgeType.value === KNOWLEDGE_TYPE_KEY.WEB && !document.meta?.source_url) {
MsgInfo('文档没有来源地址,请先在设置中填写文档地址')
return
}
Expand All @@ -29,7 +32,7 @@ function handleSync() {
if (loading.value) return
loading.value = true
const request =
props.knowledgeType === KNOWLEDGE_TYPE.LARK
knowledgeType.value === KNOWLEDGE_TYPE_KEY.LARK
? props.api.putSyncLarkDocuments(props.knowledgeId, ids)
: props.api.putSyncDocuments(props.knowledgeId, ids)
return request
Expand All @@ -47,11 +50,5 @@ function handleSync() {

<template>
<!-- 同步单个或选中文档 -->
<MkAction
v-if="knowledgeType === KNOWLEDGE_TYPE.WEB || knowledgeType === KNOWLEDGE_TYPE.LARK"
:label="batch ? '同步文档' : '同步'"
icon="icon_refresh_outlined"
:disabled="loading || !documents.length"
@click="handleSync"
/>
<MkAction :label="batch ? '同步文档' : '同步'" icon="icon_refresh_outlined" @click="handleSync" />
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,17 @@ import type DocumentApi from '@/api/admin/workspace/knowledge/document'
import GenerateQuestionsDialog from '@/components/business/generate-questions/GenerateQuestionsDialog.vue'
import { MsgSuccess } from '@/utils/message'

defineOptions({ name: 'GenerateDocumentQuestionsAction' })
defineOptions({ name: 'BatchGenerateQuestionsAction' })
const props = defineProps<{
api: typeof DocumentApi
knowledgeId: string
documentIds: string[]
batch?: boolean
display?: 'menu' | 'button'
disabled?: boolean
}>()
const emit = defineEmits<{ refresh: [] }>()
const loading = defineModel<boolean>('loading', { default: false })

/* 打开时固定目标文档,单项与批量共用文档生成接口。 */
/* 打开时固定本次批量生成的目标文档。 */
const dialogMounted = ref(false)
const dialogRef = useTemplateRef<InstanceType<typeof GenerateQuestionsDialog>>('dialogRef')
const targetDocumentIds = ref<string[]>([])
Expand Down Expand Up @@ -61,15 +59,6 @@ function handleClosed() {

<template>
<!-- 批量生成问题 -->
<el-button v-if="batch" :disabled="disabled || loading || !documentIds.length" @click="handleOpenDialog">生成问题</el-button>
<!-- 单个文档生成问题 -->
<MkAction
v-else
:display="display ?? 'menu'"
label="生成问题"
icon="icon_new-chat_outlined"
:disabled="disabled || loading || !documentIds.length"
@click="handleOpenDialog"
/>
<el-button :disabled="disabled || loading || !documentIds.length" @click="handleOpenDialog">生成问题</el-button>
<GenerateQuestionsDialog v-if="dialogMounted" ref="dialogRef" :loading="loading" @submit="handleSubmit" @closed="handleClosed" />
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<script setup lang="ts">
import { computed, nextTick, ref, useTemplateRef } from 'vue'
import { DOCUMENT_TASK_TYPE } from '@/api/enums'
import type { DocumentItem, DocumentTaskState, RelatedQuestionsConfig } from '@/api/types'
import type DocumentApi from '@/api/admin/workspace/knowledge/document'
import GenerateQuestionsDialog from '@/components/business/generate-questions/GenerateQuestionsDialog.vue'
import { MsgSuccess } from '@/utils/message'
import { isDocumentTaskRunning } from '../../status'

defineOptions({ name: 'GenerateQuestionsAction' })
const props = defineProps<{
api: typeof DocumentApi
knowledgeId: string
document: DocumentItem
display?: 'menu' | 'button'
disabled?: boolean
}>()
const emit = defineEmits<{ refresh: [] }>()
const loading = defineModel<boolean>('loading', { default: false })

// 单个文档在排队或执行时展示取消入口,其余状态打开生成配置。
const running = computed(() => isDocumentTaskRunning(props.document, DOCUMENT_TASK_TYPE.GENERATE_PROBLEM))

/* 打开时固定本次生成的目标文档。 */
const dialogMounted = ref(false)
const dialogRef = useTemplateRef<InstanceType<typeof GenerateQuestionsDialog>>('dialogRef')
const targetDocumentId = ref('')
const targetKnowledgeId = ref('')

function handleOpenDialog() {
if (props.disabled || loading.value || dialogMounted.value || !props.document.id) return
targetDocumentId.value = props.document.id
targetKnowledgeId.value = props.knowledgeId
dialogMounted.value = true
return nextTick(() => dialogRef.value?.open())
}

// 取消当前文档的生成问题任务。
function handleCancelQuestions() {
if (props.disabled || loading.value) return
loading.value = true
return props.api
.putCancelTask(props.knowledgeId, props.document.id, DOCUMENT_TASK_TYPE.GENERATE_PROBLEM)
.then(() => {
MsgSuccess('操作成功')
emit('refresh')
})
.catch(() => {})
.finally(() => {
loading.value = false
})
}

/* Action 负责提交和成功后的页面刷新,失败保留配置。 */
function handleSubmit(config: RelatedQuestionsConfig, stateList: DocumentTaskState[]) {
if (props.disabled || loading.value) return
loading.value = true
return props.api
.putGenerateDocumentQuestions(targetKnowledgeId.value, {
...config,
document_id_list: [targetDocumentId.value],
state_list: stateList,
})
.then(() => {
MsgSuccess('任务已提交')
dialogRef.value?.close()
emit('refresh')
})
.catch(() => {})
.finally(() => {
loading.value = false
})
}

function handleClosed() {
dialogMounted.value = false
targetDocumentId.value = ''
targetKnowledgeId.value = ''
}
</script>

<template>
<!-- 单个文档生成或取消生成问题 -->
<MkAction
:display="display ?? 'menu'"
:label="running ? '取消生成问题' : '生成问题'"
:icon="running ? undefined : 'icon_new-chat_outlined'"
:disabled="disabled || loading || !document.id"
@click="running ? handleCancelQuestions() : handleOpenDialog()"
/>
<GenerateQuestionsDialog v-if="dialogMounted" ref="dialogRef" :loading="loading" @submit="handleSubmit" @closed="handleClosed" />
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ export { default as BatchCancelTaskAction } from './BatchCancelTaskAction.vue'
export { default as DeleteDocumentAction } from './DeleteDocumentAction.vue'
export { default as DocumentTagsAction } from './DocumentTagsAction.vue'
export { default as EmbeddingDocumentAction } from './embedding/EmbeddingDocumentAction.vue'
export { default as GenerateDocumentQuestionsAction } from './GenerateDocumentQuestionsAction.vue'
export { default as GenerateQuestionsAction } from './generate-questions/GenerateQuestionsAction.vue'
export { default as BatchGenerateQuestionsAction } from './generate-questions/BatchGenerateQuestionsAction.vue'
export { default as DownloadDocumentAction } from './DownloadDocumentAction.vue'
export { default as ExportDocumentAction } from './ExportDocumentAction.vue'
export { default as MigrateDocumentAction } from './MigrateDocumentAction.vue'
Expand Down
50 changes: 18 additions & 32 deletions ui/src/views/knowledge-detail/document/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import KnowledgeApi from '@/api/admin/workspace/knowledge/knowledge'
import SharedKnowledgeApi from '@/api/admin/workspace/shared/knowledge/knowledge'
import CommonApi from '@/api/admin/workspace/common'
import CommonSystemApi from '@/api/admin/system/common'
import { DOCUMENT_TASK_TYPE, KNOWLEDGE_TYPE } from '@/api/enums'
import { DOCUMENT_TASK_TYPE } from '@/api/enums'
import { KNOWLEDGE_TYPE_KEY } from '@/constants/knowledge'
import { DOCUMENT_HIT_HANDLING_LABELS } from '@/constants/document'
import { DOCUMENT_STATUS_FILTER_OPTIONS, isDocumentTaskRunning } from './status'
import { DOCUMENT_STATUS_FILTER_OPTIONS } from './status'
import type { Dict, DocumentHitHandling, DocumentItem, OptionItem } from '@/api/types'
import { datetimeFormat } from '@/utils/time'
import { isWorkspaceSharedResource } from '@/utils/resource-context'
Expand All @@ -26,7 +27,8 @@ import {
DeleteDocumentAction,
DocumentTagsAction,
EmbeddingDocumentAction,
GenerateDocumentQuestionsAction,
GenerateQuestionsAction,
BatchGenerateQuestionsAction,
DownloadDocumentAction,
ExportDocumentAction,
MigrateDocumentAction,
Expand All @@ -36,18 +38,17 @@ import {
TokenizeDocumentAction,
BatchTokenizeAction,
} from './action-dropdown'
import { useKnowledgeDetailContext } from '../context'

defineOptions({ name: 'DocumentListView' })
defineProps<ResourceDetailPageProps>()
defineExpose({ customHeader: true })

const route = useRoute()
const knowledgeId = computed(() => String(route.params.knowledgeId ?? ''))
const { knowledge } = useKnowledgeDetailContext()
const knowledgeType = computed(() => route.params.type)

/* 快速创建 */
const showQuickCreate = computed(() => knowledge.value?.type === KNOWLEDGE_TYPE.BASE && !isWorkspaceSharedResource())
const showQuickCreate = computed(() => knowledgeType.value === KNOWLEDGE_TYPE_KEY.BASE && !isWorkspaceSharedResource())
const quickCreateRef = ref<InstanceType<typeof MkQuickCreate>>()

function handleCreateDocument(name: string) {
Expand Down Expand Up @@ -382,42 +383,29 @@ onBeforeUnmount(() => {
@refresh="refreshAfterOperation"
/>
<!-- 更多文档操作 -->
<MkTableMoreDropdown persistent>
<!-- 文档设置 -->
<SettingDocumentAction
:api="DocumentApi"
:knowledge-id="knowledgeId"
:documents="[row]"
:disabled="operationLoading"
@refresh="refreshAfterOperation"
/>
<MkTableMoreDropdown>
<!-- 同步文档 -->
<SyncDocumentAction
v-if="knowledgeType === KNOWLEDGE_TYPE_KEY.WEB || knowledgeType === KNOWLEDGE_TYPE_KEY.LARK"
:api="DocumentApi"
:knowledge-id="knowledgeId"
v-model:loading="operationLoading"
:documents="[row]"
:knowledge-type="knowledge?.type"
@refresh="refreshAfterOperation"
/>
<!-- 取消生成问题 -->
<BatchCancelTaskAction
<!-- 生成或取消生成问题 -->
<GenerateQuestionsAction
:api="DocumentApi"
:knowledge-id="knowledgeId"
v-model:loading="operationLoading"
:document-ids="[row.id]"
:task-type="DOCUMENT_TASK_TYPE.GENERATE_PROBLEM"
label="取消生成问题"
v-if="isDocumentTaskRunning(row, DOCUMENT_TASK_TYPE.GENERATE_PROBLEM)"
:knowledge-id="knowledgeId"
:document="row"
@refresh="refreshAfterOperation"
/>
<!-- 生成问题 -->
<GenerateDocumentQuestionsAction
<!-- 文档设置 -->
<SettingDocumentAction
:api="DocumentApi"
v-model:loading="operationLoading"
v-else
:knowledge-id="knowledgeId"
:document-ids="[row.id]"
:documents="[row]"
:disabled="operationLoading"
@refresh="refreshAfterOperation"
/>
Expand Down Expand Up @@ -454,7 +442,7 @@ onBeforeUnmount(() => {
:document-ids="[row.id]"
format="zip"
/>
<template v-if="knowledge?.type === KNOWLEDGE_TYPE.BASE || knowledge?.type === KNOWLEDGE_TYPE.WORKFLOW">
<template v-if="knowledgeType === KNOWLEDGE_TYPE_KEY.BASE || knowledgeType === KNOWLEDGE_TYPE_KEY.WORKFLOW">
<!-- 下载原文档 -->
<DownloadDocumentAction :api="DocumentApi" :knowledge-id="knowledgeId" v-model:loading="operationLoading" :document="row" />
<!-- 替换原文档 -->
Expand Down Expand Up @@ -499,10 +487,9 @@ onBeforeUnmount(() => {
@refresh="refreshAfterOperation"
/>
<!-- 批量生成问题 -->
<GenerateDocumentQuestionsAction
<BatchGenerateQuestionsAction
:api="DocumentApi"
v-model:loading="operationLoading"
batch
:knowledge-id="knowledgeId"
:document-ids="selectedDocumentIds"
:disabled="operationLoading"
Expand Down Expand Up @@ -537,7 +524,6 @@ onBeforeUnmount(() => {
:knowledge-id="knowledgeId"
v-model:loading="operationLoading"
:documents="selectedDocuments"
:knowledge-type="knowledge?.type"
batch
@refresh="refreshAfterOperation"
/>
Expand Down
Loading
Loading