Skip to content

Commit b7b39ba

Browse files
committed
feat: 能力绑定从大模型设置移到模型助手管理-大模型设置移除支持的能力配置(UI与保存逻辑),模型助手新增/编辑弹窗新增支持的能力配置区(Skills多选/接口多选/流程多选/工具逗号列表),AiAssistantEntity新增capabilities字段(实体/DbContext/建表ALTER/保存接口),前端助手列表加载能力选项并解析回显
1 parent 59a5ea6 commit b7b39ba

7 files changed

Lines changed: 92 additions & 80 deletions

File tree

‎Juggle.Api/Controllers/Api/AiController.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ public async Task<ApiResult> SaveAssistant([FromBody] AiAssistantSaveRequest req
235235
entity.OutputParams = req.OutputParams;
236236
entity.QuickPrompts = req.QuickPrompts;
237237
entity.Icon = req.Icon;
238+
entity.Capabilities = req.Capabilities;
238239
entity.Enabled = req.Enabled ? 1 : 0;
239240
entity.UpdatedAt = DateTime.Now.ToString("o");
240241
await _db.SaveChangesAsync();
@@ -477,6 +478,8 @@ public class AiAssistantSaveRequest
477478
public string? QuickPrompts { get; set; }
478479
public string? Icon { get; set; }
479480
public bool Enabled { get; set; } = true;
481+
/// <summary>支持的能力(JSON: {skills,apis,flows,tools})</summary>
482+
public string? Capabilities { get; set; }
480483
}
481484

482485
public class AiOptimizePromptRequest

‎Juggle.Api/Program.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ await ctx.Response.WriteAsync(
254254
try { db.Database.ExecuteSqlRaw("CREATE TABLE IF NOT EXISTS t_ai_assistant(id INTEGER PRIMARY KEY AUTOINCREMENT, assistant_name TEXT, description TEXT, system_prompt TEXT, input_params TEXT, output_params TEXT, enabled INTEGER DEFAULT 1, created_at TEXT, created_by INTEGER, updated_at TEXT, updated_by INTEGER, tenant_id INTEGER, deleted INTEGER DEFAULT 0);"); } catch { }
255255
try { db.Database.ExecuteSqlRaw("ALTER TABLE t_ai_assistant ADD COLUMN quick_prompts TEXT DEFAULT NULL;"); } catch { }
256256
try { db.Database.ExecuteSqlRaw("ALTER TABLE t_ai_assistant ADD COLUMN icon TEXT DEFAULT NULL;"); } catch { }
257+
// 助手支持的能力(skills/apis/flows/tools)
258+
try { db.Database.ExecuteSqlRaw("ALTER TABLE t_ai_assistant ADD COLUMN capabilities TEXT DEFAULT NULL;"); } catch { }
257259
// AI 助手对话会话表
258260
try { db.Database.ExecuteSqlRaw("CREATE TABLE IF NOT EXISTS t_ai_conversation(id INTEGER PRIMARY KEY AUTOINCREMENT, assistant_id INTEGER, assistant_name TEXT, system_prompt TEXT, input_params TEXT, output_params TEXT, messages TEXT, outputs TEXT, provider_id INTEGER, model TEXT, title TEXT, status INTEGER DEFAULT 0, created_at TEXT, created_by INTEGER, updated_at TEXT, updated_by INTEGER, tenant_id INTEGER, deleted INTEGER DEFAULT 0);"); } catch { }
259261
// Redis 多实例配置表

‎Juggle.Domain/Entities/AiAssistantEntity.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,7 @@ public class AiAssistantEntity : BaseEntity
3030

3131
/// <summary>是否启用:1=启用(出现在菜单) 0=禁用</summary>
3232
public int Enabled { get; set; } = 1;
33+
34+
/// <summary>支持的能力(JSON: {skills:[id],apis:["code"],flows:["key"],tools:["name"]})</summary>
35+
public string? Capabilities { get; set; }
3336
}

