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
20 changes: 20 additions & 0 deletions ui/src/api/API_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ System 接口由资源管理服务提供;本地开源后端没有对应扩展
文件状态响应还包含 `REVOKE: '4'`、`REVOKED: '5'`、`IGNORED: 'n'`。
`DocumentItem.status_meta` 使用可空的 `DocumentStatusMeta`:`aggs` 为 `{ status, count }[]`,
`state_time` 按任务编号、状态字符两级索引时间字符串;两项均可缺省。
无分段时聚合项的 `status` 可为 `null`,按忽略状态解析,不计入成功数量。
`DocumentTaskState` 和 `DocumentTaskType` 从文档枚举派生,统一从 `@/api/types` 导入。
启用状态传布尔值,`false` 不省略;单选全部时省略该字段。`tags` 传数组,沿用 Axios 的
`tags[]` 序列化,内容为标签 ID 或特殊值 `NO_TAG`,不传标签名称或 JSON 字符串。
Expand Down Expand Up @@ -531,3 +532,22 @@ System 接口由资源管理服务提供;本地开源后端没有对应扩展
Workspace 共享知识库详情由 `resourceScope: 'workspace-shared'` 选择上述完整共享 API 对象;
普通详情继续使用 `workspace/knowledge/`。共享详情和文档查询不使用 System 共享资源管理 API,
也不回退到普通 Workspace 知识库接口。文档创建者选项沿用共享列表的 System `common.ts` 查询。

### 资源对话用户授权

`workspace/chat-user.ts` 使用 Admin 请求客户端,通过资源的 `user_group` 查询和保存组自动授权,
通过 `user_group_id/<groupId>` 查询分页用户及保存逐用户授权,沿用已有接口路径与请求体。
使用 `ParamsPage`、`ResponsePage`,调用方直接读取已解包数据并管理 loading。
公共授权类型维护在 `types/chat-user.ts`,经 `@/api/types` 导出。当前由工作空间智能体详情页调用;
仓库内开源后端尚未注册这组资源授权路径,实际联调依赖提供这些接口的后端环境。

### 智能体对话日志

`workspace/application/chat-log.ts` 使用 Admin 请求客户端,日志分页请求
`/<applicationId>/chat/<currentPage>/<pageSize>`,导出使用 `postExportExcel` 请求
`/<applicationId>/chat/export`,查询包含日期、摘要或用户及点赞、点踩下限;
导出请求体为 `{ select_ids }`,空数组表示导出当前筛选结果。
日志业务类型经 `@/api/types` 导出,来源枚举经 `@/api/enums` 导出,loading 由调用方维护。
清除策略通过 PUT `batch_clean_time` 提交当前智能体的单元素 `id_list`、`clean_time`、
`file_clean_time`,避免调用通用应用编辑流程;成功后页面刷新应用详情。
聊天记录、标注、添加知识库接口保留在同一 API 文件,尚未接入本次日志列表页面。
74 changes: 74 additions & 0 deletions ui/src/api/admin/workspace/application/chat-log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { del, get, post, postExportExcel, put } from '../../core/request'
import type { ParamsPage, ResponsePage } from '../../core/types'
import type { ChatLog, Dict } from '@/api/types'
import { getWorkspaceId } from '@/utils/resource-context'

const getPrefix = () => `/workspace/${getWorkspaceId()}/application`

/** 分页获取智能体对话日志。 */
const getChatLog = (applicationId: string, page: ParamsPage, query: Dict<unknown>) =>
get<ResponsePage<ChatLog>>(`${getPrefix()}/${applicationId}/chat/${page.currentPage}/${page.pageSize}`, query)

/** 导出筛选结果或选中的对话日志。 */
const postExportChatLog = (applicationId: string, applicationName: string, query: Dict<unknown>, data: { select_ids: string[] }) =>
postExportExcel(`${applicationName}.xlsx`, `${getPrefix()}/${applicationId}/chat/export`, query, data)

/** 保存当前智能体的日志与文件清除策略。 */
const putChatLogCleanTime = (applicationId: string, cleanTime: number, fileCleanTime: number) =>
put<{ id_list: string[]; clean_time: number; file_clean_time: number }, boolean>(`${getPrefix()}/batch_clean_time`, {
id_list: [applicationId],
clean_time: cleanTime,
file_clean_time: fileCleanTime,
})

