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
4 changes: 4 additions & 0 deletions ui/src/api/API_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ v3 编辑表单提交 `{ name, description }`,标题上限 64、更新说明

### 知识库维护

`getKnowledgeMcpConfig(knowledgeId)` 与 `postKnowledgeKeywordIndex(knowledgeId)` 为预留接口方法,
目前不发送 HTTP 请求:前者返回空配置文本,后者模拟成功。后续确认后端协议后替换方法内部实现。
MCP 入口加载配置后打开只读及复制弹窗;分词索引入口仅调用方法并在成功后提示“操作成功”。

`exportKnowledgeExcel`、`exportKnowledgeZip`、`exportKnowledge` 分别通过 GET 请求知识库
`/<knowledgeId>/export`、`export_zip`、`export_knowledge`,复用 `getExportFile` 下载。
三种结果分别为文档 Excel、包含图片的文档 ZIP 和可导入创建的知识库 ZIP;优先使用服务端文件名。
Expand Down
16 changes: 16 additions & 0 deletions ui/src/api/admin/workspace/knowledge/knowledge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,23 @@ const postKnowledgeImport = (file: File, folderId: string) => {
return post<FormData, { knowledge_id: string; type: KnowledgeItem['type'] }>(`${getPrefix()}/import_knowledge`, payload)
}

/** 获取知识库 MCP 配置详情,暂返回空配置供交互联调。 */
const getKnowledgeMcpConfig = (knowledgeId: string): Promise<string> => {
// TODO 接入 MCP 配置查询接口,使用 knowledgeId 获取配置文本。
void knowledgeId
return Promise.resolve('')
}

/** 执行知识库分词索引,暂模拟成功供交互联调。 */
const postKnowledgeKeywordIndex = (knowledgeId: string): Promise<void> => {
// TODO 接入分词索引接口,使用 knowledgeId 提交索引任务。
void knowledgeId
return Promise.resolve()
}

export default {
getKnowledgeMcpConfig,
postKnowledgeKeywordIndex,
exportKnowledgeExcel,
exportKnowledgeZip,
exportKnowledge,
Expand Down
1 change: 1 addition & 0 deletions ui/src/api/types/portal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Dict } from './common'

