From 1522009aec63848341d7f1ab9d32b7d0f742ce68 Mon Sep 17 00:00:00 2001 From: matevip Date: Wed, 17 Jun 2026 17:38:42 +0800 Subject: [PATCH] feat(agent): one-sentence AI employee creation wizard Turn a single natural-language requirement into a ready-to-review employee: the model proposes name, persona, runtime type and a validated set of skills/tools/knowledge base, which the user confirms or tweaks before the agent is created. - backend: POST /api/v1/agents/generate builds a draft from the workspace's real capability catalog; every suggested tool/skill/KB is re-validated against the catalog so nothing hallucinated is offered - frontend: 3-step wizard at /agents/create reusing the existing create + binding endpoints; reusable capability picker shows selected items as compact chips with an on-demand searchable catalog --- .../agent/controller/AgentController.java | 18 + .../agent/service/AgentGenerationService.java | 357 ++++++++++++ .../java/vip/mate/agent/vo/AgentDraftVO.java | 65 +++ mateclaw-ui/src/api/index.ts | 2 + .../agent/WizardCapabilityPicker.vue | 140 +++++ mateclaw-ui/src/i18n/locales/en-US.ts | 50 ++ mateclaw-ui/src/i18n/locales/zh-CN.ts | 50 ++ mateclaw-ui/src/router/index.ts | 6 + mateclaw-ui/src/views/AgentCreateWizard.vue | 525 ++++++++++++++++++ mateclaw-ui/src/views/Agents.vue | 6 + 10 files changed, 1219 insertions(+) create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/service/AgentGenerationService.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/vo/AgentDraftVO.java create mode 100644 mateclaw-ui/src/components/agent/WizardCapabilityPicker.vue create mode 100644 mateclaw-ui/src/views/AgentCreateWizard.vue diff --git a/mateclaw-server/src/main/java/vip/mate/agent/controller/AgentController.java b/mateclaw-server/src/main/java/vip/mate/agent/controller/AgentController.java index c38412a8..e3b77655 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/controller/AgentController.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/controller/AgentController.java @@ -12,7 +12,9 @@ import vip.mate.channel.web.Utf8SseEmitter; import vip.mate.agent.AgentService; import vip.mate.agent.AgentState; import vip.mate.agent.model.AgentEntity; +import vip.mate.agent.service.AgentGenerationService; import vip.mate.agent.vo.AgentCapabilitiesVO; +import vip.mate.agent.vo.AgentDraftVO; import vip.mate.audit.service.AuditEventService; import vip.mate.llm.model.ModelConfigEntity; import vip.mate.llm.service.ModelCapabilityService; @@ -51,6 +53,7 @@ public class AgentController { private final ModelConfigService modelConfigService; private final ModelCapabilityService modelCapabilityService; private final SystemSettingService systemSettingService; + private final AgentGenerationService agentGenerationService; private final ObjectMapper objectMapper; private final ExecutorService sseExecutor = Executors.newCachedThreadPool(); @@ -128,6 +131,16 @@ public class AgentController { } } + @Operation(summary = "根据一句话需求生成员工草稿(不落库)") + @PostMapping("/generate") + @RequireWorkspaceRole("member") + public R generate( + @RequestHeader(value = "X-Workspace-Id", required = false) Long workspaceId, + @RequestBody GenerateRequest request) { + long wsId = workspaceId != null ? workspaceId : 1L; + return R.ok(agentGenerationService.generateDraft(request.getRequirement(), wsId)); + } + @Operation(summary = "创建Agent") @PostMapping @RequireWorkspaceRole("member") @@ -270,6 +283,11 @@ public class AgentController { private String conversationId = "default"; } + @lombok.Data + public static class GenerateRequest { + private String requirement; + } + /** * 校验目标资源实际归属的 workspace 与请求 header 一致。 * 防止 "在 workspace A 鉴权,操作 workspace B 资源" 的跨域攻击。 diff --git a/mateclaw-server/src/main/java/vip/mate/agent/service/AgentGenerationService.java b/mateclaw-server/src/main/java/vip/mate/agent/service/AgentGenerationService.java new file mode 100644 index 00000000..8534cfca --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/agent/service/AgentGenerationService.java @@ -0,0 +1,357 @@ +package vip.mate.agent.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.stereotype.Service; +import vip.mate.agent.AgentGraphBuilder; +import vip.mate.agent.vo.AgentDraftVO; +import vip.mate.exception.MateClawException; +import vip.mate.llm.model.ModelConfigEntity; +import vip.mate.llm.service.ModelConfigService; +import vip.mate.skill.model.SkillEntity; +import vip.mate.skill.service.SkillService; +import vip.mate.tool.model.AvailableToolDTO; +import vip.mate.tool.service.AvailableToolService; +import vip.mate.wiki.model.WikiKnowledgeBaseEntity; +import vip.mate.wiki.service.WikiKnowledgeBaseService; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Turns a single natural-language requirement into a ready-to-review employee + * draft. The model is given the workspace's real capability catalog (tools, + * skills, knowledge bases) and asked to pick from it, so the resulting draft + * proposes a name, persona, type and a coherent set of capabilities in one + * shot. Every suggested capability is re-validated against the catalog before + * it leaves this service, so a hallucinated tool name or skill id never + * reaches the wizard. + * + *

