From f6a3a1592a943e3c4d22e77ae61add84f1514f8c Mon Sep 17 00:00:00 2001 From: matevip Date: Thu, 9 Apr 2026 00:32:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(ux):=20Phase=201=20=E2=80=94=20onboarding,?= =?UTF-8?q?=20doctor,=20templates,=20navigation=20convergence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mateclaw-server/nul | 1 - mateclaw-server/pom.xml | 2 +- .../agent/controller/TemplateController.java | 38 ++ .../vip/mate/agent/model/TemplateDTO.java | 33 ++ .../mate/agent/service/TemplateService.java | 112 ++++++ .../system/controller/SetupController.java | 42 +++ .../controller/SystemHealthController.java | 30 ++ .../system/service/SystemHealthService.java | 171 +++++++++ .../resources/templates/code-reviewer.json | 37 ++ .../templates/general-assistant.json | 37 ++ .../resources/templates/research-analyst.json | 37 ++ mateclaw-ui/package.json | 2 +- mateclaw-ui/src/api/index.ts | 5 + mateclaw-ui/src/i18n/locales/en-US.ts | 39 ++ mateclaw-ui/src/i18n/locales/zh-CN.ts | 39 ++ mateclaw-ui/src/router/index.ts | 102 ++--- mateclaw-ui/src/views/Doctor/DoctorDrawer.vue | 133 +++++++ .../src/views/Onboarding/OnboardingWizard.vue | 235 ++++++++++++ .../src/views/Onboarding/StepConfigure.vue | 354 ++++++++++++++++++ .../src/views/Onboarding/StepPathSelect.vue | 120 ++++++ .../src/views/Onboarding/StepVerify.vue | 251 +++++++++++++ mateclaw-ui/src/views/Settings/Layout.vue | 55 ++- mateclaw-ui/src/views/layout/MainLayout.vue | 128 ++++--- 23 files changed, 1885 insertions(+), 118 deletions(-) delete mode 100644 mateclaw-server/nul create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/controller/TemplateController.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/model/TemplateDTO.java create mode 100644 mateclaw-server/src/main/java/vip/mate/agent/service/TemplateService.java create mode 100644 mateclaw-server/src/main/java/vip/mate/system/controller/SystemHealthController.java create mode 100644 mateclaw-server/src/main/java/vip/mate/system/service/SystemHealthService.java create mode 100644 mateclaw-server/src/main/resources/templates/code-reviewer.json create mode 100644 mateclaw-server/src/main/resources/templates/general-assistant.json create mode 100644 mateclaw-server/src/main/resources/templates/research-analyst.json create mode 100644 mateclaw-ui/src/views/Doctor/DoctorDrawer.vue create mode 100644 mateclaw-ui/src/views/Onboarding/OnboardingWizard.vue create mode 100644 mateclaw-ui/src/views/Onboarding/StepConfigure.vue create mode 100644 mateclaw-ui/src/views/Onboarding/StepPathSelect.vue create mode 100644 mateclaw-ui/src/views/Onboarding/StepVerify.vue diff --git a/mateclaw-server/nul b/mateclaw-server/nul deleted file mode 100644 index 96ee2339..00000000 --- a/mateclaw-server/nul +++ /dev/null @@ -1 +0,0 @@ -/bin/sh: wmic: command not found diff --git a/mateclaw-server/pom.xml b/mateclaw-server/pom.xml index 1bffb72e..4a8bd2b0 100644 --- a/mateclaw-server/pom.xml +++ b/mateclaw-server/pom.xml @@ -6,7 +6,7 @@ vip.mate mateclaw-server - 1.0.314 + 1.0.418-SNAPSHOT jar MateClaw Server diff --git a/mateclaw-server/src/main/java/vip/mate/agent/controller/TemplateController.java b/mateclaw-server/src/main/java/vip/mate/agent/controller/TemplateController.java new file mode 100644 index 00000000..63a0a4a1 --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/agent/controller/TemplateController.java @@ -0,0 +1,38 @@ +package vip.mate.agent.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; +import vip.mate.agent.model.AgentEntity; +import vip.mate.agent.model.TemplateDTO; +import vip.mate.agent.service.TemplateService; +import vip.mate.common.result.R; + +import java.util.List; + +/** + * Agent 模板接口 + * + * @author MateClaw Team + */ +@Tag(name = "Agent Templates") +@RestController +@RequestMapping("/api/v1/templates") +@RequiredArgsConstructor +public class TemplateController { + + private final TemplateService templateService; + + @Operation(summary = "获取模板列表") + @GetMapping + public R> list() { + return R.ok(templateService.listTemplates()); + } + + @Operation(summary = "应用模板创建Agent") + @PostMapping("/{id}/apply") + public R apply(@PathVariable String id) { + return R.ok(templateService.applyTemplate(id)); + } +} diff --git a/mateclaw-server/src/main/java/vip/mate/agent/model/TemplateDTO.java b/mateclaw-server/src/main/java/vip/mate/agent/model/TemplateDTO.java new file mode 100644 index 00000000..4cf773d8 --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/agent/model/TemplateDTO.java @@ -0,0 +1,33 @@ +package vip.mate.agent.model; + +import lombok.Data; + +import java.util.List; + +/** + * Agent 模板 DTO + * + * @author MateClaw Team + */ +@Data +public class TemplateDTO { + + private String id; + private String name; + private String nameZh; + private String description; + private String descriptionZh; + private String icon; + private String agentType; + private String tags; + private Integer maxIterations; + private List workspaceFiles; + + @Data + public static class WorkspaceFileTemplate { + private String filename; + private String content; + private Boolean enabled; + private Integer sortOrder; + } +} diff --git a/mateclaw-server/src/main/java/vip/mate/agent/service/TemplateService.java b/mateclaw-server/src/main/java/vip/mate/agent/service/TemplateService.java new file mode 100644 index 00000000..cc5621cc --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/agent/service/TemplateService.java @@ -0,0 +1,112 @@ +package vip.mate.agent.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.mate.agent.AgentService; +import vip.mate.agent.model.AgentEntity; +import vip.mate.agent.model.TemplateDTO; +import vip.mate.exception.MateClawException; +import vip.mate.workspace.document.WorkspaceFileService; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Agent 模板服务 + *

