Skip to content

Commit af8c50a

Browse files
feat: default mode
1 parent 88e2db7 commit af8c50a

39 files changed

Lines changed: 814 additions & 887 deletions

File tree

ui/src/api/API_README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文
114114
- 同一业务边界内的重复类型提取到最近共同目录的 `common.ts``common.ts` 不得成为无关类型
115115
的集合。
116116
- API 与 View 或 Component 使用同一业务类型时只保留一份声明,不得在两层分别定义。
117+
- 智能体详情及保存参数中的 `default_model_setting` 共用模型类别 `DefaultModelType` 和单项
118+
配置 `ModelConfig``types/model.ts`),工作流页面及设置抽屉从 `@/api/types` 引用。
117119
- 名称相同但业务含义或字段约束不同的类型不要强行合并,应使用明确的领域名称区分。
118120
- 使用 `interface` 描述对象结构,使用 `type` 描述联合类型、交叉类型、工具类型结果或别名。
119121
- 类型名称必须体现业务含义,避免使用 `Data``Item``Info` 等脱离领域后含义不清的名称。

ui/src/api/types/application.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import type LogicFlow from '@logicflow/core'
44
import { APPLICATION_TYPE } from '@/api/enums'
5+
import type { DefaultModelType, ModelConfig } from './model'
56

67
export type ApplicationType = (typeof APPLICATION_TYPE)[keyof typeof APPLICATION_TYPE]
78

