From b5bb9a33e7ae7be231cbd8f6f16f9b8258c38753 Mon Sep 17 00:00:00 2001 From: lpf Date: Sat, 7 Mar 2026 14:23:48 +0800 Subject: [PATCH] Refine webui layout and config defaults --- cmd/clawgo/cmd_gateway.go | 2 + cmd/clawgo/main.go | 2 +- config.example.json | 8 +- pkg/api/server.go | 93 ++-- webui/src/components/Header.tsx | 16 +- webui/src/components/Layout.tsx | 2 +- webui/src/components/NavItem.tsx | 8 +- webui/src/components/Sidebar.tsx | 84 ++-- webui/src/components/StatCard.tsx | 2 +- webui/src/context/AppContext.tsx | 89 +++- webui/src/i18n/index.ts | 66 +++ webui/src/pages/Chat.tsx | 608 +++++++++++++++++++++++---- webui/src/pages/Config.tsx | 15 +- webui/src/pages/Cron.tsx | 8 +- webui/src/pages/Dashboard.tsx | 210 ++++++--- webui/src/pages/EKG.tsx | 178 ++++++-- webui/src/pages/LogCodes.tsx | 6 +- webui/src/pages/Logs.tsx | 6 +- webui/src/pages/Memory.tsx | 8 +- webui/src/pages/Skills.tsx | 27 +- webui/src/pages/SubagentProfiles.tsx | 27 +- webui/src/pages/Subagents.tsx | 22 +- webui/src/pages/TaskAudit.tsx | 2 +- 23 files changed, 1132 insertions(+), 357 deletions(-) diff --git a/cmd/clawgo/cmd_gateway.go b/cmd/clawgo/cmd_gateway.go index c927976..41ca30d 100644 --- a/cmd/clawgo/cmd_gateway.go +++ b/cmd/clawgo/cmd_gateway.go @@ -129,6 +129,8 @@ func gatewayCmd() { } registryServer := api.NewServer(cfg.Gateway.Host, cfg.Gateway.Port, cfg.Gateway.Token, nodes.DefaultManager()) + registryServer.SetGatewayVersion(version) + registryServer.SetWebUIVersion(version) registryServer.SetConfigPath(getConfigPath()) registryServer.SetWorkspacePath(cfg.WorkspacePath()) registryServer.SetLogFilePath(cfg.LogFilePath()) diff --git a/cmd/clawgo/main.go b/cmd/clawgo/main.go index c536ee0..27a2f5d 100644 --- a/cmd/clawgo/main.go +++ b/cmd/clawgo/main.go @@ -19,7 +19,7 @@ import ( //go:embed workspace var embeddedFiles embed.FS -var version = "0.1.0" +var version = "dev" var buildTime = "unknown" const logo = "🦞" diff --git a/config.example.json b/config.example.json index 0414940..0b4d71c 100644 --- a/config.example.json +++ b/config.example.json @@ -73,6 +73,7 @@ "main": { "enabled": true, "type": "router", + "notify_main_policy": "final_only", "display_name": "Main Agent", "role": "orchestrator", "system_prompt_file": "agents/main/AGENT.md", @@ -85,7 +86,6 @@ "runtime": { "proxy": "proxy", "temperature": 0.2, - "timeout_sec": 900, "max_retries": 1, "retry_backoff_ms": 1000, "max_parallel_runs": 4 @@ -94,6 +94,7 @@ "coder": { "enabled": true, "type": "worker", + "notify_main_policy": "final_only", "display_name": "Code Agent", "role": "code", "system_prompt_file": "agents/coder/AGENT.md", @@ -106,7 +107,6 @@ "runtime": { "proxy": "proxy", "temperature": 0.2, - "timeout_sec": 1200, "max_retries": 1, "retry_backoff_ms": 1000, "max_task_chars": 20000, @@ -117,6 +117,7 @@ "tester": { "enabled": true, "type": "worker", + "notify_main_policy": "on_blocked", "display_name": "Test Agent", "role": "test", "system_prompt_file": "agents/tester/AGENT.md", @@ -129,7 +130,6 @@ "runtime": { "proxy": "proxy", "temperature": 0.1, - "timeout_sec": 1200, "max_retries": 1, "retry_backoff_ms": 1000, "max_parallel_runs": 2 @@ -141,11 +141,11 @@ "transport": "node", "node_id": "edge-dev", "parent_agent_id": "main", + "notify_main_policy": "internal_only", "display_name": "Edge Dev Main Agent", "role": "remote_main", "memory_namespace": "node.edge-dev.main", "runtime": { - "timeout_sec": 1200, "max_retries": 1, "retry_backoff_ms": 1000, "max_parallel_runs": 1 diff --git a/pkg/api/server.go b/pkg/api/server.go index 1edbd5f..b1fac5f 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -32,24 +32,26 @@ import ( ) type Server struct { - addr string - token string - mgr *nodes.Manager - server *http.Server - configPath string - workspacePath string - logFilePath string - onChat func(ctx context.Context, sessionKey, content string) (string, error) - onChatHistory func(sessionKey string) []map[string]interface{} - onConfigAfter func() - onCron func(action string, args map[string]interface{}) (interface{}, error) - onSubagents func(ctx context.Context, action string, args map[string]interface{}) (interface{}, error) - webUIDir string - ekgCacheMu sync.Mutex - ekgCachePath string - ekgCacheStamp time.Time - ekgCacheSize int64 - ekgCacheRows []map[string]interface{} + addr string + token string + mgr *nodes.Manager + server *http.Server + gatewayVersion string + webuiVersion string + configPath string + workspacePath string + logFilePath string + onChat func(ctx context.Context, sessionKey, content string) (string, error) + onChatHistory func(sessionKey string) []map[string]interface{} + onConfigAfter func() + onCron func(action string, args map[string]interface{}) (interface{}, error) + onSubagents func(ctx context.Context, action string, args map[string]interface{}) (interface{}, error) + webUIDir string + ekgCacheMu sync.Mutex + ekgCachePath string + ekgCacheStamp time.Time + ekgCacheSize int64 + ekgCacheRows []map[string]interface{} } func NewServer(host string, port int, token string, mgr *nodes.Manager) *Server { @@ -79,7 +81,9 @@ func (s *Server) SetCronHandler(fn func(action string, args map[string]interface func (s *Server) SetSubagentHandler(fn func(ctx context.Context, action string, args map[string]interface{}) (interface{}, error)) { s.onSubagents = fn } -func (s *Server) SetWebUIDir(dir string) { s.webUIDir = strings.TrimSpace(dir) } +func (s *Server) SetWebUIDir(dir string) { s.webUIDir = strings.TrimSpace(dir) } +func (s *Server) SetGatewayVersion(v string) { s.gatewayVersion = strings.TrimSpace(v) } +func (s *Server) SetWebUIVersion(v string) { s.webuiVersion = strings.TrimSpace(v) } func (s *Server) Start(ctx context.Context) error { if s.mgr == nil { @@ -583,8 +587,8 @@ func (s *Server) handleWebUIVersion(w http.ResponseWriter, r *http.Request) { } _ = json.NewEncoder(w).Encode(map[string]interface{}{ "ok": true, - "gateway_version": gatewayBuildVersion(), - "webui_version": detectWebUIVersion(strings.TrimSpace(s.webUIDir)), + "gateway_version": firstNonEmptyString(s.gatewayVersion, gatewayBuildVersion()), + "webui_version": firstNonEmptyString(s.webuiVersion, detectWebUIVersion(strings.TrimSpace(s.webUIDir))), }) } @@ -1334,24 +1338,17 @@ func gatewayBuildVersion() string { } func detectWebUIVersion(webUIDir string) string { - if strings.TrimSpace(webUIDir) == "" { - return "unknown" - } - assets := filepath.Join(webUIDir, "assets") - entries, err := os.ReadDir(assets) - if err != nil { - return "unknown" - } - for _, e := range entries { - name := e.Name() - if strings.HasPrefix(name, "index-") && strings.HasSuffix(name, ".js") { - mid := strings.TrimSuffix(strings.TrimPrefix(name, "index-"), ".js") - if mid != "" { - return mid - } + _ = webUIDir + return "dev" +} + +func firstNonEmptyString(values ...string) string { + for _, v := range values { + if strings.TrimSpace(v) != "" { + return strings.TrimSpace(v) } } - return "unknown" + return "" } func detectLocalIP() string { @@ -2715,6 +2712,9 @@ func (s *Server) handleWebUIEKGStats(w http.ResponseWriter, r *http.Request) { errSig := strings.TrimSpace(fmt.Sprintf("%v", row["errsig"])) source := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", row["source"]))) channel := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", row["channel"]))) + if source == "heartbeat" { + continue + } if source == "" { source = "unknown" } @@ -2723,33 +2723,22 @@ func (s *Server) handleWebUIEKGStats(w http.ResponseWriter, r *http.Request) { } sourceStats[source]++ channelStats[channel]++ - isHeartbeat := source == "heartbeat" if provider != "" { switch status { case "success": providerScore[provider] += 1 - if !isHeartbeat { - providerScoreWorkload[provider] += 1 - } + providerScoreWorkload[provider] += 1 case "suppressed": providerScore[provider] += 0.2 - if !isHeartbeat { - providerScoreWorkload[provider] += 0.2 - } + providerScoreWorkload[provider] += 0.2 case "error": providerScore[provider] -= 1 - if !isHeartbeat { - providerScoreWorkload[provider] -= 1 - } + providerScoreWorkload[provider] -= 1 } } if errSig != "" && status == "error" { errSigCount[errSig]++ - if isHeartbeat { - errSigHeartbeat[errSig]++ - } else { - errSigWorkload[errSig]++ - } + errSigWorkload[errSig]++ } } toTopScore := func(m map[string]float64, n int) []kv { diff --git a/webui/src/components/Header.tsx b/webui/src/components/Header.tsx index 0066378..9c9fbde 100644 --- a/webui/src/components/Header.tsx +++ b/webui/src/components/Header.tsx @@ -5,7 +5,7 @@ import { useAppContext } from '../context/AppContext'; const Header: React.FC = () => { const { t, i18n } = useTranslation(); - const { isGatewayOnline, setSidebarOpen } = useAppContext(); + const { isGatewayOnline, setSidebarOpen, sidebarCollapsed } = useAppContext(); const toggleLang = () => { const nextLang = i18n.language === 'en' ? 'zh' : 'en'; @@ -18,10 +18,18 @@ const Header: React.FC = () => { -
- +
+
+ +
+ {!sidebarCollapsed && ( + {t('appName')} + )}
- {t('appName')} +
+ +
+ {t('appName')}
diff --git a/webui/src/components/Layout.tsx b/webui/src/components/Layout.tsx index f25206f..e107459 100644 --- a/webui/src/components/Layout.tsx +++ b/webui/src/components/Layout.tsx @@ -25,7 +25,7 @@ const Layout: React.FC = () => { animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} transition={{ duration: 0.2 }} - className="absolute inset-0 overflow-y-auto" + className="absolute inset-0 overflow-y-auto overflow-x-hidden" > diff --git a/webui/src/components/NavItem.tsx b/webui/src/components/NavItem.tsx index 0d58f0d..6044b38 100644 --- a/webui/src/components/NavItem.tsx +++ b/webui/src/components/NavItem.tsx @@ -5,19 +5,21 @@ interface NavItemProps { icon: React.ReactNode; label: string; to: string; + collapsed?: boolean; } -const NavItem: React.FC = ({ icon, label, to }) => ( +const NavItem: React.FC = ({ icon, label, to, collapsed = false }) => ( `w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 ${ + title={collapsed ? label : undefined} + className={({ isActive }) => `w-full flex items-center ${collapsed ? 'justify-center' : 'gap-3'} px-3 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 ${ isActive ? 'bg-indigo-500/15 text-indigo-300 border border-indigo-500/30' : 'text-zinc-400 hover:bg-zinc-800/60 hover:text-zinc-200 border border-transparent' }`} > {icon} - {label} + {!collapsed && label} ); diff --git a/webui/src/components/Sidebar.tsx b/webui/src/components/Sidebar.tsx index fcdb5c1..ee323c1 100644 --- a/webui/src/components/Sidebar.tsx +++ b/webui/src/components/Sidebar.tsx @@ -1,72 +1,92 @@ import React from 'react'; -import { LayoutDashboard, MessageSquare, Settings, Clock, Terminal, Zap, FolderOpen, ClipboardList, BrainCircuit, Hash, Bot, Boxes } from 'lucide-react'; +import { LayoutDashboard, MessageSquare, Settings, Clock, Terminal, Zap, FolderOpen, ClipboardList, BrainCircuit, Hash, Bot, Boxes, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useAppContext } from '../context/AppContext'; import NavItem from './NavItem'; const Sidebar: React.FC = () => { const { t } = useTranslation(); - const { token, setToken, sidebarOpen } = useAppContext(); + const { token, setToken, sidebarOpen, sidebarCollapsed, setSidebarCollapsed } = useAppContext(); const sections = [ { - title: t('sidebarCore'), + title: t('sidebarMain'), items: [ { icon: , label: t('dashboard'), to: '/' }, { icon: , label: t('chat'), to: '/chat' }, { icon: , label: t('subagentsRuntime'), to: '/subagents' }, - { icon: , label: t('logs'), to: '/logs' }, - { icon: , label: t('logCodes'), to: '/log-codes' }, - { icon: , label: t('skills'), to: '/skills' }, ], }, { - title: t('sidebarSystem'), - items: [ - { icon: , label: t('config'), to: '/config' }, - { icon: , label: t('cronJobs'), to: '/cron' }, - { icon: , label: t('memory'), to: '/memory' }, - { icon: , label: t('subagentProfiles'), to: '/subagent-profiles' }, - ], - }, - { - title: t('sidebarOps'), + title: t('sidebarRuntime'), items: [ { icon: , label: t('taskAudit'), to: '/task-audit' }, + { icon: , label: t('logs'), to: '/logs' }, + { icon: , label: t('ekg'), to: '/ekg' }, ], }, { - title: t('sidebarInsights'), + title: t('sidebarConfig'), items: [ - { icon: , label: t('ekg'), to: '/ekg' }, + { icon: , label: t('config'), to: '/config' }, + { icon: , label: t('subagentProfiles'), to: '/subagent-profiles' }, + { icon: , label: t('cronJobs'), to: '/cron' }, + ], + }, + { + title: t('sidebarKnowledge'), + items: [ + { icon: , label: t('memory'), to: '/memory' }, + { icon: , label: t('skills'), to: '/skills' }, + { icon: , label: t('logCodes'), to: '/log-codes' }, ], }, ]; return ( -