+ * 扫描 classpath:templates/*.json 下的模板文件, + * 支持列出模板和应用模板创建 Agent。 + * + * @author MateClaw Team + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TemplateService { + + private final AgentService agentService; + private final WorkspaceFileService workspaceFileService; + private final ObjectMapper objectMapper; + + /** + * 列出所有可用模板 + */ + public List listTemplates() { + List templates = new ArrayList<>(); + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + try { + Resource[] resources = resolver.getResources("classpath:templates/*.json"); + for (Resource resource : resources) { + try (InputStream is = resource.getInputStream()) { + TemplateDTO dto = objectMapper.readValue(is, TemplateDTO.class); + templates.add(dto); + } catch (IOException e) { + log.warn("Failed to parse template file: {}", resource.getFilename(), e); + } + } + } catch (IOException e) { + log.error("Failed to scan template files", e); + } + + templates.sort(Comparator.comparing(TemplateDTO::getId)); + return templates; + } + + /** + * 应用模板创建 Agent 及其工作区文件 + * + * @param templateId 模板 ID + * @return 创建的 AgentEntity + */ + @Transactional + public AgentEntity applyTemplate(String templateId) { + TemplateDTO template = listTemplates().stream() + .filter(t -> t.getId().equals(templateId)) + .findFirst() + .orElseThrow(() -> new MateClawException("模板不存在: " + templateId)); + + // 1. 创建 Agent + AgentEntity agent = new AgentEntity(); + agent.setName(template.getName()); + agent.setDescription(template.getDescription()); + agent.setAgentType(template.getAgentType()); + agent.setIcon(template.getIcon()); + agent.setTags(template.getTags()); + agent.setMaxIterations(template.getMaxIterations()); + AgentEntity created = agentService.createAgent(agent); + + // 2. 创建工作区文件 + if (template.getWorkspaceFiles() != null && !template.getWorkspaceFiles().isEmpty()) { + for (TemplateDTO.WorkspaceFileTemplate wf : template.getWorkspaceFiles()) { + workspaceFileService.saveFile(created.getId(), wf.getFilename(), wf.getContent()); + } + + // 3. 启用指定文件并按 sortOrder 排序(setPromptFiles 用列表索引作为排序值) + List enabledFilenames = template.getWorkspaceFiles().stream() + .filter(wf -> Boolean.TRUE.equals(wf.getEnabled())) + .sorted((a, b) -> { + int sa = a.getSortOrder() != null ? a.getSortOrder() : 0; + int sb = b.getSortOrder() != null ? b.getSortOrder() : 0; + return Integer.compare(sa, sb); + }) + .map(TemplateDTO.WorkspaceFileTemplate::getFilename) + .toList(); + + if (!enabledFilenames.isEmpty()) { + workspaceFileService.setPromptFiles(created.getId(), enabledFilenames); + } + } + + return created; + } +} diff --git a/mateclaw-server/src/main/java/vip/mate/system/controller/SetupController.java b/mateclaw-server/src/main/java/vip/mate/system/controller/SetupController.java index 2bbc47b5..0e0f2b72 100644 --- a/mateclaw-server/src/main/java/vip/mate/system/controller/SetupController.java +++ b/mateclaw-server/src/main/java/vip/mate/system/controller/SetupController.java @@ -8,6 +8,12 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; import vip.mate.common.result.R; import vip.mate.config.DatabaseBootstrapRunner; +import vip.mate.llm.service.ModelConfigService; +import vip.mate.llm.service.ModelDiscoveryService; +import vip.mate.llm.service.ModelProviderService; + +import java.util.List; +import java.util.Map; /** * Setup API for first-run initialization. @@ -23,6 +29,9 @@ import vip.mate.config.DatabaseBootstrapRunner; public class SetupController { private final DatabaseBootstrapRunner bootstrapRunner; + private final ModelConfigService modelConfigService; + private final ModelDiscoveryService modelDiscoveryService; + private final ModelProviderService modelProviderService; /** * Check whether the application has been initialized. @@ -60,6 +69,39 @@ public class SetupController { return R.ok("Initialized with " + language); } + /** + * Onboarding status: whether the system has a usable model configured. + * Used by the frontend to decide whether to show the onboarding wizard. + */ + @GetMapping("/onboarding-status") + public R> getOnboardingStatus() { + boolean hasDefaultModel = false; + try { + modelConfigService.getDefaultModel(); + hasDefaultModel = true; + } catch (Exception e) { + // no default model configured + } + + boolean ollamaOnline = false; + try { + ollamaOnline = modelDiscoveryService.testConnection("ollama").isSuccess(); + } catch (Exception e) { + // Ollama not available + } + + List configuredProviders = modelProviderService.listProviders().stream() + .filter(p -> Boolean.TRUE.equals(p.getConfigured())) + .map(p -> p.getId()) + .toList(); + + return R.ok(Map.of( + "hasDefaultModel", hasDefaultModel, + "ollamaOnline", ollamaOnline, + "configuredProviders", configuredProviders + )); + } + @Data public static class InitRequest { private String language; diff --git a/mateclaw-server/src/main/java/vip/mate/system/controller/SystemHealthController.java b/mateclaw-server/src/main/java/vip/mate/system/controller/SystemHealthController.java new file mode 100644 index 00000000..e294b7ca --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/system/controller/SystemHealthController.java @@ -0,0 +1,30 @@ +package vip.mate.system.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import vip.mate.common.result.R; +import vip.mate.system.service.SystemHealthService; + +/** + * System health check endpoint. + * + * @author MateClaw Team + */ +@Tag(name = "System Health") +@RestController +@RequestMapping("/api/v1/system") +@RequiredArgsConstructor +public class SystemHealthController { + + private final SystemHealthService healthService; + + @Operation(summary = "System health check") + @GetMapping("/health") + public R getHealth() { + return R.ok(healthService.check()); + } +} diff --git a/mateclaw-server/src/main/java/vip/mate/system/service/SystemHealthService.java b/mateclaw-server/src/main/java/vip/mate/system/service/SystemHealthService.java new file mode 100644 index 00000000..ef4e1a7a --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/system/service/SystemHealthService.java @@ -0,0 +1,171 @@ +package vip.mate.system.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import vip.mate.config.DatabaseBootstrapRunner; +import vip.mate.exception.MateClawException; +import vip.mate.llm.model.ProviderInfoDTO; +import vip.mate.llm.service.ModelConfigService; +import vip.mate.llm.service.ModelProviderService; +import vip.mate.tool.mcp.model.McpServerEntity; +import vip.mate.tool.mcp.runtime.McpClientManager; +import vip.mate.tool.mcp.runtime.McpClientManager.ConnectionResult; +import vip.mate.tool.mcp.service.McpServerService; + +import java.util.ArrayList; +import java.util.List; + +/** + * System health check service. + *

+ * Inspects default model, provider configurations, MCP server connections, + * and database initialization status. + * + * @author MateClaw Team + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SystemHealthService { + + private final ModelConfigService modelConfigService; + private final ModelProviderService modelProviderService; + private final McpClientManager mcpClientManager; + private final McpServerService mcpServerService; + private final DatabaseBootstrapRunner bootstrapRunner; + + public HealthResponse check() { + List checks = new ArrayList<>(); + + // 1. Default model check + checks.add(checkDefaultModel()); + + // 2. Provider checks (only providers that require API keys) + checks.addAll(checkProviders()); + + // 3. MCP server checks (enabled servers only) + checks.addAll(checkMcpServers()); + + // 4. Database initialization check + checks.add(checkDatabase()); + + // Determine overall status + String overall = "healthy"; + for (HealthCheck c : checks) { + if ("error".equals(c.status())) { + overall = "error"; + break; + } + if ("warning".equals(c.status())) { + overall = "warning"; + } + } + + return new HealthResponse(overall, checks); + } + + private HealthCheck checkDefaultModel() { + try { + var model = modelConfigService.getDefaultModel(); + return new HealthCheck( + "default-model", + "healthy", + "Default model: " + model.getName(), + null + ); + } catch (MateClawException e) { + return new HealthCheck( + "default-model", + "error", + e.getMessage(), + new HealthAction("Configure Model", "/settings/models") + ); + } + } + + private List checkProviders() { + List results = new ArrayList<>(); + try { + List providers = modelProviderService.listProviders(); + for (ProviderInfoDTO provider : providers) { + // Only check providers that require an API key + if (!Boolean.TRUE.equals(provider.getRequireApiKey())) { + continue; + } + String providerId = provider.getId(); + boolean configured = modelProviderService.isProviderConfigured(providerId); + if (!configured) { + String reason = modelProviderService.getProviderUnavailableReason(providerId); + results.add(new HealthCheck( + "provider:" + providerId, + "warning", + provider.getName() + " - " + (reason != null ? reason : "Not configured"), + new HealthAction("Configure", "/settings/models") + )); + } + } + } catch (Exception e) { + log.warn("Failed to check providers: {}", e.getMessage()); + results.add(new HealthCheck( + "providers", + "warning", + "Unable to check providers: " + e.getMessage(), + null + )); + } + return results; + } + + private List checkMcpServers() { + List results = new ArrayList<>(); + try { + List servers = mcpServerService.listAll(); + for (McpServerEntity server : servers) { + if (!Boolean.TRUE.equals(server.getEnabled())) { + continue; + } + ConnectionResult cr = mcpClientManager.getConnectionResult(server.getId()); + if (cr == null || !cr.success()) { + String msg = server.getName() + " - " + + (cr != null ? cr.message() : "Not connected"); + results.add(new HealthCheck( + "mcp:" + server.getName(), + "warning", + msg, + new HealthAction("View Servers", "/settings/mcp-servers") + )); + } + } + } catch (Exception e) { + log.warn("Failed to check MCP servers: {}", e.getMessage()); + results.add(new HealthCheck( + "mcp-servers", + "warning", + "Unable to check MCP servers: " + e.getMessage(), + null + )); + } + return results; + } + + private HealthCheck checkDatabase() { + if (bootstrapRunner.isInitialized()) { + return new HealthCheck("database", "healthy", "Database initialized", null); + } + return new HealthCheck( + "database", + "error", + "Database not initialized", + new HealthAction("Setup", "/setup") + ); + } + + // ==================== Response Records ==================== + + public record HealthResponse(String overall, List checks) {} + + public record HealthCheck(String name, String status, String message, HealthAction action) {} + + public record HealthAction(String label, String route) {} +} diff --git a/mateclaw-server/src/main/resources/templates/code-reviewer.json b/mateclaw-server/src/main/resources/templates/code-reviewer.json new file mode 100644 index 00000000..04753793 --- /dev/null +++ b/mateclaw-server/src/main/resources/templates/code-reviewer.json @@ -0,0 +1,37 @@ +{ + "id": "code-reviewer", + "name": "Code Reviewer", + "nameZh": "代码审查员", + "description": "Expert code reviewer. Reads code, identifies issues, suggests improvements.", + "descriptionZh": "代码审查专家。阅读代码、发现问题、提出改进建议。", + "icon": "🔍", + "agentType": "react", + "tags": "code,review,developer", + "maxIterations": 10, + "workspaceFiles": [ + { + "filename": "AGENTS.md", + "content": "## Role\nYou are a senior code reviewer. Your job is to:\n1. Read code thoroughly before commenting\n2. Identify bugs, security issues, and performance problems\n3. Suggest concrete improvements with code examples\n4. Be direct but constructive\n\n## Review Checklist\n- Logic errors and edge cases\n- Security vulnerabilities\n- Performance bottlenecks\n- Code readability and naming\n- Error handling completeness\n- Test coverage gaps\n", + "enabled": true, + "sortOrder": 0 + }, + { + "filename": "SOUL.md", + "content": "# Soul\n\nBe the reviewer you'd want on your own code.\nPoint out what's wrong, but also acknowledge what's done well.\nAlways provide the fix, not just the critique.\n", + "enabled": true, + "sortOrder": 1 + }, + { + "filename": "PROFILE.md", + "content": "## Project Context\n\n- Language/Framework:\n- Code style guide:\n- Testing framework:\n\n_Update as you learn about the codebase._\n", + "enabled": true, + "sortOrder": 2 + }, + { + "filename": "MEMORY.md", + "content": "## Code Review Memory\n\n### Recurring Issues\n- (none yet)\n\n### Project Conventions\n- (none yet)\n", + "enabled": true, + "sortOrder": 3 + } + ] +} diff --git a/mateclaw-server/src/main/resources/templates/general-assistant.json b/mateclaw-server/src/main/resources/templates/general-assistant.json new file mode 100644 index 00000000..e1c4845a --- /dev/null +++ b/mateclaw-server/src/main/resources/templates/general-assistant.json @@ -0,0 +1,37 @@ +{ + "id": "general-assistant", + "name": "General Assistant", + "nameZh": "通用助手", + "description": "A versatile AI assistant for everyday tasks — search, write, analyze, and more.", + "descriptionZh": "通用 AI 助手,适合日常任务——搜索、写作、分析等。", + "icon": "🤖", + "agentType": "react", + "tags": "general,assistant,default", + "maxIterations": 10, + "workspaceFiles": [ + { + "filename": "AGENTS.md", + "content": "## Memory\n\nYour memory is stored in workspace files:\n- `PROFILE.md`: stable user profile and preferences\n- `MEMORY.md`: long-term facts, lessons, recurring patterns\n- `memory/YYYY-MM-DD.md`: daily notes and temporary context\n\n## Guidelines\n- Read workspace memory before answering questions about past decisions\n- Write stable facts to MEMORY.md, temporary notes to daily files\n- Be helpful, concise, and proactive\n", + "enabled": true, + "sortOrder": 0 + }, + { + "filename": "SOUL.md", + "content": "# Soul\n\nActually help, don't perform. Have your own opinions.\nFigure things out yourself first — use tools, then ask.\nKeep it concise when it should be, detailed when it matters.\n", + "enabled": true, + "sortOrder": 1 + }, + { + "filename": "PROFILE.md", + "content": "## User Profile\n\n- Name:\n- Preferences:\n- Communication style:\n\n_Update this as you learn about the user._\n", + "enabled": true, + "sortOrder": 2 + }, + { + "filename": "MEMORY.md", + "content": "## Long-term Memory\n\n### Stable Facts\n- (none yet)\n\n### Lessons Learned\n- (none yet)\n", + "enabled": true, + "sortOrder": 3 + } + ] +} diff --git a/mateclaw-server/src/main/resources/templates/research-analyst.json b/mateclaw-server/src/main/resources/templates/research-analyst.json new file mode 100644 index 00000000..47f58168 --- /dev/null +++ b/mateclaw-server/src/main/resources/templates/research-analyst.json @@ -0,0 +1,37 @@ +{ + "id": "research-analyst", + "name": "Research Analyst", + "nameZh": "研究分析师", + "description": "Breaks down complex research tasks into steps. Uses web search and Wiki for deep analysis.", + "descriptionZh": "将复杂研究任务分解为步骤。使用网络搜索和 Wiki 进行深度分析。", + "icon": "📊", + "agentType": "plan_execute", + "tags": "research,analysis,planning", + "maxIterations": 20, + "workspaceFiles": [ + { + "filename": "AGENTS.md", + "content": "## Role\nYou are a research analyst. For complex questions:\n1. Break the question into sub-questions\n2. Research each sub-question using available tools\n3. Synthesize findings into a coherent analysis\n4. Cite sources and note confidence levels\n\n## Research Process\n- Start with web search for recent information\n- Check Wiki knowledge base for existing analysis\n- Cross-reference multiple sources\n- Flag contradictions between sources\n", + "enabled": true, + "sortOrder": 0 + }, + { + "filename": "SOUL.md", + "content": "# Soul\n\nPrioritize accuracy over speed. Say \"I don't know\" when evidence is insufficient.\nDistinguish facts from opinions. Always cite your sources.\n", + "enabled": true, + "sortOrder": 1 + }, + { + "filename": "PROFILE.md", + "content": "## Research Context\n\n- Domain focus:\n- Preferred sources:\n- Output format preference:\n\n_Update as research topics become clear._\n", + "enabled": true, + "sortOrder": 2 + }, + { + "filename": "MEMORY.md", + "content": "## Research Memory\n\n### Key Findings\n- (none yet)\n\n### Source Quality Notes\n- (none yet)\n", + "enabled": true, + "sortOrder": 3 + } + ] +} diff --git a/mateclaw-ui/package.json b/mateclaw-ui/package.json index 49727215..656d991b 100644 --- a/mateclaw-ui/package.json +++ b/mateclaw-ui/package.json @@ -1,6 +1,6 @@ { "name": "mateclaw-ui", - "version": "1.0.314", + "version": "1.0.418-SNAPSHOT", "private": true, "type": "module", "description": "MateClaw - Personal AI Assistant Web Console", diff --git a/mateclaw-ui/src/api/index.ts b/mateclaw-ui/src/api/index.ts index 9105b3e7..a9afcfd4 100644 --- a/mateclaw-ui/src/api/index.ts +++ b/mateclaw-ui/src/api/index.ts @@ -259,6 +259,11 @@ export const oauthApi = { revoke: () => http.delete('/oauth/openai/revoke'), } +// ==================== Setup ==================== +export const setupApi = { + onboardingStatus: () => http.get('/setup/onboarding-status'), +} + // ==================== Settings ==================== export const settingsApi = { get: () => http.get('/settings'), diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index 29a2a543..69529db7 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -188,6 +188,9 @@ export default { skills: 'Skills', wiki: 'Wiki KB', tools: 'Tools', + core: 'Core', + connect: 'Connect', + system: 'System', datasources: 'Datasources', mcpServers: 'MCP Servers', settingsGroup: 'Settings', @@ -203,6 +206,16 @@ export default { roleUser: 'User', roleAdmin: 'Admin', }, + doctor: { + title: 'System Diagnostics', + checking: 'Checking...', + allGood: 'All systems healthy', + hasWarnings: '{count} warning(s)', + hasErrors: '{count} issue(s)', + refresh: 'Re-check', + lastChecked: 'Checked {time} ago', + diagnose: 'Diagnose', + }, settings: { title: 'Settings', sections: { @@ -214,6 +227,7 @@ export default { music: 'Music Generation', video: 'Video Generation', about: 'About', + advanced: 'Advanced', }, modelTitle: 'Model Management', modelDesc: 'Manage provider presets and default model selection', @@ -1257,4 +1271,29 @@ export default { statusPending: 'Pending', }, }, + onboarding: { + title: 'Welcome to MateClaw', + subtitle: 'Set up your first AI model', + stepPath: 'Choose Path', + stepConfigure: 'Configure Model', + stepVerify: 'Verify', + localTitle: 'Local Model', + localDesc: 'Ollama installed? Enable local models in one click', + localDetected: 'Detected', + cloudTitle: 'Cloud API', + cloudDesc: 'Use cloud models like OpenAI, DashScope, etc.', + selectModel: 'Select Model', + setDefault: 'Set as Default', + enterApiKey: 'Enter API Key', + testConnection: 'Test Connection', + testSuccess: 'Connection Successful', + testFailed: 'Connection Failed', + saveAndContinue: 'Save & Continue', + verifyTitle: 'Verify Your Model', + verifyMessage: 'Hello! Tell me briefly about yourself.', + send: 'Send', + startUsing: 'Start Using MateClaw', + skip: 'Skip', + back: 'Back', + }, } as const diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index 8a2fea57..457af4d9 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -183,6 +183,9 @@ export default { control: '控制台', channels: '渠道', sessions: '会话', + core: '核心', + connect: '连接', + system: '系统', agent: '智能体', workspace: '工作区', skills: '技能', @@ -214,6 +217,7 @@ export default { music: '音乐生成', video: '视频生成', about: '关于', + advanced: '高级', }, modelTitle: '模型管理', modelDesc: '管理模型预设与默认模型选择', @@ -941,6 +945,16 @@ export default { testFailed: '连接失败,请检查配置', }, }, + doctor: { + title: '系统诊断', + checking: '检查中...', + allGood: '所有系统正常', + hasWarnings: '{count} 个警告', + hasErrors: '{count} 个问题', + refresh: '重新检查', + lastChecked: '{time} 前检查', + diagnose: '诊断问题', + }, wiki: { desc: 'AI 驱动的结构化知识库,自动消化原始材料为 Wiki 页面', createKB: '新建知识库', @@ -1267,4 +1281,29 @@ export default { statusPending: '等待中', }, }, + onboarding: { + title: '欢迎使用 MateClaw', + subtitle: '配置你的第一个 AI 模型', + stepPath: '选择方式', + stepConfigure: '配置模型', + stepVerify: '验证', + localTitle: '本地模型', + localDesc: '已安装 Ollama?一键启用本地模型', + localDetected: '已检测到', + cloudTitle: '云端 API', + cloudDesc: '使用 OpenAI、DashScope 等云端模型', + selectModel: '选择模型', + setDefault: '设为默认', + enterApiKey: '输入 API Key', + testConnection: '测试连接', + testSuccess: '连接成功', + testFailed: '连接失败', + saveAndContinue: '保存并继续', + verifyTitle: '验证你的模型', + verifyMessage: '你好!简单介绍一下你自己。', + send: '发送', + startUsing: '开始使用 MateClaw', + skip: '跳过', + back: '返回', + }, } as const diff --git a/mateclaw-ui/src/router/index.ts b/mateclaw-ui/src/router/index.ts index 0d12a963..6a96782e 100644 --- a/mateclaw-ui/src/router/index.ts +++ b/mateclaw-ui/src/router/index.ts @@ -8,72 +8,45 @@ const router = createRouter({ component: () => import('@/views/layout/MainLayout.vue'), redirect: '/chat', children: [ + // ==================== Core ==================== { path: 'chat', name: 'Chat', component: () => import('@/views/ChatConsole.vue'), meta: { title: 'Chat' }, }, - { - path: 'channels', - name: 'Channels', - component: () => import('@/views/Channels.vue'), - meta: { title: 'Channels' }, - }, - { - path: 'sessions', - name: 'Sessions', - component: () => import('@/views/Sessions.vue'), - meta: { title: 'Sessions' }, - }, - { - path: 'workspace', - name: 'Workspace', - component: () => import('@/views/AgentWorkspace.vue'), - meta: { title: 'Workspace' }, - }, { path: 'agents', name: 'Agents', component: () => import('@/views/Agents.vue'), meta: { title: 'Agents' }, }, - { - path: 'skills', - name: 'Skills', - component: () => import('@/views/SkillMarket.vue'), - meta: { title: 'Skills' }, - }, { path: 'wiki', name: 'Wiki', component: () => import('@/views/Wiki/index.vue'), meta: { title: 'Wiki' }, }, + // ==================== Connect ==================== + { + path: 'channels', + name: 'Channels', + component: () => import('@/views/Channels.vue'), + meta: { title: 'Channels' }, + }, + { + path: 'skills', + name: 'Skills', + component: () => import('@/views/SkillMarket.vue'), + meta: { title: 'Skills' }, + }, { path: 'tools', name: 'Tools', component: () => import('@/views/Tools.vue'), meta: { title: 'Tools' }, }, - { - path: 'datasources', - name: 'Datasources', - component: () => import('@/views/Datasources.vue'), - meta: { title: 'Datasources' }, - }, - { - path: 'mcp-servers', - name: 'McpServers', - component: () => import('@/views/McpServers.vue'), - meta: { title: 'MCP Servers' }, - }, - { - path: 'cron-jobs', - name: 'CronJobs', - component: () => import('@/views/CronJobs.vue'), - meta: { title: 'Cron Jobs' }, - }, + // ==================== Settings (absorbs advanced pages) ==================== { path: 'settings', component: () => import('@/views/Settings/Layout.vue'), @@ -121,6 +94,37 @@ const router = createRouter({ component: () => import('@/views/Settings/Video/index.vue'), meta: { title: 'Settings - Video' }, }, + // Advanced (absorbed from top-level nav) + { + path: 'workspace', + name: 'SettingsWorkspace', + component: () => import('@/views/AgentWorkspace.vue'), + meta: { title: 'Settings - Workspace' }, + }, + { + path: 'cron-jobs', + name: 'SettingsCronJobs', + component: () => import('@/views/CronJobs.vue'), + meta: { title: 'Settings - Cron Jobs' }, + }, + { + path: 'datasources', + name: 'SettingsDatasources', + component: () => import('@/views/Datasources.vue'), + meta: { title: 'Settings - Datasources' }, + }, + { + path: 'mcp-servers', + name: 'SettingsMcpServers', + component: () => import('@/views/McpServers.vue'), + meta: { title: 'Settings - MCP Servers' }, + }, + { + path: 'token-usage', + name: 'SettingsTokenUsage', + component: () => import('@/views/TokenUsage.vue'), + meta: { title: 'Settings - Token Usage' }, + }, { path: 'about', name: 'SettingsAbout', @@ -129,6 +133,7 @@ const router = createRouter({ }, ], }, + // ==================== Security ==================== { path: 'security', component: () => import('@/views/Security/Layout.vue'), @@ -154,12 +159,13 @@ const router = createRouter({ }, ], }, - { - path: 'token-usage', - name: 'TokenUsage', - component: () => import('@/views/TokenUsage.vue'), - meta: { title: 'Token Usage' }, - }, + // ==================== Redirects (backward compatibility) ==================== + { path: 'sessions', redirect: '/chat' }, + { path: 'workspace', redirect: '/settings/workspace' }, + { path: 'cron-jobs', redirect: '/settings/cron-jobs' }, + { path: 'datasources', redirect: '/settings/datasources' }, + { path: 'mcp-servers', redirect: '/settings/mcp-servers' }, + { path: 'token-usage', redirect: '/settings/token-usage' }, ], }, { diff --git a/mateclaw-ui/src/views/Doctor/DoctorDrawer.vue b/mateclaw-ui/src/views/Doctor/DoctorDrawer.vue new file mode 100644 index 00000000..eede13bd --- /dev/null +++ b/mateclaw-ui/src/views/Doctor/DoctorDrawer.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/mateclaw-ui/src/views/Onboarding/OnboardingWizard.vue b/mateclaw-ui/src/views/Onboarding/OnboardingWizard.vue new file mode 100644 index 00000000..39d3fcab --- /dev/null +++ b/mateclaw-ui/src/views/Onboarding/OnboardingWizard.vue @@ -0,0 +1,235 @@ + + + + + diff --git a/mateclaw-ui/src/views/Onboarding/StepConfigure.vue b/mateclaw-ui/src/views/Onboarding/StepConfigure.vue new file mode 100644 index 00000000..314baa0c --- /dev/null +++ b/mateclaw-ui/src/views/Onboarding/StepConfigure.vue @@ -0,0 +1,354 @@ + + + + + diff --git a/mateclaw-ui/src/views/Onboarding/StepPathSelect.vue b/mateclaw-ui/src/views/Onboarding/StepPathSelect.vue new file mode 100644 index 00000000..bcd15471 --- /dev/null +++ b/mateclaw-ui/src/views/Onboarding/StepPathSelect.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/mateclaw-ui/src/views/Onboarding/StepVerify.vue b/mateclaw-ui/src/views/Onboarding/StepVerify.vue new file mode 100644 index 00000000..6e3f18c9 --- /dev/null +++ b/mateclaw-ui/src/views/Onboarding/StepVerify.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/mateclaw-ui/src/views/Settings/Layout.vue b/mateclaw-ui/src/views/Settings/Layout.vue index 03632bd0..64cba5cb 100644 --- a/mateclaw-ui/src/views/Settings/Layout.vue +++ b/mateclaw-ui/src/views/Settings/Layout.vue @@ -2,16 +2,18 @@

- - - {{ section.label }} - +
@@ -71,6 +73,38 @@ const sections = computed(() => [ label: t('settings.sections.video'), icon: '', }, + // Divider: Advanced + { id: 'divider-advanced', path: '', label: t('settings.sections.advanced'), icon: '', isDivider: true }, + { + id: 'workspace', + path: '/settings/workspace', + label: t('nav.workspace'), + icon: '', + }, + { + id: 'cron-jobs', + path: '/settings/cron-jobs', + label: t('nav.cronJobs'), + icon: '', + }, + { + id: 'datasources', + path: '/settings/datasources', + label: t('nav.datasources'), + icon: '', + }, + { + id: 'mcp-servers', + path: '/settings/mcp-servers', + label: t('nav.mcpServers'), + icon: '', + }, + { + id: 'token-usage', + path: '/settings/token-usage', + label: t('nav.tokenUsage'), + icon: '', + }, { id: 'about', path: '/settings/about', @@ -94,6 +128,7 @@ function isActive(path: string) { .nav-item + .nav-item { margin-top: 2px; } .nav-icon { width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; } .nav-icon :deep(svg) { width: 18px; height: 18px; display: block; } +.nav-divider { font-size: 11px; font-weight: 600; color: var(--mc-text-tertiary); text-transform: uppercase; letter-spacing: 0.05em; padding: 16px 8px 6px; margin-top: 4px; } .settings-content { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 24px; } @media (max-width: 900px) { diff --git a/mateclaw-ui/src/views/layout/MainLayout.vue b/mateclaw-ui/src/views/layout/MainLayout.vue index 11873783..8ee75e17 100644 --- a/mateclaw-ui/src/views/layout/MainLayout.vue +++ b/mateclaw-ui/src/views/layout/MainLayout.vue @@ -54,8 +54,13 @@ - +