export interface PortalAuthConfig extends Dict<unknown> {
login_value?: string[]
default_value?: string
max_attempts?: number
failed_attempts?: number
lock_time?: number
Expand Down
18 changes: 15 additions & 3 deletions ui/src/views/VIEW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ src/views/knowledge/
│ └── action-dropdown/
│ ├── index.ts
│ ├── SettingKnowledgeAction.vue # 跳转知识库设置
│ ├── KeywordIndexKnowledgeAction.vue # 分词索引入口
│ ├── mcp-config-action/ # MCP 配置入口与专属只读弹窗
│ ├── ExportKnowledgeAction.vue # Excel、文档 ZIP 与知识库包导出
│ ├── MoveKnowledgeAction.vue
│ └── DeleteKnowledgeAction.vue
Expand All @@ -198,6 +200,10 @@ src/views/knowledge/
组合单项转移、删除 Action,并管理批量选择、全选、批量转移与批量删除。共享知识库不展示这些操作。
`SettingKnowledgeAction` 暂不增加权限判断,点击后携带当前
`workspaceId` 和知识库 ID 跳转 `workspace-knowledge-setting`;阻止事件冒泡,避免同时触发卡片详情跳转。
“设置”之后依次组合 `KeywordIndexKnowledgeAction` 和 `McpConfigKnowledgeAction`,接收完整
Knowledge API 并复用页面操作 loading。分词索引不增加确认弹窗,调用成功后只提示“操作成功”;
MCP 配置沿用工具的只读文本与悬浮复制交互,专属弹窗按需挂载、关闭后卸载。
两者接口暂为本地占位,MCP 返回空文本,分词索引模拟成功,尚不执行服务端操作。
`ExportKnowledgeAction` 接收完整 Knowledge API,暂不增加权限判断;悬停“导出”展开右侧
子菜单,分别导出文档 Excel、文档 ZIP 和可再次导入的知识库包。
导出复用页面操作 loading,防止重复请求,结束或失败后恢复;共享知识库不展示该入口。
Expand Down Expand Up @@ -401,7 +407,7 @@ Dialog。新增或重命名文件时,应同步更新所有导入和页面功
| `system/identity/workspaces/WorkspaceListView.vue` | 工作空间列表页面 |
| `system/chat/user-groups/GroupsListView.vue` | 对话用户组及组成员管理页面 |
| `system/chat/users/UserListView.vue` | 对话用户列表、配额及用户导入管理页面 |
| `system/chat/portal-setting/PortalSettingView.vue` | 门户基本信息、访问开关、认证配置与门户预览;跨域详细配置暂保留说明入口 |
| `system/chat-management/portal-setting/PortalSettingView.vue` | 门户基本信息、访问开关、认证配置、跨域地址编辑与门户预览 |
| `system/settings/theme/ThemeSettingView.vue` | 系统外观设置和登录外观预览页面 |
| `system/settings/authentication/AuthenticationView.vue` | 系统登录及认证源配置页面 |
| `system/settings/email/EmailSettingsView.vue` | 系统邮件 SMTP 服务配置页面 |
Expand All @@ -413,8 +419,14 @@ Dialog。新增或重命名文件时,应同步更新所有导入和页面功
## 备注要求

门户设置的 `components/ButtonEditPortal.vue`、`ButtonPortalAuthSetting.vue` 和
`ButtonPortalCorsSetting.vue` 分别封装对应按钮、弹窗及表单状态。页面保留默认配置、统一保存和
预览布局;编辑组件暴露草稿预览,认证组件暴露 `open()` 供首次开启认证时联动打开。
`ButtonPortalCorsSetting.vue` 分别封装对应按钮、浮层及表单状态。认证组件使用 `MkDrawer`,
打开时与系统 `LoginSetting` 共用 `AuthSettingApi.getLoginSetting()`,通过返回的 `login_methods`
生成登录方式选项,复用登录方式标签,初始保留账号登录。提供默认登录方式及验证码与账号锁定设置;默认方式
仅可选已勾选项,取消勾选当前默认项时回退到首个可选项。认证组件暴露 `open()` 供首次开启认证
时联动打开;当前认证配置仅保存到页面内存,取消不回写,刷新后恢复默认配置,待接口接入后持久化。
跨域组件通过 `v-model` 接收地址数组,弹窗按一行一个地址编辑;保存时清理行首尾空白和空行,
取消不回写,关闭后清理草稿。空数组表示不限制来源的配置意图;目前地址仅保存在页面内存,
尚未对接 `cors_config` 或实际跨域策略。

- 新增路由级页面时,必须在“页面功能登记”中添加一条简洁、明确的功能说明。
- 页面职责改变、文件重命名或目录移动时,必须同步修改登记内容。
Expand Down
16 changes: 16 additions & 0 deletions ui/src/views/knowledge/KnowledgeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
AuthorizeKnowledgeAction,
DeleteKnowledgeAction,
ExportKnowledgeAction,
KeywordIndexKnowledgeAction,
McpConfigKnowledgeAction,
MoveKnowledgeAction,
RelatedResourcesKnowledgeAction,
SettingKnowledgeAction,
Expand Down Expand Up @@ -228,6 +230,20 @@ function handleBatchDelete() {
<!-- TODO 生成问题 -->
<!-- 设置 -->
<SettingKnowledgeAction label="设置" :knowledge="knowledge" />
<!-- 分词索引 -->
<KeywordIndexKnowledgeAction
v-model:loading="knowledgeOperationLoading"
label="分词索引"
:api="KnowledgeApi"
:knowledge="knowledge"
/>
<!-- MCP 配置详情 -->
<McpConfigKnowledgeAction
v-model:loading="knowledgeOperationLoading"
label="MCP 配置详情"
:api="KnowledgeApi"
:knowledge="knowledge"
/>
<!-- 资源授权 -->
<AuthorizeKnowledgeAction label="资源授权" :knowledge="knowledge" />

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script setup lang="ts">
import { Share } from '@element-plus/icons-vue'
import type KnowledgeApi from '@/api/admin/workspace/knowledge/knowledge'
import type { KnowledgeItem } from '@/api/types'
import { MsgSuccess } from '@/utils/message'

defineOptions({ name: 'KeywordIndexKnowledgeAction' })

const props = defineProps<{ api: typeof KnowledgeApi; knowledge: KnowledgeItem; label: string }>()
const loading = defineModel<boolean>('loading', { default: false })

/* 知识库分词索引 */
function handleKeywordIndex() {
if (loading.value) return
loading.value = true
return props.api
.postKnowledgeKeywordIndex(props.knowledge.id)
.then(() => {
MsgSuccess('操作成功')
})
.finally(() => {
loading.value = false
})
}
</script>

<template>
<!-- 执行分词索引 -->
<MkDropdownItem :disabled="loading" @click.stop="handleKeywordIndex">
<template #icon><MkIcon :icon="Share" /></template>
<span>{{ label }}</span>
</MkDropdownItem>
</template>
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/** 知识库卡片菜单操作。 */
export { default as KeywordIndexKnowledgeAction } from './KeywordIndexKnowledgeAction.vue'
export { default as McpConfigKnowledgeAction } from './mcp-config-action/McpConfigKnowledgeAction.vue'
export { default as ExportKnowledgeAction } from './ExportKnowledgeAction.vue'
export { default as SettingKnowledgeAction } from './SettingKnowledgeAction.vue'
export { default as MoveKnowledgeAction } from './MoveKnowledgeAction.vue'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script setup lang="ts">
import { ref } from 'vue'
import { copyText } from '@/utils/clipboard'

defineOptions({ name: 'KnowledgeMcpConfigDialog' })

const emit = defineEmits<{ closed: [] }>()

/* 配置展示与复制 */
const visible = ref(false)
const config = ref('')

function open(mcpConfig: string) {
config.value = mcpConfig
visible.value = true
}

function handleClosed() {
config.value = ''
emit('closed')
}

defineExpose({ open })
</script>

<template>
<MkDialog v-model="visible" title="MCP 配置详情" @closed="handleClosed">
<div class="group knowledge-mcp-config-detail relative">
<el-input v-model="config" :autosize="{ minRows: 8, maxRows: 24 }" disabled type="textarea" />
<!-- 复制 MCP 配置 -->
<el-button
class="group-hover-visible absolute right-3 top-3 z-10 h-8 shadow-md"
circle
title="复制配置"
:disabled="!config"
@click="copyText(config)"
>
<MkIcon name="icon_copy_outlined" />
</el-button>
</div>
</MkDialog>
</template>

<style scoped lang="scss">
.knowledge-mcp-config-detail {
:deep(.el-textarea__inner) {
color: var(--el-text-color-primary);
cursor: pointer;
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { nextTick, ref, useTemplateRef } from 'vue'
import type KnowledgeApi from '@/api/admin/workspace/knowledge/knowledge'
import type { KnowledgeItem } from '@/api/types'
import KnowledgeMcpConfigDialog from './KnowledgeMcpConfigDialog.vue'

defineOptions({ name: 'McpConfigKnowledgeAction' })

const props = defineProps<{ api: typeof KnowledgeApi; knowledge: KnowledgeItem; label: string }>()
const loading = defineModel<boolean>('loading', { default: false })

/* 知识库 MCP 配置详情 */
const dialogMounted = ref(false)
const mcpConfigDialogRef = useTemplateRef<InstanceType<typeof KnowledgeMcpConfigDialog>>('mcpConfigDialogRef')

function handleOpenMcpConfig() {
if (loading.value) return
loading.value = true
return props.api
.getKnowledgeMcpConfig(props.knowledge.id)
.then((config) => {
dialogMounted.value = true
return nextTick(() => mcpConfigDialogRef.value?.open(config))
})
.finally(() => {
loading.value = false
})
}

function handleDialogClosed() {
dialogMounted.value = false
}
</script>

<template>
<!-- 查看 MCP 配置详情 -->
<MkDropdownItem :disabled="loading" @click.stop="handleOpenMcpConfig">
<template #icon><MkIcon name="icon_describe_outlined" /></template>
<span>{{ label }}</span>
</MkDropdownItem>

<KnowledgeMcpConfigDialog v-if="dialogMounted" ref="mcpConfigDialogRef" @closed="handleDialogClosed" />
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const portalSetting = reactive<PortalSetting>({
cors_config: {},
})
const saving = ref(false)
// 跨域地址暂存于页面,待接口协议确定后接入 cors_config。
const portalCorsOrigins = ref<string[]>([])

const portalAccessUrl = new URL('/portal', window.location.origin).href
const portalApiUrl = new URL('/api/portal', window.location.origin).href
Expand Down Expand Up @@ -59,6 +61,13 @@ function handleAccessChange(field: AccessField, value: string | number | boolean
return savePortalSetting({ [field]: Boolean(value) })
}

/* 认证接口暂未接入,保存到当前页面配置供再次打开回填。 */
function savePortalAuthSetting(payload: PortalSettingPayload) {
Object.assign(portalSetting, payload)
MsgSuccess('已应用到当前页面')
return Promise.resolve()
}

/* 编辑草稿预览与认证入口联动 */

const authSettingButtonRef = useTemplateRef<InstanceType<typeof ButtonPortalAuthSetting>>('authSettingButtonRef')
Expand Down Expand Up @@ -136,15 +145,15 @@ const previewLogo = computed(() => portalSetting.logo)
<span>身份认证</span>
<div class="flex-align-center gap-2">
<!-- 配置身份认证 -->
<ButtonPortalAuthSetting ref="authSettingButtonRef" :setting="portalSetting" :saving="saving" :save="savePortalSetting" />
<ButtonPortalAuthSetting ref="authSettingButtonRef" :setting="portalSetting" :saving="saving" :save="savePortalAuthSetting" />
<el-switch :model-value="portalSetting.enable_auth" size="small" @change="handleAccessChange('enable_auth', $event)" />
</div>
</div>
<div class="flex-between gap-2">
<span>跨域设置</span>
<div class="flex-align-center gap-2">
<!-- 查看跨域配置说明 -->
<ButtonPortalCorsSetting :disabled="saving" />
<!-- 配置跨域地址 -->
<ButtonPortalCorsSetting v-model="portalCorsOrigins" :disabled="saving" />
<el-switch :model-value="portalSetting.enable_cors" size="small" @change="handleAccessChange('enable_cors', $event)" />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,14 @@ function handleClosePortalEdit() {
</el-button>
<MkDialog v-model="editVisible" title="编辑" @closed="handleClosePortalEdit">
<el-form ref="portalFormRef" :model="portalForm" :rules="portalRules" label-position="top" @submit.prevent>
<!-- <el-form-item label="门户 Logo">
<MkEditAvatar v-model="portalForm.logo" :default-icon="defaultLogo" :size="48" :editable="!saving" @change="handleLogoChange" />
</el-form-item> -->
<el-form-item label="名称" prop="name">
<!-- <MkEditAvatar v-model="portalForm.logo" :default-icon="defaultLogo" :size="48" :editable="!saving" @change="handleLogoChange" /> -->
<el-input v-model="portalForm.name" maxlength="64" show-word-limit :disabled="saving" placeholder="请输入门户名称" />
</el-form-item>
</el-form>
<template #footer>
<!-- 取消编辑 -->
<el-button :disabled="saving" @click="editVisible = false">取消</el-button>
<el-button plain :disabled="saving" @click="editVisible = false">取消</el-button>
<!-- 保存门户信息 -->
<el-button type="primary" :loading="saving" @click="handleSavePortalInfo">保存</el-button>
</template>
Expand Down
Loading
Loading