/** 将对话记录添加至知识库。 */
const postChatLogAddKnowledge = (applicationId: string, data: Dict<unknown>) => post(`${getPrefix()}/${applicationId}/add_knowledge`, data)

/** 分页获取对话中的聊天记录。 */
const getChatRecordLog = (applicationId: string, chatId: string, page: ParamsPage, orderAsc = true) =>
get<ResponsePage<Dict<unknown>>>(`${getPrefix()}/${applicationId}/chat/${chatId}/chat_record/${page.currentPage}/${page.pageSize}`, {
order_asc: orderAsc,
})

/** 获取聊天记录详情。 */
const getChatRecordDetails = (applicationId: string, chatId: string, chatRecordId: string) =>
get<Dict<unknown>>(`${getPrefix()}/${applicationId}/chat/${chatId}/chat_record/${chatRecordId}`)

/** 获取聊天记录的标注段落。 */
const getMarkChatRecord = (applicationId: string, chatId: string, chatRecordId: string) =>
get<Dict<unknown>[]>(`${getPrefix()}/${applicationId}/chat/${chatId}/chat_record/${chatRecordId}/improve`)

/** 保存聊天记录的知识库标注内容。 */
const putChatRecordLog = (
applicationId: string,
chatId: string,
chatRecordId: string,
knowledgeId: string,
documentId: string,
data: Dict<unknown>,
) => put(`${getPrefix()}/${applicationId}/chat/${chatId}/chat_record/${chatRecordId}/knowledge/${knowledgeId}/document/${documentId}/improve`, data)

/** 删除聊天记录的知识库标注。 */
const deleteMarkChatRecord = (
applicationId: string,
chatId: string,
chatRecordId: string,
knowledgeId: string,
documentId: string,
paragraphId: string,
) =>
del(
`${getPrefix()}/${applicationId}/chat/${chatId}/chat_record/${chatRecordId}/knowledge/${knowledgeId}/document/${documentId}/paragraph/${paragraphId}/improve`,
)

export default {
getChatLog,
postExportChatLog,
putChatLogCleanTime,
postChatLogAddKnowledge,
getChatRecordLog,
getChatRecordDetails,
getMarkChatRecord,
putChatRecordLog,
deleteMarkChatRecord,
}
80 changes: 26 additions & 54 deletions ui/src/api/admin/workspace/chat-user.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,35 @@
import type { Ref } from 'vue'
import { Result } from '@/request/Result'
import { get, put } from '@/request/index'
import type { ChatUserGroupItem, ChatUserGroupUserItem, putUserGroupUserParams } from '@/api/type/workspaceChatUser'
import type { pageRequest, PageList } from '@/api/type/common'
import { get, put } from '../core/request'
import type { ParamsPage, ResponsePage } from '../core/types'
import type {
ChatUserAuthorization,
ChatUserAuthorizationGroup,
ChatUserAuthorizationResource,
ChatUserGroupAuthorizationPayload,
ChatUserAuthorizationPayload,
Dict,
} from '@/api/types'
import { getWorkspaceId } from '@/utils/resource-context'