89
export interface ApplicationDetail {
10+
default_model_setting?: Partial<Record<DefaultModelType, ModelConfig>>
911
create_time?: string
1012
desc?: string | null
1113
folder?: string
@@ -27,6 +29,7 @@ export interface ApplicationDetail {
2729
}
2830

2931
export interface ApplicationFormPayload {
32+
default_model_setting?: ApplicationDetail['default_model_setting']
3033
name?: string
3134
desc?: string
3235
model_id?: string

ui/src/api/types/model.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
/** Workspace 模型与工具页面共用的业务类型。 */
22

33
import { MODEL_STATUS } from '@/api/enums'
4+
import type { Dict } from './common'
45

56
export type ModelStatus = (typeof MODEL_STATUS)[keyof typeof MODEL_STATUS]
67

8+
/** 智能体默认模型配置支持的模型类别。 */
9+
export type DefaultModelType = 'LLM' | 'TTS' | 'STT' | 'IMAGE' | 'TTI' | 'TTV' | 'ITV' | 'RERANKER'
10+
11+
export interface ModelConfig {
12+
model_id?: string
13+
model_params_setting?: Dict<unknown>
14+
}
15+
716
export interface ModelProviderItem {
817
icon: string
918
name: string

ui/src/components/COMPONENT_README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,11 +1053,22 @@ import FolderTree from '@/components/business/folder-tree/index.vue'
10531053
按供应商分组展示模型,通过 `v-model` 控制模型 ID,并在选择变化时触发 `change``options`
10541054
`ModelItem[]``providerOptions``ModelProviderItem[]`,模型列表和供应商列表均由使用方查询。
10551055

1056-
`showModelParams` 默认为 `false`,设为 `true` 时显示参数按钮,设为 `false` 时隐藏。
1056+
`canEditParams` 默认为 `false`,设为 `true` 时显示参数按钮,设为 `false` 时隐藏。
1057+
参数按钮位于选择框右侧内部,与选择框共用外边框,并通过短竖线与下拉箭头分隔。
10571058
未选择模型或传入 `disabled` 时按钮禁用;点击打开组件内的 `ModelParamsDialog`
10581059
通过 `v-model:model-params` 绑定参数:切换模型时加载默认值,清空模型时清空参数,弹窗确认后
10591060
回写配置,取消不修改已保存参数。参数表单沿用上层提供的 `getModelParamsForm(modelId)` 注入方法;
10601061
隐藏参数入口时,组件只处理模型选择,不加载或修改参数。
1062+
使用方开启参数入口后应绑定对应参数字段,并移除独立的参数按钮、弹窗和默认参数请求。
1063+
通过 Props 与事件回写设置的子组件,应分别发送模型 ID 和参数的局部更新,由父级合并,避免
1064+
同次交互连续更新时使用旧 Props 覆盖刚选中的模型。
1065+
1066+
`canAdd` 默认为 `false`,开启后在下拉列表底部显示“添加模型”,复用 `ModelCreateButton`
1067+
供应商选择和模型创建流程。组件通过 `isWorkspaceResource()``isSystemSharedResource()` 读取
1068+
`resourceScope`:Workspace 传入 `ModelApi`,System 共享资源传入 `SystemSharedModelApi`
1069+
其他范围暂不展示创建入口。创建成功后触发 `refresh`,使用方重新加载原业务范围的模型选项,
1070+
不自动替换当前选中模型。`footer` 插槽仍可覆盖默认创建入口。
1071+
10611072

10621073
```vue
10631074
<script setup lang="ts">
@@ -1067,7 +1078,7 @@ import ModelSelect from '@/components/business/model-select/index.vue'
10671078
<ModelSelect
10681079
v-model="selectedModelId"
10691080
v-model:model-params="modelParams"
1070-
show-model-params
1081+
can-edit-params
10711082
:options="modelOptions"
10721083
:provider-options="providerOptions"
10731084
placeholder="请选择模型"
Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
<script setup lang="ts">
2-
import { computed, onBeforeUnmount, ref, useTemplateRef } from 'vue'
3-
import { Check, Operation } from '@element-plus/icons-vue'
2+
import { computed, ref, useTemplateRef } from 'vue'
3+
import type { SelectInstance } from 'element-plus'
44
import { MODEL_STATUS } from '@/api/enums'
55
import type { ModelProviderItem, ModelItem } from '@/api/types'
66
import { groupBy } from 'lodash'
77
import ModelParamsDialog from './ModelParamsDialog.vue'
8+
import ModelCreateButton from '@/views/model/create-model/ModelCreateButton.vue'
9+
import ModelApi from '@/api/admin/workspace/model/model'
10+
import SystemSharedModelApi from '@/api/admin/system/shared-resources/model'
11+
import { isWorkspaceResource, isSystemSharedResource } from '@/utils/resource-context'
812
defineOptions({ name: 'ModelSelect', inheritAttrs: false })
913
1014
interface ModelOptionGroup {
@@ -19,28 +23,50 @@ const props = withDefaults(
1923
modelValue: string
2024
options: ModelItem[]
2125
providerOptions: ModelProviderItem[]
22-
showModelParams?: boolean
26+
canEditParams?: boolean
27+
canAdd?: boolean
2328
modelParams?: Record<string, unknown>
2429
disabled?: boolean
2530
}>(),
2631
{
2732
modelValue: '',
2833
options: () => [],
2934
providerOptions: () => [],
30-
showModelParams: false,
35+
canEditParams: false,
36+
canAdd: false,
3137
modelParams: () => ({}),
3238
disabled: false,
3339
},
3440
)
35-
const _options = computed(() => {
36-
return groupBy(props.options, 'provider')
37-
})
41+
3842
const emit = defineEmits<{
3943
change: [modelId: string]
44+
refresh: []
4045
'update:modelValue': [modelId: string]
4146
'update:modelParams': [settings: Record<string, unknown>]
4247
}>()
4348
49+
const modelOptionGroups = computed<ModelOptionGroup[]>(() => {
50+
const providerMap = new Map(props.providerOptions.map((provider) => [provider.provider, provider]))
51+
52+
return Object.entries(_options.value ?? {}).map(([provider, models]) => {
53+
const providerOption = providerMap.get(provider)
54+
return {
55+
icon: providerOption?.icon ?? '',
56+
models: [...models].sort((left, right) => Number(right.status === MODEL_STATUS.SUCCESS) - Number(left.status === MODEL_STATUS.SUCCESS)),
57+
name: providerOption?.name ?? provider,
58+
provider,
59+
}
60+
})
61+
})
62+
63+
const selectedProviderIcon = computed(
64+
() => modelOptionGroups.value.find(({ models }) => models.some(({ id }) => id === selectedModelId.value))?.icon ?? '',
65+
)
66+
const _options = computed(() => {
67+
return groupBy(props.options, 'provider')
68+
})
69+
4470
const loading = ref(false)
4571
4672
const selectedModelId = computed({
@@ -52,17 +78,30 @@ const selectedModelId = computed({
5278
},
5379
})
5480
81+
// 创建模型:根据当前资源范围传入完整 API,创建后由调用方刷新选项。
82+
const selectRef = useTemplateRef<SelectInstance>('selectRef')
83+
const allModelProvider: ModelProviderItem = { icon: '', name: '全部模型', provider: 'all' }
84+
const createModelApi = computed(() => {
85+
if (isWorkspaceResource()) return ModelApi
86+
if (isSystemSharedResource()) return SystemSharedModelApi
87+
return undefined
88+
})
89+
90+
function handleOpenCreateModel(open: () => void) {
91+
if (props.disabled || !props.canAdd || !createModelApi.value) return
92+
selectRef.value?.blur()
93+
open()
94+
}
95+
5596
// 模型参数:切换模型加载默认值,确认弹窗后回写配置。
5697
const modelParamsDialogRef = useTemplateRef<InstanceType<typeof ModelParamsDialog>>('modelParamsDialogRef')
57-
let modelParamsRequestId = 0
5898
5999
function resetModelParams(modelId: string) {
60-
const requestId = ++modelParamsRequestId
61-
if (!props.showModelParams) return
100+
if (!props.canEditParams) return
62101
emit('update:modelParams', {})
63102
if (!modelId) return
64103
modelParamsDialogRef.value?.resetDefault(modelId).then((settings) => {
65-
if (requestId !== modelParamsRequestId || props.modelValue !== modelId || !props.showModelParams) return
104+
if (props.modelValue !== modelId || !props.canEditParams) return
66105
emit('update:modelParams', settings)
67106
})
68107
}
@@ -71,36 +110,16 @@ function openModelParams() {
71110
if (!props.modelValue || props.disabled) return
72111
modelParamsDialogRef.value?.open(props.modelValue, props.modelParams)
73112
}
74-
75-
onBeforeUnmount(() => {
76-
modelParamsRequestId += 1
77-
})
78-
79-
const modelOptionGroups = computed<ModelOptionGroup[]>(() => {
80-
const providerMap = new Map(props.providerOptions.map((provider) => [provider.provider, provider]))
81-
82-
return Object.entries(_options.value ?? {}).map(([provider, models]) => {
83-
const providerOption = providerMap.get(provider)
84-
return {
85-
icon: providerOption?.icon ?? '',
86-
models: [...models].sort((left, right) => Number(right.status === MODEL_STATUS.SUCCESS) - Number(left.status === MODEL_STATUS.SUCCESS)),
87-
name: providerOption?.name ?? provider,
88-
provider,
89-
}
90-
})
91-
})
92-
93-
const selectedProviderIcon = computed(
94-
() => modelOptionGroups.value.find(({ models }) => models.some(({ id }) => id === selectedModelId.value))?.icon ?? '',
95-
)
96113
</script>
97114

98115
<template>
99-
<div class="flex w-full items-center gap-2">
116+
<div class="relative w-full" :class="{ 'model-select--with-params': canEditParams }">
100117
<el-select
118+
ref="selectRef"
101119
v-model="selectedModelId"
120+
placeholder="请选择模型"
102121
v-bind="$attrs"
103-
class="min-w-0 flex-1"
122+
class="w-full"
104123
:disabled="disabled"
105124
clearable
106125
filterable
@@ -120,7 +139,6 @@ const selectedProviderIcon = computed(
120139
<span class="min-w-0 flex-1 truncate" :title="model.name">{{ model.name }}</span>
121140
<el-tag v-if="model.source === 'shared'" size="small" type="info">共享</el-tag>
122141
<span v-if="model.status !== MODEL_STATUS.SUCCESS" class="text-danger">不可用</span>
123-
<Check v-if="model.id === selectedModelId" class="h-4 w-4 shrink-0 text-primary" />
124142
</div>
125143
</el-option>
126144
</el-option-group>
@@ -132,13 +150,34 @@ const selectedProviderIcon = computed(
132150
</div>
133151
</template>
134152

135-
<template #empty>
136-
<MkEmpty description="暂无可用模型" />
153+
<template v-if="canAdd && createModelApi" #footer>
154+
<slot name="footer">
155+
<ModelCreateButton :api="createModelApi" :current-provider="allModelProvider" :providers="providerOptions" @refresh="emit('refresh')">
156+
<template #default="{ open }">
157+
<el-button type="primary" link :disabled="disabled" @click.stop="handleOpenCreateModel(open)">
158+
<MkIcon name="icon_add_outlined" />
159+
<span>添加模型</span>
160+
</el-button>
161+
</template>
162+
</ModelCreateButton>
163+
</slot>
137164
</template>
138165
</el-select>
139-
<el-button v-if="showModelParams" :disabled="disabled || !modelValue" title="模型参数设置" aria-label="模型参数设置" @click="openModelParams">
140-
<MkIcon :icon="Operation" />
141-
</el-button>
166+
<div v-if="canEditParams" class="absolute inset-y-px right-3 flex items-center gap-3">
167+
<el-divider direction="vertical" />
168+
<el-button link :disabled="disabled || !modelValue" title="模型参数设置" @click.stop="openModelParams">
169+
<MkIcon name="icon_preferences_outlined" />
170+
</el-button>
171+
</div>
142172
</div>
143-
<ModelParamsDialog v-if="showModelParams" ref="modelParamsDialogRef" @submit="emit('update:modelParams', $event)" />
173+
<ModelParamsDialog v-if="canEditParams" ref="modelParamsDialogRef" @submit="emit('update:modelParams', $event)" />
144174
</template>
175+
176+
<style scoped>
177+
/* 参数入口与选择器共用外边框,为原生下拉箭头和清空按钮预留空间。 */
178+
.model-select--with-params {
179+
:deep(.el-select__wrapper) {
180+
padding-right: calc(var(--spacing) * 14);
181+
}
182+
}
183+
</style>

ui/src/constants/model.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
export const MODEL_TYPE_LABELS: Record<string, string> = {
2+
LLM: 'AI 模型',
23
EMBEDDING: '向量模型',
3-
IMAGE: '图像模型',
4-
ITV: '图生视频',
5-
LLM: '大语言模型',
4+
IMAGE: '视觉模型',
5+
ITV: '图生视频模型',
6+
STT: '语音识别模型',
7+
TTI: '图片生成模型',
8+
TTS: '语音合成模型',
9+
TTV: '文生视频模型',
610
RERANKER: '重排模型',
7-
STT: '语音识别',
8-
TTI: '文生图',
9-
TTS: '语音合成',
10-
TTV: '文生视频',
1111
}

ui/src/router/ROUTE_README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Login、Workflow 路由没有挂载 `AppLayout`,因此不会出现在左侧导
6767
- `login`:登录及账户相关独立页面。
6868
- `workflow`:集中维护各业务的全屏画布路由,当前包含智能体和工具工作流,后续知识库等画布
6969
继续在 `admin/workflow/index.ts` 中注册。
70+
- Workspace 的全屏 Workflow 路由显式设置 `resourceScope: 'workspace'`,使模型创建等资源操作使用 Workspace API。
7071
- Workflow 路由保留当前 Workspace 和资源标识;智能体工作流地址为
7172
`/workspace/:workspaceId/application/:applicationId/workflow`,工具工作流地址为
7273
`/workspace/:workspaceId/tool/:toolId/workflow`。两个页面均可从资源操作入口进入,按住 Ctrl 或

ui/src/router/admin/workflow/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ export const workflowRoutes: RouteRecordRaw[] = [
66
path: '/workspace/:workspaceId/application/:applicationId/workflow',
77
name: 'workflow-application',
88
component: () => import('@/views/workflow/ApplicationWorkflowView.vue'),
9-
meta: { title: '智能体工作流' },
9+
meta: { title: '智能体工作流', resourceScope: 'workspace' },
1010
},
1111
{
1212
path: '/workspace/:workspaceId/tool/:toolId/workflow',
1313
name: 'workflow-tool',
1414
component: () => import('@/views/workflow/ToolWorkflowView.vue'),
15-
meta: { title: '工具工作流' },
15+
meta: { title: '工具工作流', resourceScope: 'workspace' },
1616
},
1717
]

ui/src/styles/element-plus.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@
417417

418418
/* drawer */
419419
.el-drawer {
420+
box-shadow: -6px 0px 24px 0px rgb(var(--mk-N900-rgb) / 8%);
421+
420422
&__header {
421423
position: relative;
422424
border-bottom: 1px solid var(--el-border-color);
@@ -779,6 +781,13 @@
779781
}
780782
}
781783

784+
.el-select-group {
785+
&__title {
786+
color: var(--mk-N500);
787+
font-size: var(--mk-font-size-base);
788+
}
789+
}
790+
782791
/* steps */
783792
.el-steps {
784793
align-items: center;

0 commit comments

Comments
 (0)