The draft is intentionally not persisted here. The wizard renders it for + * review and edits, then commits through the existing agent-create and + * capability-binding endpoints — reusing their tested persistence and audit + * paths rather than duplicating them. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AgentGenerationService { + + private final ModelConfigService modelConfigService; + private final AgentGraphBuilder agentGraphBuilder; + private final ObjectMapper objectMapper; + private final AvailableToolService availableToolService; + private final SkillService skillService; + private final WikiKnowledgeBaseService wikiKnowledgeBaseService; + + /** Bound the catalog we feed the model so the prompt stays compact. */ + private static final int MAX_TOOLS = 60; + private static final int MAX_SKILLS = 40; + private static final int MAX_KBS = 20; + + public AgentDraftVO generateDraft(String requirement, Long workspaceId) { + if (requirement == null || requirement.isBlank()) { + throw new MateClawException("err.agent.generate_empty", 400, + "Please describe the employee you want to create"); + } + long wsId = workspaceId != null ? workspaceId : 1L; + + ModelConfigEntity defaultModel = modelConfigService.getDefaultModel(); + if (defaultModel == null) { + throw new MateClawException("err.agent.generate_no_model", 400, + "No default model is configured yet"); + } + + // Build the capability catalog the model is allowed to pick from. + List tools = bindableTools(); + List skills = workspaceSkills(wsId); + List kbs = workspaceKbs(wsId); + + String systemPrompt = buildSystemPrompt(); + String userPrompt = buildUserPrompt(requirement.trim(), tools, skills, kbs); + + String raw; + try { + ChatModel chatModel = agentGraphBuilder.buildRuntimeChatModel(defaultModel); + ChatResponse response = chatModel.call(new Prompt(List.of( + new SystemMessage(systemPrompt), + new UserMessage(userPrompt)))); + raw = response != null && response.getResult() != null + && response.getResult().getOutput() != null + ? response.getResult().getOutput().getText() : null; + } catch (Exception e) { + log.warn("[AgentGen] LLM call failed: {}", e.getMessage()); + throw new MateClawException("err.agent.generate_failed", 500, + "Failed to generate employee draft"); + } + + JsonNode root = parseJson(raw); + if (root == null || !root.isObject()) { + throw new MateClawException("err.agent.generate_failed", 500, + "Model returned an unexpected response"); + } + return toDraft(root, tools, skills, kbs); + } + + // ==================== Catalog ==================== + + private List bindableTools() { + List all; + try { + all = availableToolService.listAvailable(); + } catch (Exception e) { + log.warn("[AgentGen] failed to list tools: {}", e.getMessage()); + return List.of(); + } + List out = new ArrayList<>(); + for (AvailableToolDTO t : all) { + // Only offer tools that are currently bindable and reachable; a + // stale MCP tool would resolve to nothing at chat time. + if (t != null && t.isAvailable() && !t.isStale() + && t.getName() != null && !t.getName().isBlank()) { + out.add(t); + if (out.size() >= MAX_TOOLS) break; + } + } + return out; + } + + private List workspaceSkills(long wsId) { + try { + List skills = skillService.listEnabledSkills(wsId); + return skills.size() > MAX_SKILLS ? skills.subList(0, MAX_SKILLS) : skills; + } catch (Exception e) { + log.warn("[AgentGen] failed to list skills: {}", e.getMessage()); + return List.of(); + } + } + + private List workspaceKbs(long wsId) { + try { + List kbs = wikiKnowledgeBaseService.listByWorkspace(wsId); + return kbs.size() > MAX_KBS ? kbs.subList(0, MAX_KBS) : kbs; + } catch (Exception e) { + log.warn("[AgentGen] failed to list knowledge bases: {}", e.getMessage()); + return List.of(); + } + } + + // ==================== Prompt ==================== + + private String buildSystemPrompt() { + return """ + You are an employee (AI agent) configuration generator for an agent platform. + Given a one-sentence requirement, output a single JSON object describing one + ready-to-use employee. Respond in the SAME language as the requirement. + + Output ONLY the JSON object, no prose, no markdown fences. Schema: + { + "name": "short display name, no instruction words", + "icon": "a single emoji matching the role", + "description": "one concise sentence shown on the roster card", + "agentType": "react | plan_execute", + "role": "short role label", + "goal": "one short sentence on what this employee achieves", + "systemPrompt": "the full persona prompt: who it is, how it works, constraints", + "tags": ["1-3 short tags"], + "recommendedQuestions": ["2-4 starter questions a user might ask first"], + "tools": ["tool names chosen ONLY from the provided tool catalog"], + "skillIds": ["skill ids chosen ONLY from the provided skill catalog, as strings"], + "primaryKbId": "one knowledge base id from the catalog, or null" + } + + Rules: + - Use agentType "plan_execute" only for multi-step / long-horizon work; otherwise "react". + - Pick tools, skillIds and primaryKbId ONLY from the catalogs given below. Never invent + names or ids. If nothing fits, return an empty array (or null for primaryKbId). + - Prefer the smallest capability set that satisfies the requirement. + - Skills already bundle their own tools, so do not also list a tool a chosen skill provides. + """; + } + + private String buildUserPrompt(String requirement, List tools, + List skills, List kbs) { + StringBuilder sb = new StringBuilder(); + sb.append("Requirement:\n").append(requirement).append("\n\n"); + + sb.append("Tool catalog (name — description):\n"); + if (tools.isEmpty()) { + sb.append("(none)\n"); + } else { + for (AvailableToolDTO t : tools) { + sb.append("- ").append(t.getName()); + if (t.getDescription() != null && !t.getDescription().isBlank()) { + sb.append(" — ").append(trim(t.getDescription(), 120)); + } + sb.append('\n'); + } + } + + sb.append("\nSkill catalog (id — name — description):\n"); + if (skills.isEmpty()) { + sb.append("(none)\n"); + } else { + for (SkillEntity s : skills) { + sb.append("- ").append(s.getId()).append(" — ").append(s.getName()); + if (s.getDescription() != null && !s.getDescription().isBlank()) { + sb.append(" — ").append(trim(s.getDescription(), 120)); + } + sb.append('\n'); + } + } + + sb.append("\nKnowledge base catalog (id — name — description):\n"); + if (kbs.isEmpty()) { + sb.append("(none)\n"); + } else { + for (WikiKnowledgeBaseEntity kb : kbs) { + sb.append("- ").append(kb.getId()).append(" — ").append(kb.getName()); + if (kb.getDescription() != null && !kb.getDescription().isBlank()) { + sb.append(" — ").append(trim(kb.getDescription(), 120)); + } + sb.append('\n'); + } + } + return sb.toString(); + } + + // ==================== Parse + validate ==================== + + private AgentDraftVO toDraft(JsonNode root, List tools, + List skills, List kbs) { + String name = text(root, "name"); + if (name.isBlank()) { + name = "New employee"; + } + String agentType = text(root, "agentType"); + if (!"plan_execute".equals(agentType)) { + agentType = "react"; + } + + return AgentDraftVO.builder() + .name(trim(name, 60)) + .icon(firstEmoji(text(root, "icon"))) + .description(trim(text(root, "description"), 200)) + .agentType(agentType) + .role(trim(text(root, "role"), 60)) + .goal(trim(text(root, "goal"), 120)) + .systemPrompt(text(root, "systemPrompt")) + .tags(stringList(root.get("tags"), 5)) + .recommendedQuestions(stringList(root.get("recommendedQuestions"), 4)) + .tools(validTools(root.get("tools"), tools)) + .skillIds(validSkillIds(root.get("skillIds"), skills)) + .primaryKbId(validKbId(root.get("primaryKbId"), kbs)) + .build(); + } + + private List validTools(JsonNode node, List catalog) { + Set allowed = new LinkedHashSet<>(); + for (AvailableToolDTO t : catalog) allowed.add(t.getName()); + List out = new ArrayList<>(); + if (node != null && node.isArray()) { + for (JsonNode n : node) { + String v = n.asText(""); + if (allowed.contains(v) && !out.contains(v)) out.add(v); + } + } + return out; + } + + private List validSkillIds(JsonNode node, List catalog) { + Map allowed = new LinkedHashMap<>(); + for (SkillEntity s : catalog) allowed.put(s.getId(), Boolean.TRUE); + List out = new ArrayList<>(); + if (node != null && node.isArray()) { + for (JsonNode n : node) { + Long id = asLong(n); + if (id != null && allowed.containsKey(id) && !out.contains(id)) out.add(id); + } + } + return out; + } + + private Long validKbId(JsonNode node, List catalog) { + Long id = asLong(node); + if (id == null) return null; + for (WikiKnowledgeBaseEntity kb : catalog) { + if (kb.getId().equals(id)) return id; + } + return null; + } + + // ==================== Helpers ==================== + + private JsonNode parseJson(String response) { + if (response == null || response.isBlank()) return null; + String cleaned = response.trim(); + if (cleaned.startsWith("```json")) cleaned = cleaned.substring(7); + else if (cleaned.startsWith("```")) cleaned = cleaned.substring(3); + if (cleaned.endsWith("```")) cleaned = cleaned.substring(0, cleaned.length() - 3); + cleaned = cleaned.trim(); + try { + return objectMapper.readTree(cleaned); + } catch (Exception e) { + log.debug("[AgentGen] JSON parse failed: {}", e.getMessage()); + return null; + } + } + + /** Accept both numeric and textual ids — textual is preferred to preserve precision. */ + private Long asLong(JsonNode node) { + if (node == null || node.isNull()) return null; + try { + if (node.isTextual()) { + String v = node.asText().trim(); + return v.isEmpty() ? null : Long.parseLong(v); + } + if (node.isNumber()) return node.asLong(); + } catch (NumberFormatException ignored) { + // fall through + } + return null; + } + + private static String text(JsonNode root, String field) { + JsonNode n = root.get(field); + return n == null || n.isNull() ? "" : n.asText("").trim(); + } + + private static List stringList(JsonNode node, int max) { + List out = new ArrayList<>(); + if (node != null && node.isArray()) { + for (JsonNode n : node) { + String v = n.asText("").trim(); + if (!v.isEmpty() && !out.contains(v)) { + out.add(v); + if (out.size() >= max) break; + } + } + } + return out; + } + + private static String trim(String s, int max) { + if (s == null) return ""; + String t = s.trim(); + return t.length() > max ? t.substring(0, max) : t; + } + + /** Keep only the first emoji-ish glyph so the icon column never holds a sentence. */ + private static String firstEmoji(String s) { + if (s == null || s.isBlank()) return "🤖"; + String t = s.trim(); + int end = t.offsetByCodePoints(0, Math.min(t.codePointCount(0, t.length()), 1)); + return t.substring(0, end); + } +} diff --git a/mateclaw-server/src/main/java/vip/mate/agent/vo/AgentDraftVO.java b/mateclaw-server/src/main/java/vip/mate/agent/vo/AgentDraftVO.java new file mode 100644 index 00000000..80b9693d --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/agent/vo/AgentDraftVO.java @@ -0,0 +1,65 @@ +package vip.mate.agent.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +/** + * An AI-generated employee draft produced from a single natural-language + * requirement. The draft is never persisted on its own — the create wizard + * shows it for review, lets the user tweak any field, then commits it through + * the normal agent-create and capability-binding endpoints. + * + *

Every suggested capability ({@link #tools}, {@link #skillIds}, + * {@link #primaryKbId}) is validated against the workspace catalog before the + * draft is returned, so the wizard never offers a tool name or knowledge base + * that does not actually exist. + */ +@Data +@Builder +public class AgentDraftVO { + + /** Display name for the new employee. */ + private String name; + + /** Emoji icon chosen to match the role. */ + private String icon; + + /** One-line description shown on the roster card. */ + private String description; + + /** Runtime kind: {@code react} or {@code plan_execute}. */ + private String agentType; + + /** Assembled persona / system prompt, editable before commit. */ + private String systemPrompt; + + /** Short role label, used for the card tagline preview. */ + private String role; + + /** Short goal statement, used for the card tagline preview. */ + private String goal; + + /** Suggested tags. */ + private List tags; + + /** A few starter questions to seed the first conversation. */ + private List recommendedQuestions; + + /** + * Tool names to bind, drawn from the workspace's available tool catalog + * (built-in and MCP). Hallucinated names are dropped during validation. + */ + private List tools; + + /** Skill ids to bind, validated against the workspace's enabled skills. */ + @JsonSerialize(contentUsing = ToStringSerializer.class) + private List skillIds; + + /** Primary knowledge base id to attach, or null when none fits. */ + @JsonSerialize(using = ToStringSerializer.class) + private Long primaryKbId; +} diff --git a/mateclaw-ui/src/api/index.ts b/mateclaw-ui/src/api/index.ts index 00fb40d0..210d0a1e 100644 --- a/mateclaw-ui/src/api/index.ts +++ b/mateclaw-ui/src/api/index.ts @@ -109,6 +109,8 @@ export const agentApi = { list: (params?: { enabled?: boolean }) => http.get('/agents', { params }), get: (id: string | number) => http.get(`/agents/${id}`), create: (data: any) => http.post('/agents', data), + /** Generate a reviewable employee draft from a one-sentence requirement (no persistence). */ + generate: (requirement: string) => http.post('/agents/generate', { requirement }), update: (id: string | number, data: any) => http.put(`/agents/${id}`, data), delete: (id: string | number) => http.delete(`/agents/${id}`), chat: (id: string | number, data: any) => http.post(`/agents/${id}/chat`, data), diff --git a/mateclaw-ui/src/components/agent/WizardCapabilityPicker.vue b/mateclaw-ui/src/components/agent/WizardCapabilityPicker.vue new file mode 100644 index 00000000..b6edf0a0 --- /dev/null +++ b/mateclaw-ui/src/components/agent/WizardCapabilityPicker.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index 4fc3d992..17f1a478 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -1352,6 +1352,56 @@ export default { contextHint: 'Manage context files (e.g. AGENT.md) that define this agent\'s behavior, knowledge, and instructions.', goToContext: 'Edit Context Files', }, + wizard: { + entry: 'AI Create', + kicker: 'AI Create', + title: 'Create an employee in one sentence', + subtitle: 'Describe the employee you need — AI configures the persona, type and tools; confirm to onboard.', + steps: { describe: 'Describe', confirm: 'Confirm', onboard: 'Onboard' }, + placeholder: 'e.g. An ops assistant that can check the weather and write daily reports', + inputHint: 'Enter to generate · Shift+Enter for newline', + generate: 'Generate', + generating: 'Generating…', + tryLabel: 'Try:', + examples: ['Quarterly business report', 'Contract risk review', 'Cross-team weekly digest'], + generateFailed: 'Generation failed, please retry or rephrase', + emptyRequirement: 'Please describe the employee you need first', + aiNotice: 'The fields below were generated by AI from your description — edit anything before creating.', + fields: { + name: 'Name', + icon: 'Icon', + type: 'Type', + description: 'Description', + persona: 'Persona', + tags: 'Tags', + }, + sections: { + tools: 'Tools & MCP', + skills: 'Skills', + kb: 'Knowledge base', + }, + aiPicked: 'AI picked {count}', + selected: '{count} selected', + addSkill: 'Add skill', + addTool: 'Add tool', + collapse: 'Collapse', + noAiSkills: 'No skills suggested — add manually', + noAiTools: 'No tools suggested — add manually', + remove: 'Remove', + noTools: 'No tools available', + noSkills: 'No skills available', + kbNone: 'No knowledge base', + back: 'Back to describe', + confirmCreate: 'Confirm & onboard', + creating: 'Creating…', + createFailed: 'Creation failed', + successTitle: '{name} is onboarded', + successSubtitle: 'The new employee has joined your team and is ready to chat.', + stat: { skills: 'Skills', tools: 'Tools', kb: 'KB' }, + startChat: 'Start chatting', + buildAnother: 'Build another', + roster: 'Roster', + }, }, security: { title: 'Security', diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index ca304ac1..05ffc987 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -1243,6 +1243,56 @@ export default { contextHint: '管理此智能体的上下文文件(如 AGENT.md),定义智能体的行为、知识和指令。', goToContext: '前往编辑上下文', }, + wizard: { + entry: 'AI 创建', + kicker: 'AI 创建', + title: '一句话创建员工', + subtitle: '描述你需要的员工,AI 自动配好人设、类型与工具,确认即可上岗', + steps: { describe: '描述需求', confirm: '确认配置', onboard: '上岗' }, + placeholder: '例如:帮我做一个会查天气、能写日报的运营助手', + inputHint: '回车生成 · Shift+回车换行', + generate: '生成员工', + generating: '正在生成…', + tryLabel: '试试:', + examples: ['季度经营分析报告', '合同条款风险审查', '跨部门周报汇总'], + generateFailed: '生成失败,请重试或换一种描述', + emptyRequirement: '请先描述你需要的员工', + aiNotice: '以下内容由 AI 根据你的描述生成,可自由修改后再创建', + fields: { + name: '名称', + icon: '图标', + type: '类型', + description: '描述', + persona: '人设', + tags: '标签', + }, + sections: { + tools: '工具 & MCP', + skills: '技能 Skills', + kb: '知识库 Wiki', + }, + aiPicked: 'AI 已选 {count} 项', + selected: '已选 {count}', + addSkill: '添加技能', + addTool: '添加工具', + collapse: '收起', + noAiSkills: 'AI 未推荐技能,可手动添加', + noAiTools: 'AI 未推荐工具,可手动添加', + remove: '移除', + noTools: '暂无可用工具', + noSkills: '暂无可用技能', + kbNone: '不绑定知识库', + back: '返回重新描述', + confirmCreate: '确认创建并上岗', + creating: '创建中…', + createFailed: '创建失败', + successTitle: '{name} 已上岗', + successSubtitle: '新员工已加入你的团队,随时可以开始对话', + stat: { skills: '技能', tools: '工具', kb: '知识库' }, + startChat: '立即对话', + buildAnother: '再建一个', + roster: '员工列表', + }, }, security: { title: '安全管理', diff --git a/mateclaw-ui/src/router/index.ts b/mateclaw-ui/src/router/index.ts index 5b6e92c8..c7c1890c 100644 --- a/mateclaw-ui/src/router/index.ts +++ b/mateclaw-ui/src/router/index.ts @@ -39,6 +39,12 @@ const router = createRouter({ component: () => import('@/views/Agents.vue'), meta: { title: 'Agents', requiredCapability: 'manage:agents' }, }, + { + path: 'agents/create', + name: 'AgentCreateWizard', + component: () => import('@/views/AgentCreateWizard.vue'), + meta: { title: 'Create Agent', requiredCapability: 'manage:agents' }, + }, { // Live runtime view folded into the Agents page as a sub-view. // Kept as a redirect so old links / bookmarks still resolve. diff --git a/mateclaw-ui/src/views/AgentCreateWizard.vue b/mateclaw-ui/src/views/AgentCreateWizard.vue new file mode 100644 index 00000000..b395e06c --- /dev/null +++ b/mateclaw-ui/src/views/AgentCreateWizard.vue @@ -0,0 +1,525 @@ + + + + + diff --git a/mateclaw-ui/src/views/Agents.vue b/mateclaw-ui/src/views/Agents.vue index d555b5b0..e86791e4 100644 --- a/mateclaw-ui/src/views/Agents.vue +++ b/mateclaw-ui/src/views/Agents.vue @@ -31,6 +31,12 @@ >{{ liveRunning }} +