diff --git a/ui/src/api/admin/workspace/model/model.ts b/ui/src/api/admin/workspace/model/model.ts index ce0eac7b41f..f294c078435 100644 --- a/ui/src/api/admin/workspace/model/model.ts +++ b/ui/src/api/admin/workspace/model/model.ts @@ -42,6 +42,8 @@ const putPauseModelDownload = (modelId: string) => { return put(`${getPrefix()}/${modelId}/pause_download`) } + + export default { deleteModel, getModelDetail, diff --git a/ui/src/assets/tool/icon_datasource.svg b/ui/src/assets/tool/icon_datasource.svg new file mode 100644 index 00000000000..88862f01dd1 --- /dev/null +++ b/ui/src/assets/tool/icon_datasource.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/src/assets/tool/icon_mcp.svg b/ui/src/assets/tool/icon_mcp.svg new file mode 100644 index 00000000000..5f73dcaa283 --- /dev/null +++ b/ui/src/assets/tool/icon_mcp.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/src/assets/tool/icon_skill.svg b/ui/src/assets/tool/icon_skill.svg new file mode 100644 index 00000000000..e783d057148 --- /dev/null +++ b/ui/src/assets/tool/icon_skill.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/src/assets/tool/icon_tool.svg b/ui/src/assets/tool/icon_tool.svg new file mode 100644 index 00000000000..1544370a29e --- /dev/null +++ b/ui/src/assets/tool/icon_tool.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/assets/tool/icon_tool_custom.svg b/ui/src/assets/tool/icon_tool_custom.svg new file mode 100644 index 00000000000..6a759b9238c --- /dev/null +++ b/ui/src/assets/tool/icon_tool_custom.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/assets/workflow/logo_workflow.svg b/ui/src/assets/workflow/logo_workflow.svg new file mode 100644 index 00000000000..7de1fb4227f --- /dev/null +++ b/ui/src/assets/workflow/logo_workflow.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ui/src/components.d.ts b/ui/src/components.d.ts index 1aaecfee17b..e7165ea46b7 100644 --- a/ui/src/components.d.ts +++ b/ui/src/components.d.ts @@ -23,7 +23,7 @@ declare module 'vue' { MkEmpty: typeof import('./components/global/mk-empty/index.vue')['default'] MkFilterableDropdown: typeof import('./components/global/mk-filterable-dropdown/index.vue')['default'] MkIcon: typeof import('./components/global/mk-icon/index.vue')['default'] - MkLoadMore: typeof import('./components/global/mk-load-more/index.vue')['default'] + MkInfiniteScroll: typeof import('./components/global/mk-infinite-scroll/index.vue')['default'] MkSearchInput: typeof import('./components/global/mk-search-input/index.vue')['default'] MkStatusLabel: typeof import('./components/global/mk-status-label/index.vue')['default'] MkTable: typeof import('./components/global/mk-table/index.vue')['default'] @@ -31,6 +31,7 @@ declare module 'vue' { MkViewLayout: typeof import('./components/global/mk-view-layout/index.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + ToolIcon: typeof import('./components/global/mk-icon/ToolIcon.vue')['default'] } } @@ -47,7 +48,7 @@ declare global { const MkEmpty: typeof import('./components/global/mk-empty/index.vue')['default'] const MkFilterableDropdown: typeof import('./components/global/mk-filterable-dropdown/index.vue')['default'] const MkIcon: typeof import('./components/global/mk-icon/index.vue')['default'] - const MkLoadMore: typeof import('./components/global/mk-load-more/index.vue')['default'] + const MkInfiniteScroll: typeof import('./components/global/mk-infinite-scroll/index.vue')['default'] const MkSearchInput: typeof import('./components/global/mk-search-input/index.vue')['default'] const MkStatusLabel: typeof import('./components/global/mk-status-label/index.vue')['default'] const MkTable: typeof import('./components/global/mk-table/index.vue')['default'] @@ -55,4 +56,5 @@ declare global { const MkViewLayout: typeof import('./components/global/mk-view-layout/index.vue')['default'] const RouterLink: typeof import('vue-router')['RouterLink'] const RouterView: typeof import('vue-router')['RouterView'] + const ToolIcon: typeof import('./components/global/mk-icon/ToolIcon.vue')['default'] } \ No newline at end of file diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 13a69718dd3..e17c2172eec 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -65,7 +65,7 @@ src/components/ │ │ └── index.vue # 带搜索过滤和滚动列表的下拉选择 │ ├── mk-icon/ │ │ └── index.vue # SVG Symbol 与 Element Plus 图标统一入口 -│ ├── mk-load-more/ +│ ├── mk-infinite-scroll/ │ │ └── index.vue # 分页列表滚动触底加载与结束状态 │ ├── mk-view-layout/ │ │ ├── index.vue # 路由页面标题、操作区和内容区统一结构 @@ -319,13 +319,15 @@ Element Plus Dropdown 属性和事件通过 `$attrs` 传入,并暴露 `handleO 仍可自行使用 `el-scrollbar`。页面可以在同一个插槽中组织标题、内容和空状态,并只写一次业务 状态判断。传入 `aside` 插槽后才会渲染左侧栏;左右结构上方的独立内容放入 `top` 插槽。页面加载状态通过 `loading` Prop 传入,由组件将 Element Plus Loading 遮罩绑定到整个布局根节点。默认插槽没有 -渲染 `Header` 时会自动显示当前标题;显式渲染后则由页面控制标题内容。 +渲染 `Header` 时会自动显示当前标题;显式渲染后则由页面控制标题内容。`collapsible` 默认为 +`false`;传入后,展开状态仅在鼠标移入侧栏或焦点进入侧栏时显示收起按钮,收起状态始终显示展开 +按钮。收起时释放侧栏宽度,页面不需要自行维护折叠状态。 主内容区固定保留 `px-6` 水平留白。内置滚动容器会抵消两侧留白,再为滚动内容补回 `24px` 水平间距,使滚动条贴齐主区域右边界,并允许 `MkTable` 的底部操作栏延伸至完整主区域宽度。 ```vue - + @@ -368,17 +370,19 @@ Element Plus Dropdown 属性和事件通过 `$attrs` 传入,并暴露 `handleO ``` -### MkLoadMore +### MkInfiniteScroll 分页列表的滚动触底加载组件,使用 `IntersectionObserver` 监听组件所在滚动区域的底部,不依赖 -Element Plus 的 `v-infinite-scroll`。通过 `paginationConfig` 接收 `currentPage`、`pageSize` 和 -`total`,组件内部判断是否加载完成;接近底部时,`load` 事件返回下一页页码。`loading` 用于阻止 -重复请求并展示加载状态,`disabled` 可临时禁用加载,`distance` 设置提前触发距离且默认为 -`200px`。数据不足以撑满滚动区域时会继续加载,直至填满或全部加载完成。`loading` 和 `finished` -插槽可覆盖默认提示。 +Element Plus 的 `v-infinite-scroll`。组件通过 `v-model` 管理已经加载的列表数据,通过 `load` +传入按页请求方法,并在内部管理页码、首次加载、数据追加、加载状态、结束状态和过期请求。请求 +方法接收 `{ currentPage, pageSize }`,返回包含 `records`、`current`、`size` 和 `total` 的分页结果。 +默认每页加载 20 条,可通过 `pageSize` 修改。组件挂载后自动加载第一页,之后只在底部哨兵随 +用户滚动进入可视区域时加载下一页。查询条件变化时通过组件暴露的 `reset()` 重新加载。 ```vue - + + + ``` ### MkSearchInput diff --git a/ui/src/components/global/mk-icon/ToolIcon.vue b/ui/src/components/global/mk-icon/ToolIcon.vue new file mode 100644 index 00000000000..92a9dbff4ad --- /dev/null +++ b/ui/src/components/global/mk-icon/ToolIcon.vue @@ -0,0 +1,36 @@ + + diff --git a/ui/src/components/global/mk-infinite-scroll/index.vue b/ui/src/components/global/mk-infinite-scroll/index.vue new file mode 100644 index 00000000000..6a077f055dd --- /dev/null +++ b/ui/src/components/global/mk-infinite-scroll/index.vue @@ -0,0 +1,116 @@ + + + diff --git a/ui/src/components/global/mk-load-more/index.vue b/ui/src/components/global/mk-load-more/index.vue deleted file mode 100644 index 602cd3b03c1..00000000000 --- a/ui/src/components/global/mk-load-more/index.vue +++ /dev/null @@ -1,100 +0,0 @@ - - - diff --git a/ui/src/components/global/mk-view-layout/index.vue b/ui/src/components/global/mk-view-layout/index.vue index b640cb30749..8ca5b92181b 100644 --- a/ui/src/components/global/mk-view-layout/index.vue +++ b/ui/src/components/global/mk-view-layout/index.vue @@ -8,10 +8,12 @@ defineOptions({ name: 'MkViewLayout', inheritAttrs: false }) const props = withDefaults( defineProps<{ + collapsible?: boolean loading?: boolean title?: string }>(), { + collapsible: false, loading: false, }, ) @@ -72,7 +74,7 @@ const LayoutContent: FunctionalComponent = () => {
- + diff --git a/ui/src/components/global/mk-view-layout/layout-aside.vue b/ui/src/components/global/mk-view-layout/layout-aside.vue index 5d05927415c..d53bad92768 100644 --- a/ui/src/components/global/mk-view-layout/layout-aside.vue +++ b/ui/src/components/global/mk-view-layout/layout-aside.vue @@ -4,16 +4,23 @@ import { Fragment, h, isVNode, + ref, type FunctionalComponent, type VNode, useSlots, } from 'vue' -defineOptions({ name: 'MkViewLayoutAside' }) +defineOptions({ name: 'LayoutAside' }) -const props = defineProps<{ - title?: string -}>() +const props = withDefaults( + defineProps<{ + collapsible?: boolean + title?: string + }>(), + { + collapsible: false, + }, +) defineSlots<{ default?: (props: { Header: FunctionalComponent; title: string }) => unknown @@ -21,6 +28,8 @@ defineSlots<{ const title = computed(() => props.title || '') const slots = useSlots() +const isCollapsed = ref(false) +const asideCollapsed = computed(() => props.collapsible && isCollapsed.value) function flattenSlotNodes(children: unknown): VNode[] { const childNodes = Array.isArray(children) ? children : [children] @@ -48,10 +57,47 @@ const LayoutAsideContent: FunctionalComponent = () => { ...contentNodes, ] } + +function toggleAside(event: MouseEvent) { + isCollapsed.value = !isCollapsed.value + + if (event.detail > 0) { + const toggleButton = event.currentTarget as HTMLButtonElement + toggleButton.blur() + } +} diff --git a/ui/src/components/mk-search-list/mk-list-item.vue b/ui/src/components/mk-search-list/mk-list-item.vue index e93cd5a16ec..cf84119db59 100644 --- a/ui/src/components/mk-search-list/mk-list-item.vue +++ b/ui/src/components/mk-search-list/mk-list-item.vue @@ -3,7 +3,12 @@ import { computed } from 'vue' defineOptions({ name: 'MkListItem' }) -const { active = false, index = 0, labelField, row } = defineProps<{ +const { + active = false, + index = 0, + labelField, + row, +} = defineProps<{ active?: boolean index?: number labelField?: keyof T & string diff --git a/ui/src/components/mk-source-card/index.vue b/ui/src/components/mk-source-card/index.vue index 9a8ff5a873f..d6783698815 100644 --- a/ui/src/components/mk-source-card/index.vue +++ b/ui/src/components/mk-source-card/index.vue @@ -30,8 +30,8 @@ import MkSourceCardActionDropdown from './mk-source-card-action-dropdown.vue' defineOptions({ name: 'MkSourceCard' }) const props = defineProps<{ - create_time?: string - nick_name?: string + create_time?: string //创建日期 + nick_name?: string // 创建者 title: string }>() @@ -75,7 +75,7 @@ const slots = defineSlots<{ -
+
diff --git a/ui/src/styles/STYLE_README.md b/ui/src/styles/STYLE_README.md index a526e246795..e175ddadc52 100644 --- a/ui/src/styles/STYLE_README.md +++ b/ui/src/styles/STYLE_README.md @@ -154,7 +154,8 @@ CSS 自定义属性同样按变量名排序。嵌套选择器、伪类和媒体 ## 常用交互工具类 `group-hover-visible` 用于列表项、卡片等父级带有 `group` 的悬浮操作区。操作区默认透明且不响应 -鼠标事件;父级悬浮或内部获得焦点时显示并恢复交互。该工具类只负责显隐,布局继续由使用方组合。 +鼠标事件;距离操作区最近的 `group` 悬浮或内部获得焦点时显示并恢复交互,外层 `group` 不会越过 +内层 `group` 触发操作区。该工具类只负责显隐,布局继续由使用方组合。 ```html
diff --git a/ui/src/styles/app.scss b/ui/src/styles/app.scss index 52b1a350121..8a73437b338 100644 --- a/ui/src/styles/app.scss +++ b/ui/src/styles/app.scss @@ -88,3 +88,10 @@ h6 { margin-left: 4px; } } + +// 资源的card grid布局 +.mk-resource-card-grid { + display: grid; + gap: 16px; + grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); +} diff --git a/ui/src/styles/element-plus.scss b/ui/src/styles/element-plus.scss index bf61f4c95cb..2b7370bb820 100644 --- a/ui/src/styles/element-plus.scss +++ b/ui/src/styles/element-plus.scss @@ -42,6 +42,12 @@ --el-disabled-text-color: var(--mk-N400); } +/* avatar */ +.el-avatar { + --el-avatar-bg-color: #3370ff; + flex-shrink: 0; +} + /* button */ .el-button { --el-button-font-weight: 400 !important; @@ -89,7 +95,7 @@ &:hover { box-shadow: 0px 2px 4px 0px rgb(var(--mk-N900-rgb) / 12%); color: var(--el-text-color-primary); - background: none; + background: transparent; border-color: var(--el-border-color); } } diff --git a/ui/src/styles/tailwind.css b/ui/src/styles/tailwind.css index 750ff5aee72..9be5e8f5807 100644 --- a/ui/src/styles/tailwind.css +++ b/ui/src/styles/tailwind.css @@ -6,6 +6,7 @@ --color-danger: var(--mk-danger); --color-success: var(--mk-success); --color-warning: var(--mk-warning); + --color-purple: var(--mk-purple); --color-N100: var(--mk-N100); --color-N300: var(--mk-N300); --color-N350: var(--mk-N350); @@ -23,6 +24,9 @@ --spacing-sidebar: var(--mk-sidebar-width); --spacing-sidebar-expanded: var(--mk-sidebar-expanded-width); + /* shadows */ + --shadow-md: var(--el-box-shadow-light); + /* typography */ --text-xs: 10px; --text-xs--line-height: 14px; @@ -85,9 +89,14 @@ transition-property: opacity; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - :where(.group):hover &, - :where(.group):focus-within & { + :where(.group):is(:hover, :focus-within) & { opacity: 1; pointer-events: auto; } + + /* 存在更近但未激活的 group 时,取消外层 group 传递下来的显示状态 */ + :where(.group):is(:hover, :focus-within) :where(.group:not(:hover):not(:focus-within)) & { + opacity: 0; + pointer-events: none; + } } diff --git a/ui/src/styles/variables.scss b/ui/src/styles/variables.scss index ee1efd98f60..1a7236cbd96 100644 --- a/ui/src/styles/variables.scss +++ b/ui/src/styles/variables.scss @@ -18,6 +18,7 @@ rgba(235, 241, 255, 0.35) 39.6%, rgba(231, 249, 255, 0.35) 94.3% ); + --mk-purple: #7f3bf5; // 用于表头背景。 --mk-N100: #f5f6f7; --mk-N100-rgb: 245 246 247; diff --git a/ui/src/utils/common.ts b/ui/src/utils/common.ts index a4b1147ed73..39e930e6d8a 100644 --- a/ui/src/utils/common.ts +++ b/ui/src/utils/common.ts @@ -5,3 +5,13 @@ import { nanoid } from 'nanoid' export function randomId() { return nanoid() } + +/* + icon url +*/ +export const resetUrl = (url: string, defaultUrl?: string) => { + if (url && url.startsWith('./')) { + return `${window.MaxKB?.prefix}/${url.substring(2)}` + } + return url ? url : defaultUrl ? defaultUrl : '' +} diff --git a/ui/src/views/model/ModelView.vue b/ui/src/views/model/ModelView.vue index 6c5d3c7d8d0..a19b5174f16 100644 --- a/ui/src/views/model/ModelView.vue +++ b/ui/src/views/model/ModelView.vue @@ -102,7 +102,7 @@ onMounted(() => {