import useStore from '@/stores'
const prefix: any = { _value: '/workspace/' }
Object.defineProperty(prefix, 'value', {
get: function () {
const { user } = useStore()
return this._value + user.getWorkspaceId()
},
})
/**
* 获取用户组列表
*/
const getUserGroupList: (resource: any, loading?: Ref<boolean>) => Promise<Result<ChatUserGroupItem[]>> = (resource, loading) => {
return get(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group`, undefined, loading)
}
const getPrefix = (resource: ChatUserAuthorizationResource) => `/workspace/${getWorkspaceId()}/${resource.resource_type}/${resource.resource_id}`

/**
* 修改用户组列表授权
*/
const editUserGroupList: (resource: any, data: { user_group_id: string, is_auth: boolean }[], loading?: Ref<boolean>) => Promise<Result<any>> = (resource, data, loading) => {
return put(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group`, data, undefined, loading)
/** 获取资源的对话用户组及自动授权状态。 */
const getUserGroupList = (resource: ChatUserAuthorizationResource) => {
return get<ChatUserAuthorizationGroup[]>(`${getPrefix(resource)}/user_group`)
}

/**
* 获取用户组的用户列表
*/
const getUserGroupUserList: (
resource: any,
user_group_id: string,
page: pageRequest,
params?: any,
loading?: Ref<boolean>,
) => Promise<Result<PageList<ChatUserGroupUserItem[]>>> = (resource, user_group_id, page, params, loading) => {
return get(
`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}/${page.current_page}/${page.page_size}`,
params,
loading,
)
/** 保存资源的用户组自动授权设置。 */
const putUserGroupAuthorization = (resource: ChatUserAuthorizationResource, data: ChatUserGroupAuthorizationPayload[]) => {
return put<ChatUserGroupAuthorizationPayload[], boolean>(`${getPrefix(resource)}/user_group`, data)
}

/**
* 更新用户组的用户列表
*/
const putUserGroupUser: (
resource: any,
user_group_id: string,
data: putUserGroupUserParams[],
loading?: Ref<boolean>,
) => Promise<Result<boolean>> = (resource, user_group_id, data, loading) => {
return put(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}`, data, undefined, loading)
/** 分页查询用户组内的对话用户及资源授权状态。 */
const getUserGroupUserList = (resource: ChatUserAuthorizationResource, userGroupId: string, page: ParamsPage, query?: Dict<unknown>) => {
return get<ResponsePage<ChatUserAuthorization>>(`${getPrefix(resource)}/user_group_id/${userGroupId}/${page.currentPage}/${page.pageSize}`, query)
}

export default {
getUserGroupList,
editUserGroupList,
getUserGroupUserList,
putUserGroupUser
/** 保存用户组内的对话用户资源授权。 */
const putUserGroupUser = (resource: ChatUserAuthorizationResource, userGroupId: string, data: ChatUserAuthorizationPayload[]) => {
return put<ChatUserAuthorizationPayload[], boolean>(`${getPrefix(resource)}/user_group_id/${userGroupId}`, data)
}

export default { getUserGroupList, putUserGroupAuthorization, getUserGroupUserList, putUserGroupUser }
12 changes: 12 additions & 0 deletions ui/src/api/enums/chat-log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** 对话日志的发起来源。 */
export const CHAT_LOG_SOURCE = {
ONLINE: 'ONLINE',
API_CALL: 'API_CALL',
ENTERPRISE_WECHAT: 'ENTERPRISE_WECHAT',
WECHAT_PUBLIC_ACCOUNT: 'WECHAT_PUBLIC_ACCOUNT',
LARK: 'LARK',
DINGTALK: 'DINGTALK',
ENTERPRISE_WECHAT_ROBOT: 'ENTERPRISE_WECHAT_ROBOT',
TRIGGER: 'TRIGGER',
SLACK: 'SLACK',
} as const
1 change: 1 addition & 0 deletions ui/src/api/enums/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from './file'
export * from './state'

export * from './document'
export * from './chat-log'
2 changes: 2 additions & 0 deletions ui/src/api/types/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export interface ApplicationDetail extends Omit<ApplicationFormPayload, 'desc'>
}

export interface ApplicationFormPayload {
clean_time?: number
file_clean_time?: number
default_model_setting?: DefaultModelSettingPayload
name?: string
desc?: string
Expand Down
17 changes: 17 additions & 0 deletions ui/src/api/types/chat-log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { CHAT_LOG_SOURCE } from '@/api/enums'

export type ChatLogSource = (typeof CHAT_LOG_SOURCE)[keyof typeof CHAT_LOG_SOURCE]

/** 智能体对话日志列表记录。 */
export interface ChatLog {
id: string
abstract: string
chat_record_count: number
star_num: number
trample_num: number
mark_sum: number
asker?: { username?: string; nick_name?: string } | null
ip_address?: string | null
source?: { type?: string } | null
update_time: string
}
28 changes: 28 additions & 0 deletions ui/src/api/types/chat-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,31 @@ export interface BatchSetChatUserQuotaResult {
success_count: number
failed_count: number
}

/** 对话用户授权所属的资源。 */
export interface ChatUserAuthorizationResource {
resource_id: string
resource_type: string
}

/** 资源内的对话用户组自动授权状态。 */
export interface ChatUserAuthorizationGroup {
id: string
name: string
is_auth: boolean
}

/** 用户组内的对话用户资源授权状态。 */
export interface ChatUserAuthorization extends Pick<ChatUserBase, 'id' | 'username' | 'nick_name' | 'source'> {
is_auth: boolean
}

export interface ChatUserGroupAuthorizationPayload {
user_group_id: string
is_auth: boolean
}

export interface ChatUserAuthorizationPayload {
chat_user_id: string
is_auth: boolean
}
2 changes: 1 addition & 1 deletion ui/src/api/types/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type DocumentTaskType = (typeof DOCUMENT_TASK_TYPE)[keyof typeof DOCUMENT

/** 文件各任务的分段计数及状态发生时间。 */
export interface DocumentStatusMeta {
aggs?: { status: string; count: number }[]
aggs?: { status: string | null; count: number }[]
state_time?: Partial<Record<DocumentTaskType, Partial<Record<DocumentTaskState, string>>>>
}

Expand Down
1 change: 1 addition & 0 deletions ui/src/api/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export type * from './state'
export type * from './portal'

export type * from './document'
export type * from './chat-log'
2 changes: 1 addition & 1 deletion ui/src/assets/iconfont.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions ui/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
TableFilterCascader: typeof import('./components/global/mk-table/mk-table-filter/TableFilterCascader.vue')['default']
TableFilterCustom: typeof import('./components/global/mk-table/mk-table-filter/TableFilterCustom.vue')['default']
TableFilterMultiple: typeof import('./components/global/mk-table/mk-table-filter/TableFilterMultiple.vue')['default']
TableFilterSingle: typeof import('./components/global/mk-table/mk-table-filter/TableFilterSingle.vue')['default']
ToolIcon: typeof import('./components/global/mk-icon/ToolIcon.vue')['default']
Expand Down Expand Up @@ -95,6 +96,7 @@ declare global {
const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView']
const TableFilterCascader: typeof import('./components/global/mk-table/mk-table-filter/TableFilterCascader.vue')['default']
const TableFilterCustom: typeof import('./components/global/mk-table/mk-table-filter/TableFilterCustom.vue')['default']
const TableFilterMultiple: typeof import('./components/global/mk-table/mk-table-filter/TableFilterMultiple.vue')['default']
const TableFilterSingle: typeof import('./components/global/mk-table/mk-table-filter/TableFilterSingle.vue')['default']
const ToolIcon: typeof import('./components/global/mk-icon/ToolIcon.vue')['default']
Expand Down
33 changes: 30 additions & 3 deletions ui/src/components/COMPONENT_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,10 @@ Dialog、Drawer、Popover、嵌套区域等其他大、小表格均禁止开启
数据写回边界自行 `cloneDeep`,保持 LogicFlow 的可观察树约束。

`MkTableFilter` 放在 `global/mk-table/mk-table-filter/`,公共入口为 `index.vue`。
入口统一选择模式、渲染触发按钮、计算筛选高亮和回写模型;三个内部组件分别维护对应交互:
入口统一选择模式、渲染触发按钮、计算筛选高亮和回写模型;四个内部组件分别维护对应交互:
`TableFilterSingle.vue` 复用 `MkDropdown`、`MkDropdownMenu` 和 `MkDropdownItem`,
`TableFilterMultiple.vue` 管理多选草稿及确认,`TableFilterCascader.vue` 管理级联面板及即时提交。
`TableFilterMultiple.vue` 管理多选草稿及确认,`TableFilterCascader.vue` 管理级联面板及即时提交,
`TableFilterCustom.vue` 管理对象草稿和自定义内容;多选与自定义模式共用 `TableFilterActions.vue` 的重置、确定按钮。
内部组件通过默认插槽接收同一个触发按钮,只向入口发送 `select` 和 `open`;页面只使用
`MkTableFilter`,不直接引用模式组件。多选与级联继续使用 Popover,不强行统一其提交和关闭行为。

Expand All @@ -297,6 +298,7 @@ Dialog、Drawer、Popover、嵌套区域等其他大、小表格均禁止开启
| `multiple` | `TableFilterOption[]` / 字符串、数字或布尔值数组 | 打开时复制草稿,确定后提交;重置提交 `[]`;点击外部丢弃草稿 |
| `single` | `TableFilterOption[]` / 字符串、数字或布尔值,加 `null` | 点击立即提交并关闭;“全部”提交 `null` |
| `cascader` | Element Plus `CascaderOption[]` / 由 `cascaderProps` 决定 | 勾选立即提交;多选保持打开,单选提交后关闭 |
| `custom` | 不传 options / 自定义对象 | 打开时深拷贝草稿,确定后提交;重置提交 `resetValue` |

`TableFilterOption` 位于 `global/mk-table/mk-table-filter/types.ts`,包含 `label`、`value` 和可选
`disabled`;扁平选项不使用空字符串作为业务值。`false`、`0` 都是有效筛选条件,正确高亮筛选图标。
Expand All @@ -310,7 +312,14 @@ Dialog、Drawer、Popover、嵌套区域等其他大、小表格均禁止开启
外部模型变更同步面板;自身提交后的模型回写保留原始父组勾选,关闭再打开也保持。
通过 `cascaderProps` 覆盖字段映射、单多选、路径输出及懒加载等原生配置。
级联多选重置为 `[]`,单选重置为 `null`;单选模型需声明可空类型。
`width` 可覆盖弹层宽度,扁平模式默认 192px,级联默认自适应。
`width` 可覆盖弹层宽度,扁平模式默认 192px,级联默认自适应,自定义模式默认 240px。

自定义模式通过默认作用域插槽接收 `{ value }`,`value` 是可编辑的独立对象草稿,字段类型跟随
`v-model` 推断;页面用 `v-model="value.xxx"` 绑定输入,不直接修改页面已生效的筛选。
`resetValue` 传入完整的默认对象,未传时回退 `{}`;固定表单字段应显式传入,避免重置后缺失字段。
重置和确定都关闭浮层、回写模型并触发 `change`;点击外部关闭不提交,再次打开按当前模型回填。
草稿、提交值和默认值之间使用 Lodash 深拷贝隔离,图标按已生效对象是否与 `resetValue` 深度相等决定高亮。
自定义内容放在内置滚动区,页面不再编写 Popover、开关状态或重置、确定按钮。

`change(value)` 在写回模型后触发,类型跟随模型;`open` 在每次打开时触发,供页面加载选项。
组件不请求业务接口、不转换查询参数。文档文件状态用唯一选项值映射为 `status` 和 `task_type`,
Expand All @@ -334,6 +343,24 @@ Dialog、Drawer、Popover、嵌套区域等其他大、小表格均禁止开启
<MkTableFilter v-model="tagIds" mode="cascader" label="标签" :options="tagOptions" @open="loadTagOptions" @change="reload" />
```

```vue
<!-- 自定义反馈筛选;feedback 为 { min_star: 0, min_trample: 0 } -->
<MkTableFilter
v-model="feedback"
mode="custom"
label="用户反馈"
:reset-value="{ min_star: 0, min_trample: 0 }"
@change="reload"
>
<template #default="{ value }">
<div class="space-y-3">
<el-input-number v-model="value.min_star" :min="0" :value-on-clear="0" controls-position="right" align="left" />
<el-input-number v-model="value.min_trample" :min="0" :value-on-clear="0" controls-position="right" align="left" />
</div>
</template>
</MkTableFilter>
```

表格操作列需要 More 菜单时使用 `MkTableMoreDropdown`。组件统一提供点击型、右下定位的 More
按钮以及 `MkDropdownMenu`,默认插槽中直接放置 `MkDropdownItem`;插槽为空,或其中的条件菜单项
均未渲染时,不显示 More 触发器。其他 Dropdown 属性和事件通过 `$attrs` 传入,菜单容器样式通过
Expand Down
Loading
Loading