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
11 changes: 10 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,29 @@
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.43.9",
"@element-plus/icons-vue": "^2.3.2",
"@floating-ui/dom": "^1.8.0",
"@he-tree/vue": "^2.10.5",
"@logicflow/core": "^2.2.5",
"@logicflow/extension": "^2.3.1",
"@vavt/cm-extension": "^1.11.2",
"axios": "^1.19.0",
"codemirror": "^6.0.2",
"cropperjs": "^1.6.3",
"dingtalk-jsapi": "^3.2.9",
"dompurify": "^3.4.14",
"echarts": "^6.1.0",
"element-plus": "^2.14.2",
"highlight.js": "^11.12.0",
"jsencrypt": "^3.5.4",
"katex": "^0.16.47",
"lodash": "^4.18.1",
"md-editor-v3": "^6.5.6",
"mermaid": "^11.17.2",
"nanoid": "^6.0.1",
"nprogress": "^0.2.0",
"pinia": "^3.0.4",
"prettier": "3.8.4",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.7",
"vue": "^3.5.38",
"vue-codemirror": "^6.1.1",
Expand All @@ -60,7 +70,6 @@
"jiti": "^2.7.0",
"npm-run-all2": "^9.0.2",
"oxlint": "~1.69.0",
"prettier": "3.8.4",
"sass": "^1.101.0",
"tailwindcss": "^4.3.2",
"typescript": "~6.0.0",
Expand Down
10 changes: 7 additions & 3 deletions ui/src/api/API_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ src/api/
│ │ ├── application/ # 智能体接口
│ │ ├── knowledge/ # 知识库接口
│ │ ├── model/ # 模型接口
│ │ ├── tool/ # 工具及工具商店接口
│ │ ├── tool/ # 工具、工具工作流及工具商店接口
│ │ └── <resource>.ts # 工作空间公共资源接口
│ └── provider.ts # Workspace 与 System 共用的模型供应商接口
├── chat/ # Chat 独立请求体系,当前预留
Expand Down Expand Up @@ -52,6 +52,8 @@ src/api/
所属一级业务域下。
- 一类资源的增删改查放在同一个最终资源文件中。调用方直接导入该文件,不为业务目录创建聚合
入口,也不创建汇总所有业务接口的 `api.ts`。
- 工具基础信息和工具工作流使用后端不同资源接口:`workspace/tool/tool.ts` 维护工具增删改查,
`workspace/tool/workflow.ts` 维护工具工作流的加载、保存和发布。
- 每个业务接口函数必须添加简短的 JSDoc,说明接口的业务作用;注释应描述“获取什么”“保存什么”
或“对哪个资源执行什么操作”,不重复参数类型、请求方法等代码已经清楚表达的信息。
- 每个业务接口使用 `const` 声明的箭头函数,不单独具名导出;在文件末尾通过
Expand Down Expand Up @@ -134,7 +136,7 @@ API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文
- Admin Router 与请求客户端直接读取 `window.MaxKB` 运行时路径配置;`Window` 和
`MaxKBRuntimeConfig` 的全局类型统一声明在根目录 `env.d.ts`。
- Admin 普通 JSON 请求使用 Axios;`request.ts` 导出 Axios 实例以及 `promise`、`get`、
`post`、`put`、`del` 和 Blob 文件 `downloadRequest` 请求封装。
`post`、`put`、`del`、流式响应 `postStream` 和 Blob 文件 `downloadRequest` 请求封装。
- 正常 JSON 接口返回 `Promise<T>`,请求层负责解包后端 `{ code, message, data }` 响应。
- GET 文件导出使用 `getExportFile`;需要通过 POST 同时传递查询参数和可选请求体的 Excel 导出
使用 `postExportExcel`;Skill 压缩包等指定请求方法的文件下载使用 `downloadRequest`。请求层统一
Expand All @@ -146,4 +148,6 @@ API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文
`stores/index.ts` 导出的 `useStore()` 按需访问 Store;401 响应统一清除 token 并跳转 Admin
登录页。
- loading 不作为 API 函数参数,由调用接口的页面或 Store 管理。
- 上传、下载和流式请求在真实需求出现时独立设计,不提前塞入普通 JSON 请求客户端。
- 流式 POST 请求使用 `postStream` 返回原始 `Response`,由业务组件按具体协议解析数据块;
鉴权、语言请求头和错误状态仍由请求基础设施统一处理。
- 上传、下载和其他特殊请求在真实需求出现时独立设计,不提前塞入普通 JSON 请求客户端。
72 changes: 69 additions & 3 deletions ui/src/api/admin/core/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ interface ExportRequestConfig extends AxiosRequestConfig {
skipGlobalErrorMessage?: boolean
}

interface StreamRequestConfig {
signal?: AbortSignal
}

