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
40 changes: 38 additions & 2 deletions ui/src/api/admin/workspace/application/application.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { get, post, put } from '../../core/request'
import { del, get, getExportFile, post, put } from '../../core/request'
import type { ParamsPage, ResponsePage } from '../../core/types'
import type { ApplicationDetail, ApplicationFormPayload, Dict } from '@/api/types'
import { getWorkspaceId } from '@/utils/resource-context'
Expand All @@ -18,6 +18,16 @@ const getApplicationDetail = (applicationId: string) => {
return get<ApplicationDetail>(`${getPrefix()}/${applicationId}`)
}

/** 删除工作空间智能体。 */
const deleteApplication = (applicationId: string) => {
return del<undefined, boolean>(`${getPrefix()}/${applicationId}`)
}

/** 导出工作空间智能体文件。 */
const exportApplication = (applicationId: string, applicationName: string) => {
return getExportFile(`${applicationName}.mk`, `${getPrefix()}/${applicationId}/export`)
}

/** 导入智能体文件并创建工作空间智能体。 */
const postApplicationImport = (file: File, folderId: string) => {
const payload = new FormData()
Expand All @@ -30,9 +40,35 @@ const putApplication = (applicationId: string, data: ApplicationFormPayload) =>
return put<ApplicationFormPayload, ApplicationDetail>(`${getPrefix()}/${applicationId}`, data)
}

/** 移动工作空间智能体。 */
const putMoveApplication = (applicationId: string, folderId: string) => {
return put<Record<string, never>, boolean>(`${getPrefix()}/${applicationId}/move/${folderId}`, {})
}

/** 批量删除工作空间智能体。 */
const putBatchDeleteApplications = (applicationIds: string[]) => {
return put<{ id_list: string[] }, boolean>(`${getPrefix()}/batch_delete`, { id_list: applicationIds })
}

/** 批量移动工作空间智能体。 */
const putBatchMoveApplications = (applicationIds: string[], folderId: string) => {
return put<{ folder_id: string; id_list: string[] }, boolean>(`${getPrefix()}/batch_move`, { folder_id: folderId, id_list: applicationIds })
}

/** 发布工作空间智能体。 */
const putApplicationPublish = (applicationId: string) => {
return put<Record<string, never>, ApplicationDetail>(`${getPrefix()}/${applicationId}/publish`, {})
}

export default { getApplicationPage, getApplicationDetail, postApplicationImport, putApplication, putApplicationPublish }
export default {
getApplicationPage,
getApplicationDetail,
deleteApplication,
exportApplication,
postApplicationImport,
putApplication,
putMoveApplication,
putBatchDeleteApplications,
putBatchMoveApplications,
putApplicationPublish,
}
2 changes: 1 addition & 1 deletion ui/src/assets/iconfont.js

Large diffs are not rendered by default.

26 changes: 20 additions & 6 deletions ui/src/components/business/folder-tree/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@ const MoveToDialog = defineAsyncComponent(() => import('./MoveToDialog.vue'))
defineOptions({ name: 'FolderTree' })

const props = withDefaults(
defineProps<{ canEdit?: boolean; disabledFolderIds?: string[]; draggable?: boolean; rootLabel?: string; showAll?: boolean; showShared?: boolean; source: FolderSource }>(),
defineProps<{
canEdit?: boolean
disabledFolderIds?: string[]
draggable?: boolean
rootLabel?: string
showAll?: boolean
showShared?: boolean
source: FolderSource
}>(),
{ canEdit: true, disabledFolderIds: () => [], draggable: false, rootLabel: '', showAll: true, showShared: true },
)

Expand Down Expand Up @@ -92,10 +100,13 @@ function sortFolders(folders: FolderItem[], parentId: string, positionCache = re
[FOLDER_SORT.CREATE_TIME_DESC]: (left, right) => new Date(right.create_time ?? 0).getTime() - new Date(left.create_time ?? 0).getTime(),
[FOLDER_SORT.NAME_ASC]: (left, right) => left.name.localeCompare(right.name),
[FOLDER_SORT.NAME_DESC]: (left, right) => right.name.localeCompare(left.name),
[FOLDER_SORT.CUSTOM]: (left, right) => (customPositions[left.id] ?? Number.MAX_SAFE_INTEGER) - (customPositions[right.id] ?? Number.MAX_SAFE_INTEGER),
[FOLDER_SORT.CUSTOM]: (left, right) =>
(customPositions[left.id] ?? Number.MAX_SAFE_INTEGER) - (customPositions[right.id] ?? Number.MAX_SAFE_INTEGER),
}

return [...folders].sort(compareMethods[currentSort.value]).map((folder) => ({ ...folder, children: sortFolders(folder.children ?? [], folder.id, positionCache) }))
return [...folders]
.sort(compareMethods[currentSort.value])
.map((folder) => ({ ...folder, children: sortFolders(folder.children ?? [], folder.id, positionCache) }))
}

