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
1 change: 1 addition & 0 deletions ui/src/api/types/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type ApplicationType = (typeof APPLICATION_TYPE)[keyof typeof APPLICATION
export interface ApplicationDetail {
create_time?: string
desc?: string | null
folder?: string
folder_id?: string
icon?: string
id: string
Expand Down
5 changes: 4 additions & 1 deletion ui/src/components/COMPONENT_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,10 @@ Workspace 的文件夹虚拟树业务组件。组件根据当前资源上下文
文件夹树查询、搜索、排序、创建、编辑、移动和删除。通过 `v-model` 控制当前文件夹 ID,首次加载
完成后通过 `loaded` 返回该 ID 对应的完整文件夹,选择文件夹时触发 `select`。传入的 ID 不存在时,
优先回退到“全部”入口,否则回退到首个可用文件夹。`showAll`、`showShared` 分别控制全部和共享入口,
`rootLabel` 可覆盖根入口文案,`disabledFolderIds` 用于只选场景中禁用指定节点。
`rootLabel` 可覆盖根入口文案,`disabledFolderIds` 用于只选场景中禁用指定节点。显示“全部”入口的
主目录树初始化时会读取字符串类型的 `folderId` query 并设置当前文件夹;用户选择文件夹时不把
新 ID 写入路由,但会清除已有的 `folderId`,避免刷新页面后恢复到旧文件夹。隐藏“全部”入口的
移动目录树继续以调用方传入的 `v-model` 为准。

```vue
<script setup lang="ts">
Expand Down
8 changes: 8 additions & 0 deletions ui/src/components/business/folder-tree/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, defineAsyncComponent, onMounted, ref, useTemplateRef } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import FolderApi from '@/api/admin/workspace/folder'
import type { FolderSource, FolderItem } from '@/api/types'
import { FOLDER_SORT, type FolderSort } from './types'
Expand Down Expand Up @@ -28,6 +29,10 @@ const props = withDefaults(
)

const currentNodeKey = defineModel<string>({ default: FOLDER_ENTRY_ID.ALL })
const route = useRoute()
const router = useRouter()
const routeFolderId = route.query.folderId
if (props.showAll && typeof routeFolderId === 'string' && routeFolderId) currentNodeKey.value = routeFolderId

const emit = defineEmits<{ loaded: [folder?: FolderItem]; select: [folder: FolderItem] }>()

Expand Down Expand Up @@ -59,6 +64,9 @@ function loadFolders() {

function handleFolderClick(folder: FolderItem) {
currentNodeKey.value = folder.id
if (props.showAll && route.query.folderId !== undefined) {
void router.replace({ query: { ...route.query, folderId: undefined } })
}
emit('select', folder)
}

Expand Down
9 changes: 6 additions & 3 deletions ui/src/router/ROUTE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ Chat 使用独立入口 `src/chat.ts` 和独立 Router,不要把 Chat 路由
`getChildRouteList(scope)` 相同的过滤、排序和菜单字段转换逻辑生成二级导航。详情子路由通过
`title`、`icon`、`activeIcon` 和 `order` 配置二级菜单;更深层页面通过
`detailActiveMenu` 指定需要保持激活的二级菜单路由名称,不配置 `parentPath` 或 `parentName`。
`ResourceDetailLayout` 切换二级菜单时保留当前 query,详情容器返回列表时也应透传来源 query。
`ResourceDetailLayout` 切换二级菜单时保留当前 query;详情容器返回列表时按业务来源状态透传或
重新生成 query。

路由导航图标统一使用 `src/assets/iconfont.js` 中的完整 Symbol ID 字符串,并由
`MkIcon` 的 `name` 属性渲染。需要在菜单激活后切换图标时,通过 `activeIcon` 配置激活状态
Expand Down Expand Up @@ -196,8 +197,10 @@ Workspace 页面:

智能体详情路由的 `type` 来自当前 `ApplicationDetail.type`;卡片概览和简易智能体设置等详情入口
必须与 `applicationId` 一并传入该参数。高级智能体设置继续进入独立的 Workflow 路由。
智能体列表使用可选的 `folderId` query 保存当前文件夹;进入详情时继续携带该 query,返回列表后
由 `FolderTree` 恢复选中项。`folderId` 是列表来源状态,不作为详情 path 参数。
智能体详情返回列表时,根据 `ApplicationDetail.folder` 通过可选的 `folderId` query 标记所属文件夹;
列表中的 `FolderTree` 初始化时读取该 query 并恢复选中项。用户在列表中切换文件夹时不写入新的
文件夹 ID,只清除已有的 `folderId`;进入详情时也不继续携带该 query。`folderId` 不作为详情 path
参数。

System 复用页面(仅替换根前缀):

Expand Down
6 changes: 3 additions & 3 deletions ui/src/styles/element-plus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1032,10 +1032,10 @@
--el-tag-text-color: var(--el-color-primary-dark-2);
--el-tag-bg-color: rgba(var(--mk-primary-rgb) / 20%);
}
&.el-tag--default {
&.default {
// 默认 tag不能随主题色变化 颜色值不能改变
--el-tag-text-color: #2b5fd9;
--el-tag-bg-color: rgba(51, 112, 255, 0.2);
--el-tag-text-color: #2b5fd9 !important;
--el-tag-bg-color: rgba(51, 112, 255, 0.2) !important;
}
}

Expand Down
8 changes: 5 additions & 3 deletions ui/src/views/VIEW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,11 @@ src/views/application-detail/
设置、移动、导出和删除 Action,并管理批量选择、批量移动和批量删除流程。需要请求的 Action 接收
页面传入的完整 Application API。单项移动在具体目录中通过 `delete` 通知页面局部移除卡片,在
“全部智能体”中保留卡片;移动弹窗复用公共 `MoveToDialog`。非批量选择状态点击卡片时,卡片通过
`open` 事件通知 `ApplicationView` 进入详情路由。列表通过 `folderId` query 保存当前文件夹,进入详情
和返回列表时保持该 query。`WorkspaceApplicationDetail` 负责工作空间智能体详情上下文,共享的返回
入口、二级导航和右侧子路由结构由 `layout/ResourceDetailLayout.vue` 维护。详情子路由通过
`click` 事件通知 `ApplicationView` 进入详情路由。`WorkspaceApplicationDetail` 返回列表时根据详情的
`folder` 字段写入 `folderId` query,由 `FolderTree` 恢复所属文件夹;列表切换文件夹时只清除该
query,不写入新的文件夹 ID,进入详情时也不携带该 query。`WorkspaceApplicationDetail` 负责工作
空间智能体详情上下文,共享的返回入口、二级导航和右侧子路由结构由
`layout/ResourceDetailLayout.vue` 维护。详情子路由通过
`useApplicationDetailContext()` 读取只读详情;接口返回完整详情时调用 `replaceApplicationDetail()`
局部替换,接口只返回布尔值或部分数据时调用 `refreshApplicationDetail()` 重新获取详情。切换二级
菜单不会重新挂载详情容器,也不会自动重复请求详情。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function navigateToApplicationList() {
void router.push({
name: 'workspace-application-list',
params: { workspaceId: route.params.workspaceId },
query: route.query,
query: application.value?.folder ? { folderId: application.value.folder } : {},
})
}

Expand Down
18 changes: 4 additions & 14 deletions ui/src/views/application/ApplicationView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,13 @@ const router = useRouter()

/* 当前文件夹 */
const allApplicationsFolder = FOLDER_ENTRIES[RESOURCE_TYPE.APPLICATION].all
const routeFolderId = typeof route.query.folderId === 'string' && route.query.folderId ? route.query.folderId : FOLDER_ENTRY_ID.ALL
const currentFolderId = ref(routeFolderId)
const currentFolder = ref<FolderItem>({ ...allApplicationsFolder, id: currentFolderId.value })

function syncFolderQuery(folderId: string) {
const folderIdQuery = folderId === FOLDER_ENTRY_ID.ALL ? undefined : folderId
if (route.query.folderId === folderIdQuery) return

void router.replace({ query: { ...route.query, folderId: folderIdQuery } })
}
const currentFolderId = ref<string>(FOLDER_ENTRY_ID.ALL)
const currentFolder = ref<FolderItem>({ ...allApplicationsFolder })

function handleFolderSelect(folder: FolderItem) {
const folderChanged = folder.id !== currentFolder.value.id
currentFolderId.value = folder.id
currentFolder.value = folder
syncFolderQuery(folder.id)
if (folderChanged) {
cancelBatchSelection()
refreshApplications()
Expand Down Expand Up @@ -96,11 +87,10 @@ function handleDeleteApplication(applicationId: string) {
if (applicationIndex >= 0) applicationData.value.splice(applicationIndex, 1)
}

function handleOpenApplication(application: ApplicationDetail) {
function handleToDetail(application: ApplicationDetail) {
void router.push({
name: 'workspace-application-detail',
params: { applicationId: application.id, type: application.type, workspaceId: route.params.workspaceId },
query: route.query,
})
}

Expand Down Expand Up @@ -219,7 +209,7 @@ function handleBatchDelete() {
:application="application"
:selectable="batchSelectionMode"
:selected="selectedApplicationIds.includes(application.id)"
@open="handleOpenApplication(application)"
@click="handleToDetail(application)"
@selected="handleApplicationSelect(application.id, $event)"
>
<template #action-dropdown>
Expand Down
6 changes: 3 additions & 3 deletions ui/src/views/application/application-card/ApplicationCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ defineOptions({ name: 'ApplicationCard' })

const props = defineProps<{ application: ApplicationDetail; selectable?: boolean; selected?: boolean }>()

const emit = defineEmits<{ open: []; selected: [selected: boolean] }>()
const emit = defineEmits<{ click: []; selected: [selected: boolean] }>()

defineSlots<{ 'action-dropdown'?: () => unknown }>()

function handleOpen() {
if (!props.selectable) emit('open')
if (!props.selectable) emit('click')
}
</script>

Expand All @@ -34,7 +34,7 @@ function handleOpen() {

<template #tag>
<el-tag v-if="isWorkFlow(application.type)" type="warning" size="small">高级</el-tag>
<el-tag v-else type="default" size="small">简易</el-tag>
<el-tag v-else type="primary" class="default" size="small">简易</el-tag>
</template>

<p class="line-clamp-2" :title="application.desc ?? undefined">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ function handleDeleteApplication() {
loading.value = true
return props.api
.deleteApplication(props.application.id)
.finally(() => {
loading.value = false
})
.then(() => {
emit('delete', props.application.id)
MsgSuccess('删除成功')
})
.finally(() => {
loading.value = false
})
})
.catch(() => {})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@ const moveToDialogRef = useTemplateRef<{ close: () => void; open: (currentFolder

function handleOpenMoveApplication() {
dialogMounted.value = true
return nextTick(() => moveToDialogRef.value?.open(props.application.folder_id))
return nextTick(() => moveToDialogRef.value?.open(props.application.folder ?? props.currentFolderId))
}

function handleMoveApplication(targetFolderId: string) {
if (loading.value) return
loading.value = true
return props.api
.putMoveApplication(props.application.id, targetFolderId)
.finally(() => {
loading.value = false
})
.then(() => {
MsgSuccess('移动成功')
moveToDialogRef.value?.close()
if (props.currentFolderId !== FOLDER_ENTRY_ID.ALL) emit('delete', props.application.id)
})
.finally(() => {
loading.value = false
})
}

function handleDialogClosed() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ function handleSettingApplication(event: MouseEvent) {
type: props.application.type,
workspaceId: route.params.workspaceId,
},
query: route.query,
})
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ const router = useRouter()

const props = defineProps<{ folderId: string }>()

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

const dialogVisible = ref(false)
const loading = ref(false)
const selectedTemplate = ref<ApplicationTemplateType>('blank')
Expand Down Expand Up @@ -64,7 +62,6 @@ function submit() {
return auth.loadAuthBaseProfile().then(() => {
MsgSuccess('创建成功')
dialogVisible.value = false
emit('refresh')
return router.push({
name: 'workflow-application',
params: { applicationId: application.id, workspaceId: route.params.workspaceId },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,6 @@ function handleRefresh() {
</template>
</MkDropdown>

<SimpleCreateDialog ref="simpleCreateDialogRef" :folder-id="folderId" @refresh="handleRefresh" />
<AdvancedCreateDialog ref="advancedCreateDialogRef" :folder-id="folderId" @refresh="handleRefresh" />
<SimpleCreateDialog ref="simpleCreateDialogRef" :folder-id="folderId" />
<AdvancedCreateDialog ref="advancedCreateDialogRef" :folder-id="folderId" />
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ const router = useRouter()

const props = defineProps<{ folderId: string }>()

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

const dialogVisible = ref(false)
const loading = ref(false)
const applicationFormRef = ref<FormInstance>()
Expand Down Expand Up @@ -73,7 +71,6 @@ function submit() {
return auth.loadAuthBaseProfile().then(() => {
MsgSuccess('创建成功')
dialogVisible.value = false
emit('refresh')
return router.push({
name: 'workspace-application-simple-setting',
params: { applicationId: application.id, type: application.type, workspaceId: route.params.workspaceId },
Expand Down
6 changes: 3 additions & 3 deletions ui/src/views/knowledge/components/KnowledgeCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ function handleDeleteKnowledge() {
.then(() => {
loading.value = true
return KnowledgeApi.deleteKnowledge(props.knowledge.id)
.finally(() => {
loading.value = false
})
.then(() => {
emit('delete', props.knowledge.id)
MsgSuccess('删除成功')
})
.finally(() => {
loading.value = false
})
})
.catch(() => {})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ function handleDeleteTool() {
loading.value = true
return props.api
.deleteTool(props.tool.id)
.finally(() => (loading.value = false))
.then(() => {
emit('delete', props.tool.id)
MsgSuccess('删除成功')
})
.finally(() => (loading.value = false))
})
.catch(() => {})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ function handleMoveTool(targetFolderId: string) {
loading.value = true
return props.api
.putTool(props.tool.id, { folder_id: targetFolderId })
.finally(() => {
loading.value = false
})
.then(() => {
MsgSuccess('移动成功')
moveToDialogRef.value?.close()
if (props.currentFolderId !== FOLDER_ENTRY_ID.ALL) emit('delete', props.tool.id)
})
.finally(() => {
loading.value = false
})
}

function handleDialogClosed() {
Expand Down
Loading