function setRequestHeaders(config: InternalAxiosRequestConfig) {
const { auth, user } = useStore()

Expand Down Expand Up @@ -93,6 +97,16 @@ async function getResponseErrorMessage(error: unknown) {
return responseData?.message
}

async function getFetchErrorMessage(response: Response) {
const responseText = await response.text()
try {
const responseData = JSON.parse(responseText) as Partial<ApiResponse<unknown>>
return responseData.message || responseText
} catch {
return responseText
}
}

async function downloadExportResponse(response: AxiosResponse<Blob>, fileName: string, mimeType = 'application/octet-stream') {
if (response.data.type.includes('application/json')) {
const text = await response.data.text()
Expand Down Expand Up @@ -182,10 +196,23 @@ export function get<T = unknown>(url: string, params?: Dict<unknown>, loading?:
}

/** 发送指定方法的 Blob 请求并触发浏览器下载。 */
export async function downloadRequest(url: string, method: string, data?: unknown, params?: Dict<unknown>, loading?: LoadingTarget): Promise<boolean> {
export async function downloadRequest(
url: string,
method: string,
data?: unknown,
params?: Dict<unknown>,
loading?: LoadingTarget,
): Promise<boolean> {
startLoading(loading)
try {
const response = await request.request<Blob>({ url, method, data, params, responseType: 'blob', skipGlobalErrorMessage: true } as ExportRequestConfig)
const response = await request.request<Blob>({
url,
method,
data,
params,
responseType: 'blob',
skipGlobalErrorMessage: true,
} as ExportRequestConfig)

return downloadExportResponse(response, 'download')
} finally {
Expand All @@ -206,7 +233,13 @@ export async function getExportFile(fileName: string, url: string, params?: Dict
}

/** 发送 POST 请求并将 Blob 响应下载为 Excel 文件。 */
export async function postExportExcel<TData = unknown>(fileName: string, url: string, params?: Dict<unknown>, data?: TData, loading?: LoadingTarget): Promise<boolean> {
export async function postExportExcel<TData = unknown>(
fileName: string,
url: string,
params?: Dict<unknown>,
data?: TData,
loading?: LoadingTarget,
): Promise<boolean> {
startLoading(loading)
try {
const response = await request.post<Blob>(url, data, { params, responseType: 'blob', skipGlobalErrorMessage: true } as ExportRequestConfig)
Expand All @@ -222,6 +255,39 @@ export function post<TData = unknown, T = unknown>(url: string, data?: TData, pa
return promise<T>(request.post<ApiResponse<T>>(url, data, { params, timeout }), loading)
}

/** 发送 POST 请求并返回可逐块读取的原始响应。 */
export async function postStream<TData = unknown>(url: string, data?: TData, config: StreamRequestConfig = {}) {
const { auth, user } = useStore()
const headers = new Headers({ 'Content-Type': 'application/json' })
if (auth.token) headers.set('Authorization', `Bearer ${auth.token}`)
if (user.language) headers.set('Accept-Language', user.language)

const baseUrl = String(request.defaults.baseURL ?? '').replace(/\/+$/, '')
const response = await fetch(`${baseUrl}/${url.replace(/^\/+/, '')}`, {
body: JSON.stringify(data ?? {}),
headers,
method: 'POST',
signal: config.signal,
})

if (response.ok) return response

const errorMessage = (await getFetchErrorMessage(response)) || response.statusText
if (response.status === 401) {
auth.clearToken()
void router.push({ name: 'login' })
} else if (response.status === 403) {
MsgError(errorMessage || 'No permission to access')
} else if (response.status === 404) {
void router.replace({ name: 'not-found', params: { pathMatch: ['404'] } })
} else {
MsgError(errorMessage)
}
const error = new Error(errorMessage)
error.name = 'StreamRequestError'
throw error
}

/** 发送 PUT 请求。 */
export function put<TData = unknown, T = unknown>(url: string, data?: TData, params?: Dict<unknown>, loading?: LoadingTarget, timeout?: number) {
return promise<T>(request.put<ApiResponse<T>>(url, data, { params, timeout }), loading)
Expand Down
10 changes: 8 additions & 2 deletions ui/src/api/admin/workspace/application/application.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { del, get, getExportFile, post, put } from '../../core/request'
import { del, get, getExportFile, post, postStream, put } from '../../core/request'
import type { ParamsPage, ResponsePage } from '../../core/types'
import type { ApplicationDetail, ApplicationFormPayload, Dict } from '@/api/types'
import type { ApplicationDetail, ApplicationFormPayload, Dict, PromptGeneratePayload } from '@/api/types'
import { getWorkspaceId } from '@/utils/resource-context'

const getPrefix = () => {
Expand Down Expand Up @@ -72,6 +72,11 @@ const putApplicationPublish = (applicationId: string) => {
return put<Record<string, never>, ApplicationDetail>(`${getPrefix()}/${applicationId}/publish`, {})
}

/** 使用指定模型流式生成或优化系统提示词。 */
const postPromptGenerate = (applicationId: string, modelId: string, payload: PromptGeneratePayload, signal?: AbortSignal) => {
return postStream(`${getPrefix()}/${applicationId}/model/${modelId}/prompt_generate`, payload, { signal })
}

export default {
getApplicationPage,
getApplicationDetail,
Expand All @@ -85,4 +90,5 @@ export default {
putBatchMoveApplications,
putApplicationPublish,
getAllApplication,
postPromptGenerate,
}
30 changes: 30 additions & 0 deletions ui/src/api/admin/workspace/tool/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type LogicFlow from '@logicflow/core'
import { get, put } from '../../core/request'
import type { ToolWorkflowDetail } from '@/api/types'
import { getWorkspaceId } from '@/utils/resource-context'

interface ToolWorkflowPayload {
work_flow: LogicFlow.GraphConfigData
}

const getPrefix = () => {
const workspaceId = getWorkspaceId()
return `/workspace/${workspaceId}/tool`
}

/** 获取工具工作流详情。 */
const getToolWorkflow = (toolId: string) => {
return get<ToolWorkflowDetail>(`${getPrefix()}/${toolId}/workflow`)
}

/** 保存工具工作流。 */
const putToolWorkflow = (toolId: string, payload: ToolWorkflowPayload) => {
return put<ToolWorkflowPayload, ToolWorkflowDetail>(`${getPrefix()}/${toolId}/workflow`, payload)
}

/** 发布工具工作流。 */
const putToolWorkflowPublish = (toolId: string) => {
return put<undefined, boolean>(`${getPrefix()}/${toolId}/publish`)
}

export default { getToolWorkflow, putToolWorkflow, putToolWorkflowPublish }
10 changes: 10 additions & 0 deletions ui/src/api/types/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,13 @@ export interface ApplicationFormPayload {
long_term_trigger_setting?: Record<string, unknown>
long_term_trigger_type?: string
}

export interface PromptGenerateMessage {
content: string
role: 'ai' | 'user'
}

export interface PromptGeneratePayload {
messages: PromptGenerateMessage[]
prompt: string
}
16 changes: 14 additions & 2 deletions ui/src/api/types/tool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** Workspace 工具列表和工具维护共用的业务类型。 */

import type LogicFlow from '@logicflow/core'
import { TOOL_SCOPE, TOOL_TYPE } from '@/api/enums'
import type { DynamicFormField } from './model'

Expand Down Expand Up @@ -65,7 +66,18 @@ export interface ToolItem {
update_time?: string
user_id?: string | null
version?: string | null
work_flow?: Record<string, unknown>
work_flow?: LogicFlow.GraphConfigData
workspace_id: string
}

export interface ToolWorkflowDetail {
create_time?: string
id: string
is_publish: boolean
publish_time?: string | null
tool: string
update_time?: string
work_flow: LogicFlow.GraphConfigData
workspace_id: string
}

Expand All @@ -87,7 +99,7 @@ export interface ToolPayload {
name?: string
scope?: ToolScope
tool_type?: ToolType
work_flow?: Record<string, unknown>
work_flow?: LogicFlow.GraphConfigData
work_flow_template?: ToolStoreItem
}

Expand Down
3 changes: 3 additions & 0 deletions ui/src/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'

import App from './App.vue'
import { configureMarkdownEditor } from '@/components/global/markdown-editor/config'
import router from '@/router/chat'
import { pinia } from '@/stores'
import 'element-plus/dist/index.css'
import '@/styles/tailwind.css'
import '@/styles/index.scss'

configureMarkdownEditor()

const app = createApp(App)

app.use(pinia)
Expand Down
6 changes: 6 additions & 0 deletions ui/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ declare module 'vue' {
KnowledgeIcon: typeof import('./components/global/mk-icon/KnowledgeIcon.vue')['default']
LayoutAside: typeof import('./components/global/mk-view-layout/layout-aside.vue')['default']
LayoutBatchFooter: typeof import('./components/global/mk-view-layout/layout-batch-footer.vue')['default']
MdEditor: typeof import('./components/global/markdown-editor/MdEditor.vue')['default']
MdEditorMagnify: typeof import('./components/global/markdown-editor/MdEditorMagnify.vue')['default']
MdPreview: typeof import('./components/global/markdown-editor/MdPreview.vue')['default']
MkCollapse: typeof import('./components/global/mk-collapse/index.vue')['default']
MkComplexSearch: typeof import('./components/global/mk-complex-search/index.vue')['default']
MkDialog: typeof import('./components/global/mk-dialog/index.vue')['default']
Expand Down Expand Up @@ -47,6 +50,9 @@ declare global {
const KnowledgeIcon: typeof import('./components/global/mk-icon/KnowledgeIcon.vue')['default']
const LayoutAside: typeof import('./components/global/mk-view-layout/layout-aside.vue')['default']
const LayoutBatchFooter: typeof import('./components/global/mk-view-layout/layout-batch-footer.vue')['default']
const MdEditor: typeof import('./components/global/markdown-editor/MdEditor.vue')['default']
const MdEditorMagnify: typeof import('./components/global/markdown-editor/MdEditorMagnify.vue')['default']
const MdPreview: typeof import('./components/global/markdown-editor/MdPreview.vue')['default']
const MkCollapse: typeof import('./components/global/mk-collapse/index.vue')['default']
const MkComplexSearch: typeof import('./components/global/mk-complex-search/index.vue')['default']
const MkDialog: typeof import('./components/global/mk-dialog/index.vue')['default']
Expand Down
31 changes: 29 additions & 2 deletions ui/src/components/COMPONENT_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ src/components/
│ └── workspace-relation-tags/
│ └── index.vue # 标签及关联工作空间展示
├── global/ # 高频、稳定的基础组件,自动注册
│ ├── markdown-editor/
│ │ ├── config.ts # 本地扩展、内容过滤和语言包的全局配置
│ │ ├── MdEditor.vue # 支持多语言和自定义页脚的 Markdown 编辑器
│ │ ├── MdEditorMagnify.vue # 支持弹窗放大编辑的 Markdown 编辑器
│ │ ├── MdPreview.vue # 禁用代码折叠的 Markdown 预览器
│ │ ├── sup-popover.scss # Markdown 上标说明悬浮层样式
│ │ └── sup-popover.ts # Markdown 上标说明悬浮层交互
│ ├── mk-complex-search/
│ │ └── index.vue # 字段选择与输入或枚举条件组合搜索框
│ ├── mk-collapse/
Expand Down Expand Up @@ -146,8 +153,9 @@ import WorkspaceRelationTags from '@/components/business/workspace-relation-tags
由使用方手动导入。业务组件不使用 `Mk` 前缀,也不再按 Workspace 等上级领域增加额外目录。
- 不依赖固定业务的共享组合 UI 组件放在 `components/<component-name>`,由使用方手动导入。
- 一个公共组件默认使用一个 kebab-case 目录,入口统一为 `index.vue`;`codemirror-editor` 按语言
提供 `python.vue` 和 `Json.vue` 两个专用入口,`mk-logo` 按完整 Logo 与图形 Logo 提供
`LogoFull.vue` 和 `LogoIcon.vue` 两个专用入口。
提供 `python.vue` 和 `Json.vue` 两个专用入口,`markdown-editor` 提供 `MdEditor.vue`、
`MdEditorMagnify.vue` 和 `MdPreview.vue` 三个自动注册入口,`mk-logo` 按完整 Logo 与图形 Logo
提供 `LogoFull.vue` 和 `LogoIcon.vue` 两个专用入口。
- 组件名使用 PascalCase;目录名使用 kebab-case,例如 `MkIcon` 对应
`mk-icon/index.vue`。
- `index.vue` 必须通过 `defineOptions({ name: 'ComponentName' })` 声明多单词组件名,避免
Expand Down Expand Up @@ -179,6 +187,25 @@ Element Plus 使用 `ElOnlyChild` 处理浮层触发器。`el-tooltip`、`el-pop

## 自动注册组件

### MdEditor、MdEditorMagnify、MdPreview

基于 `md-editor-v3` 的全局自动注册组件。三个组件统一使用 User Store 中的当前语言,并为繁体中文
加载 `zh-TW` 语言包;底层组件的 Props 和事件通过 `$attrs` 透传。`MdEditor` 默认关闭 Prettier,
并透传 `defFooters` 插槽;`MdEditorMagnify` 在无工具栏编辑器的页脚提供放大按钮,弹窗中点击确定
才会写回内容并触发 `submitDialog`,取消或关闭弹窗会丢弃本次弹窗编辑;`MdPreview` 默认关闭代码
折叠。Admin 和 Chat 入口统一调用
`configureMarkdownEditor()`,将代码高亮、全屏、KaTeX、图片裁剪、Mermaid、ECharts 和 Prettier
配置为本地实例,避免运行时加载 CDN 资源;`md-editor-v3` v6 的图标已随依赖打包,不再维护或加载
旧版 `markdown-iconfont.js`。

```vue
<MdEditor v-model="markdownContent">
<template #defFooters>自定义页脚</template>
</MdEditor>
<MdEditorMagnify v-model="markdownContent" title="提示词" @submit-dialog="handleSubmit" />
<MdPreview :model-value="markdownContent" />
```

### MkCollapse

带标题触发器和展开过渡动画的内容折叠组件。组件内部维护展开状态,`default-expanded` 设置初始
Expand Down
Loading
Loading