function readCustomPositions(): Record<string, Record<string, number>> {
Expand All @@ -109,7 +120,9 @@ function writeCustomPositions(positionCache: Record<string, Record<string, numbe

function collectCustomPositions(parentId: string, folders: FolderItem[], positionCache: Record<string, Record<string, number>>) {
const savedPositions = positionCache[parentId] ?? {}
const orderedFolders = [...folders].sort((left, right) => (savedPositions[left.id] ?? Number.MAX_SAFE_INTEGER) - (savedPositions[right.id] ?? Number.MAX_SAFE_INTEGER))
const orderedFolders = [...folders].sort(
(left, right) => (savedPositions[left.id] ?? Number.MAX_SAFE_INTEGER) - (savedPositions[right.id] ?? Number.MAX_SAFE_INTEGER),
)

positionCache[parentId] = Object.fromEntries(orderedFolders.map((folder, index) => [folder.id, index + 1]))
orderedFolders.forEach((folder) => {
Expand Down Expand Up @@ -261,7 +274,8 @@ function getFolderDeleteContext(folder: FolderItem) {
// 顶级文件夹按当前展示顺序回退到下一个、上一个或“全部”。
const siblingFolders = sortTreeData.value
const folderIndex = siblingFolders.findIndex(({ id }) => id === deletedFolder.id)
const targetFolder = folderIndex < 0 ? folderEntries.value.all : (siblingFolders[folderIndex + 1] ?? siblingFolders[folderIndex - 1] ?? folderEntries.value.all)
const targetFolder =
folderIndex < 0 ? folderEntries.value.all : (siblingFolders[folderIndex + 1] ?? siblingFolders[folderIndex - 1] ?? folderEntries.value.all)
return { positionCache, targetFolder }
}

Expand Down Expand Up @@ -311,7 +325,7 @@ defineExpose({ refresh: loadFolders, openCreate: handleOpenCreateFolder })
<div class="flex shrink-0 items-center gap-2 px-4 pb-2">
<MkSearchInput v-model="searchKeyword" class="min-w-0 flex-1" />
<MkDropdown trigger="click" placement="bottom-end">
<el-button class="shrink-0 min-w-8! w-8!">
<el-button plain class="shrink-0 min-w-8! w-8!">
<MkIcon name="icon_moments-categories_outlined" />
</el-button>
<template #dropdown>
Expand Down
26 changes: 14 additions & 12 deletions ui/src/components/mk-drag-upload/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,21 @@ defineExpose({ clearFiles })
<template>
<div class="w-full">
<template v-if="selectedFile">
<div class="flex items-center gap-2 rounded-md border px-3 py-2">
<img :src="getFileIconUrl(selectedFile.name)" alt="" class="w-10 shrink-0 object-contain" />
<div class="min-w-0 flex-1">
<p class="truncate" :title="selectedFile.name">{{ selectedFile.name }}</p>
<span class="text-sm text-N500">{{ formatFileSize(selectedFile.size) }}</span>
<el-card class="small" shadow="never">
<div class="flex items-center gap-2">
<img :src="getFileIconUrl(selectedFile.name)" alt="" class="w-10 shrink-0 object-contain" />
<div class="min-w-0 flex-1">
<p class="truncate" :title="selectedFile.name">{{ selectedFile.name }}</p>
<span class="text-sm text-N500">{{ formatFileSize(selectedFile.size) }}</span>
</div>
<div class="flex shrink-0 items-center gap-1">
<slot name="download" :file="selectedFile" />
<el-button aria-label="删除文件" :disabled="disabled" text @click="handleRemove">
<MkIcon name="icon_delete-trash_outlined" :size="16" class="text-N600" />
</el-button>
</div>
</div>
<div class="flex shrink-0 items-center gap-1">
<slot name="download" :file="selectedFile" />
<el-button aria-label="删除文件" :disabled="disabled" text @click="handleRemove">
<MkIcon name="icon_delete-trash_outlined" :size="16" class="text-N600" />
</el-button>
</div>
</div>
</el-card>
<div class="mt-2 flex gap-3">
<el-upload
ref="uploadRef"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ const formData = ref({
const conditionRows = computed<VisibilityConditionState[]>({
get: () => formData.value.conditions,
set: (conditions) => {
formData.value.conditions = conditions.map((condition) => (condition.id ? condition : { ...condition, id: randomId(), field: [...condition.field] as [string, string] }))
formData.value.conditions = conditions.map((condition) =>
condition.id ? condition : { ...condition, id: randomId(), field: [...condition.field] as [string, string] },
)
},
})

Expand Down Expand Up @@ -78,7 +80,12 @@ function render(rules: VisibilityRules | null) {
if (rules && rules.conditions?.length) {
formData.value.action = rules.action
formData.value.condition = rules.condition
formData.value.conditions = rules.conditions.map((c) => ({ id: c.id || randomId(), field: [c.field[0], c.field[1]], compare: c.compare, value: c.value }))
formData.value.conditions = rules.conditions.map((c) => ({
id: c.id || randomId(),
field: [c.field[0], c.field[1]],
compare: c.compare,
value: c.value,
}))
formData.value.conditions.forEach((cond) => {
if (cond.field && cond.field[0] && cond.field[1]) {
const fieldType = inferFieldType(cond.field, props.leftOptions)
Expand All @@ -105,7 +112,7 @@ defineExpose({ getData, render, validate })
</script>

<template>
<div class="flex flex-col gap-4 w-full">
<div class="w-full space-y-4">
<el-radio-group v-model="formData.action">
<el-radio value="show">满足条件时显示</el-radio>
<el-radio value="hide">满足条件时隐藏</el-radio>
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/mk-search-list/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function selectRow(row: T, index: number) {
<MkSearchInput v-model="searchKeyword" class="shrink-0 px-4" />

<el-scrollbar ref="scrollbarRef" class="min-h-0 flex-1 px-4 py-2" @end-reached="loadMore">
<div v-if="filteredData.length" class="flex flex-col gap-1">
<div v-if="filteredData.length" class="space-y-1">
<template v-for="(row, index) in renderData" :key="String(row[valueField] ?? index)">
<MkListItem :active="currentValue === row[valueField]" :index="index" :label-field="labelField" :row="row" @click="selectRow(row, index)">
<template v-if="$slots.default" #default="slotProps">
Expand Down
5 changes: 4 additions & 1 deletion ui/src/layout/avatar-dropdown/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ function handleLogout() {
</template>
</MkDropdown>
</MkDropdownItem>
<MkDropdownItem divided :icon="Setting" @click="handleLogout">退出登录</MkDropdownItem>
<MkDropdownItem divided @click="handleLogout">
<template #icon><MkIcon name="icon_logout_outlined" /></template>
<span>退出登录</span>
</MkDropdownItem>
</MkDropdownMenu>
</div>
</template>
Expand Down
2 changes: 1 addition & 1 deletion ui/src/layout/sidebar/WorkspaceSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function navigateToWorkspaceMenu(workspaceMenuItem: LayoutMenuItem) {
</script>

<template>
<div class="flex flex-col gap-1 p-1">
<div class="space-y-1 p-1">
<div
v-for="workspaceMenuItem in workspaceMenuItems"
:key="workspaceMenuItem.name"
Expand Down
12 changes: 11 additions & 1 deletion ui/src/styles/element-plus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
border-color: var(--mk-N350);
}
}
&.is-disabled {
&.is-disabled:not(.is-link, .is-text, .is-circle, .is-plain) {
--el-button-disabled-bg-color: var(--mk-N400);
--el-button-disabled-border-color: var(--mk-N400);
--el-button-disabled-text-color: white;
Expand Down Expand Up @@ -232,6 +232,10 @@
--el-card-border-color: var(--mk-N300);
--el-card-padding: 16px;
overflow: visible;
&.small {
--el-card-border-radius: 6px;
--el-card-padding: 8px 12px;
}
&__body {
overflow: inherit;
}
Expand Down Expand Up @@ -676,12 +680,18 @@
.el-radio-button {
--el-radio-button-checked-bg-color: rgb(var(--mk-primary-rgb) / 10%);
--el-radio-button-checked-text-color: var(--el-color-primary);

&__inner {
border: none !important;
outline: none !important;
padding: 5px 8px;
font-weight: 400;
border-radius: var(--el-border-radius-small) !important;
color: var(--el-text-color-primary);
&:hover {
background-color: rgb(var(--mk-N900-rgb) / 10%);
color: var(--el-text-color-primary);
}
}
&.is-active {
.el-radio-button__inner {
Expand Down
23 changes: 23 additions & 0 deletions ui/src/views/VIEW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,29 @@ System 共享资源的四类特殊资源。它们统一遵循以下页面组织

这套三范围资源规则只适用于上述四类特殊资源,不扩展到普通 System 设置、身份管理或其他页面。

当前智能体列表按卡片展示、菜单 Action 和页面批量流程分层维护:

```text
src/views/application/
├── ApplicationView.vue
├── application-card/
│ ├── ApplicationCard.vue # 智能体卡片展示、选择状态和 Action 插槽
│ └── action-dropdown/
│ ├── index.ts
│ ├── DeleteApplicationAction.vue
│ ├── ExportApplicationAction.vue
│ ├── MoveApplicationAction.vue
│ └── SettingApplicationAction.vue
├── components/
│ └── ApplicationCreateDropdown.vue
└── template.ts
```

`ApplicationCard` 只维护展示内容、批量选择状态和 `action-dropdown` 插槽;`ApplicationView` 组合
设置、移动、导出和删除 Action,并管理批量选择、批量移动和批量删除流程。需要请求的 Action 接收
页面传入的完整 Application API。单项移动在具体目录中通过 `delete` 通知页面局部移除卡片,在
“全部智能体”中保留卡片;移动弹窗复用公共 `MoveToDialog`。

当前工具页面按工具类型组织维护表单,共用的参数和代码设置保留在
`tool-form/component/` 中:

Expand Down
Loading
Loading