‎Juggle.Infrastructure/Persistence/JuggleDbContext.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
134134
e.Property(p => p.QuickPrompts).HasColumnName("quick_prompts");
135135
e.Property(p => p.Icon).HasColumnName("icon");
136136
e.Property(p => p.Enabled).HasColumnName("enabled");
137+
e.Property(p => p.Capabilities).HasColumnName("capabilities");
137138
});
138139
modelBuilder.Entity<AiConversationEntity>().ToTable("t_ai_conversation");
139140
modelBuilder.Entity<AiConversationEntity>(e => {

‎JuggleNet6.Frontend/src/views/ai/AiAssistantList.vue‎

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,33 @@
103103
<div style="font-size:11px;color:#909399;margin-top:4px">运行页中显示为快捷按钮,点击即填入补充说明,方便常用提问一键发起。</div>
104104
</div>
105105
</el-form-item>
106+
<el-form-item label="支持的能力">
107+
<div style="width:100%">
108+
<div style="display:flex;gap:8px;align-items:center;margin-bottom:6px">
109+
<span style="width:40px;font-size:12px;color:#666">Skills</span>
110+
<el-select v-model="capSkills" size="small" style="flex:1" multiple collapse-tags collapse-tags-tooltip clearable placeholder="勾选该助手支持的技能">
111+
<el-option v-for="s in skillOptions" :key="s.id" :label="`${s.skillName}(${s.groupName})`" :value="s.id" />
112+
</el-select>
113+
</div>
114+
<div style="display:flex;gap:8px;align-items:center;margin-bottom:6px">
115+
<span style="width:40px;font-size:12px;color:#666">接口</span>
116+
<el-select v-model="capApis" size="small" style="flex:1" multiple collapse-tags collapse-tags-tooltip filterable clearable placeholder="勾选该助手可调用的接口">
117+
<el-option v-for="a in apiOptions" :key="a.methodCode" :label="`${a.methodName}(${a.methodCode})`" :value="a.methodCode" />
118+
</el-select>
119+
</div>
120+
<div style="display:flex;gap:8px;align-items:center;margin-bottom:6px">
121+
<span style="width:40px;font-size:12px;color:#666">流程</span>
122+
<el-select v-model="capFlows" size="small" style="flex:1" multiple collapse-tags collapse-tags-tooltip filterable clearable placeholder="勾选该助手可调用的流程">
123+
<el-option v-for="f in flowOptions" :key="f.flowKey" :label="`${f.flowName}(${f.flowKey})`" :value="f.flowKey" />
124+
</el-select>
125+
</div>
126+
<div style="display:flex;gap:8px;align-items:center">
127+
<span style="width:40px;font-size:12px;color:#666">工具</span>
128+
<el-input v-model="capTools" size="small" style="flex:1" placeholder="逗号分隔的工具名(如 web_search, calculator)" />
129+
</div>
130+
<div style="font-size:11px;color:#909399;margin-top:4px">配置该助手支持的能力范围(Skills/接口/流程/工具)。</div>
131+
</div>
132+
</el-form-item>
106133
<el-form-item label="启用"><el-switch v-model="form.enabled" /></el-form-item>
107134
</el-form>
108135
<template #footer>
@@ -174,6 +201,50 @@ const outputParams = ref<any[]>([])
174201
const quickPrompts = ref<string[]>([])
175202
const optimizing = ref(false)
176203
const iconGenerating = ref(false)
204+
// 支持的能力(skills/apis/flows/tools)
205+
const capSkills = ref<number[]>([])
206+
const capApis = ref<string[]>([])
207+
const capFlows = ref<string[]>([])
208+
const capTools = ref('')
209+
const skillOptions = ref<any[]>([])
210+
const apiOptions = ref<any[]>([])
211+
const flowOptions = ref<any[]>([])
212+
213+
/** 加载能力选项(技能/接口/流程) */
214+
async function loadCapabilityOptions() {
215+
try {
216+
const [skillRes, suiteRes, flowRes]: any[] = await Promise.all([
217+
request.get('/skill/list', { params: { enabled: 1 } }),
218+
request.get('/suite/list'),
219+
request.post('/flow/definition/page', { pageNum: 1, pageSize: 500 })
220+
])
221+
skillOptions.value = skillRes.data || []
222+
flowOptions.value = flowRes.data?.list || flowRes.data?.records || []
223+
const suites = suiteRes.data || []
224+
apiOptions.value = []
225+
for (const s of suites) {
226+
try {
227+
const apisRes: any = await request.post('/suite/api/list', { suiteCode: s.suiteCode })
228+
for (const a of (apisRes.data || [])) {
229+
apiOptions.value.push({ methodCode: a.methodCode, methodName: a.methodName })
230+
}
231+
} catch { /* 忽略单个套件失败 */ }
232+
}
233+
} catch { /* 忽略 */ }
234+
}
235+
236+
/** 解析能力 JSON 到表单状态 */
237+
function parseCapabilities(json: string) {
238+
try {
239+
const c = JSON.parse(json || '{}')
240+
capSkills.value = c.skills || []
241+
capApis.value = c.apis || []
242+
capFlows.value = c.flows || []
243+
capTools.value = (c.tools || []).join(',')
244+
} catch {
245+
capSkills.value = []; capApis.value = []; capFlows.value = []; capTools.value = ''
246+
}
247+
}
177248
178249
onMounted(loadData)
179250
@@ -194,14 +265,18 @@ function openAdd() {
194265
inputParams.value = []
195266
outputParams.value = []
196267
quickPrompts.value = []
268+
parseCapabilities('')
197269
dialogVisible.value = true
270+
loadCapabilityOptions()
198271
}
199272
function openEdit(row: any) {
200273
form.value = { ...row, enabled: row.enabled === 1 }
201274
try { inputParams.value = JSON.parse(row.inputParams || '[]') } catch { inputParams.value = [] }
202275
try { outputParams.value = JSON.parse(row.outputParams || '[]') } catch { outputParams.value = [] }
203276
try { quickPrompts.value = JSON.parse(row.quickPrompts || '[]') } catch { quickPrompts.value = [] }
277+
parseCapabilities(row.capabilities || '')
204278
dialogVisible.value = true
279+
loadCapabilityOptions()
205280
}
206281
207282
function isImageIcon(icon: string): boolean {
@@ -249,7 +324,13 @@ async function doSave() {
249324
...form.value,
250325
inputParams: JSON.stringify(inputParams.value),
251326
outputParams: JSON.stringify(outputParams.value),
252-
quickPrompts: JSON.stringify(quickPrompts.value.filter((q: string) => q.trim()))
327+
quickPrompts: JSON.stringify(quickPrompts.value.filter((q: string) => q.trim())),
328+
capabilities: JSON.stringify({
329+
skills: capSkills.value,
330+
apis: capApis.value,
331+
flows: capFlows.value,
332+
tools: capTools.value.split(',').map((s: string) => s.trim()).filter(Boolean)
333+
})
253334
})
254335
ElMessage.success('保存成功')
255336
dialogVisible.value = false

‎JuggleNet6.Frontend/src/views/kb/KnowledgeList.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ function openEdit(row: any) {
9090
dialogVisible.value = true
9191
}
9292
async function doSave() {
93-
await request.post('/kb/save', form.value)
93+
await request.post('/kb/save', { ...form.value, enabled: form.value.enabled ? 1 : 0 })
9494
ElMessage.success('保存成功')
9595
dialogVisible.value = false
9696
loadData()

‎JuggleNet6.Frontend/src/views/system/AiProviderList.vue‎

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -57,32 +57,6 @@
5757
<el-option v-for="m in fetchedModels" :key="m" :label="m" :value="m" />
5858
</el-select>
5959
</el-form-item>
60-
<el-form-item label="支持的能力">
61-
<div style="width:100%">
62-
<div style="display:flex;gap:8px;align-items:center;margin-bottom:6px">
63-
<span style="width:40px;font-size:12px;color:#666">Skills</span>
64-
<el-select v-model="capSkills" size="small" style="flex:1" multiple collapse-tags collapse-tags-tooltip clearable placeholder="勾选该模型支持的技能">
65-
<el-option v-for="s in skillOptions" :key="s.id" :label="`${s.skillName}(${s.groupName})`" :value="s.id" />
66-
</el-select>
67-
</div>
68-
<div style="display:flex;gap:8px;align-items:center;margin-bottom:6px">
69-
<span style="width:40px;font-size:12px;color:#666">接口</span>
70-
<el-select v-model="capApis" size="small" style="flex:1" multiple collapse-tags collapse-tags-tooltip filterable clearable placeholder="勾选该模型可调用的接口">
71-
<el-option v-for="a in apiOptions" :key="a.methodCode" :label="`${a.methodName}(${a.methodCode})`" :value="a.methodCode" />
72-
</el-select>
73-
</div>
74-
<div style="display:flex;gap:8px;align-items:center;margin-bottom:6px">
75-
<span style="width:40px;font-size:12px;color:#666">流程</span>
76-
<el-select v-model="capFlows" size="small" style="flex:1" multiple collapse-tags collapse-tags-tooltip filterable clearable placeholder="勾选该模型可调用的流程">
77-
<el-option v-for="f in flowOptions" :key="f.flowKey" :label="`${f.flowName}(${f.flowKey})`" :value="f.flowKey" />
78-
</el-select>
79-
</div>
80-
<div style="display:flex;gap:8px;align-items:center">
81-
<span style="width:40px;font-size:12px;color:#666">工具</span>
82-
<el-input v-model="capTools" size="small" style="flex:1" placeholder="逗号分隔的工具名(如 web_search, calculator)" />
83-
</div>
84-
</div>
85-
</el-form-item>
8660
<el-form-item label="启用"><el-switch v-model="form.enabled" /></el-form-item>
8761
<el-form-item label="备注"><el-input v-model="form.remark" /></el-form-item>
8862
</el-form>
@@ -109,48 +83,6 @@ const selectedModels = ref<string[]>([])
10983
const testingConfig = ref(false)
11084
const testMsg = ref('')
11185
const testOk = ref(false)
112-
// 支持的能力
113-
const capSkills = ref<number[]>([])
114-
const capApis = ref<string[]>([])
115-
const capFlows = ref<string[]>([])
116-
const capTools = ref('')
117-
const skillOptions = ref<any[]>([])
118-
const apiOptions = ref<any[]>([])
119-
const flowOptions = ref<any[]>([])
120-
121-
async function loadCapabilityOptions() {
122-
try {
123-
const [skillRes, suiteRes, flowRes]: any[] = await Promise.all([
124-
request.get('/skill/list', { params: { enabled: 1 } }),
125-
request.get('/suite/list'),
126-
request.post('/flow/definition/page', { pageNum: 1, pageSize: 500 })
127-
])
128-
skillOptions.value = skillRes.data || []
129-
flowOptions.value = flowRes.data?.list || []
130-
const suites = suiteRes.data || []
131-
apiOptions.value = []
132-
for (const s of suites) {
133-
try {
134-
const apisRes: any = await request.post('/suite/api/list', { suiteCode: s.suiteCode })
135-
for (const a of (apisRes.data || [])) {
136-
apiOptions.value.push({ methodCode: a.methodCode, methodName: a.methodName })
137-
}
138-
} catch { /* 忽略单个套件失败 */ }
139-
}
140-
} catch { /* 忽略 */ }
141-
}
142-
143-
function parseCapabilities(json: string) {
144-
try {
145-
const c = JSON.parse(json || '{}')
146-
capSkills.value = c.skills || []
147-
capApis.value = c.apis || []
148-
capFlows.value = c.flows || []
149-
capTools.value = (c.tools || []).join(',')
150-
} catch {
151-
capSkills.value = []; capApis.value = []; capFlows.value = []; capTools.value = ''
152-
}
153-
}
15486
15587
// 预置供应商(选择后自动填默认接口地址)
15688
const presetProviders = [
@@ -176,19 +108,15 @@ function openAdd() {
176108
form.value = { id: 0, providerName: '', baseUrl: '', apiKey: '', model: '', models: '', enabled: true, remark: '' }
177109
fetchedModels.value = []
178110
selectedModels.value = []
179-
parseCapabilities('')
180111
testMsg.value = ''
181112
dialogVisible.value = true
182-
loadCapabilityOptions()
183113
}
184114
function openEdit(row: any) {
185115
form.value = { ...row, enabled: row.enabled === 1 }
186116
fetchedModels.value = String(row.models || '').split(',').map((s: string) => s.trim()).filter(Boolean)
187117
selectedModels.value = [...fetchedModels.value]
188-
parseCapabilities(row.capabilities || '')
189118
testMsg.value = ''
190119
dialogVisible.value = true
191-
loadCapabilityOptions()
192120
}
193121
194122
/** 选择预置供应商 → 自动填接口地址默认值 */
@@ -222,12 +150,6 @@ async function doFetchModels() {
222150
223151
async function doSave() {
224152
form.value.models = selectedModels.value.join(',')
225-
form.value.capabilities = JSON.stringify({
226-
skills: capSkills.value,
227-
apis: capApis.value,
228-
flows: capFlows.value,
229-
tools: capTools.value.split(',').map((s: string) => s.trim()).filter(Boolean)
230-
})
231153
await request.post('/ai/provider/save', form.value)
232154
ElMessage.success('保存成功')
233155
dialogVisible.value = false

0 commit comments

Comments
 (0)