diff --git a/README.md b/README.md index 95f466c..33abee8 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ make dev WebUI: ```text -http://:/webui?token= +http://:/?token= ``` ## 架构概览 @@ -279,7 +279,7 @@ user -> main -> worker -> main -> user 说明: - `webrtc` 建连失败时,调度层仍会回退到现有 relay / tunnel 路径 -- Dashboard、`status`、`/webui/api/nodes` 会显示当前 Node P2P 状态和会话摘要 +- Dashboard、`status`、`/api/nodes` 会显示当前 Node P2P 状态和会话摘要 - 两台公网机器的实网验证流程见 [docs/node-p2p-e2e.md](/Users/lpf/Desktop/project/clawgo/docs/node-p2p-e2e.md) ## MCP 服务支持 diff --git a/README_EN.md b/README_EN.md index 25fec0d..b660614 100644 --- a/README_EN.md +++ b/README_EN.md @@ -148,7 +148,7 @@ make dev WebUI: ```text -http://:/webui?token= +http://:/?token= ``` ## Architecture @@ -269,7 +269,7 @@ Example: Notes: - when `webrtc` session setup fails, dispatch still falls back to the existing relay / tunnel path -- Dashboard, `status`, and `/webui/api/nodes` expose the current Node P2P runtime summary +- Dashboard, `status`, and `/api/nodes` expose the current Node P2P runtime summary - a reusable public-network validation flow is documented in [docs/node-p2p-e2e.md](/Users/lpf/Desktop/project/clawgo/docs/node-p2p-e2e.md) ## MCP Server Support diff --git a/cmd/cmd_tui.go b/cmd/cmd_tui.go index c0ed5d8..84ee13b 100644 --- a/cmd/cmd_tui.go +++ b/cmd/cmd_tui.go @@ -112,6 +112,9 @@ type tuiModel struct { sessionCursor int sessionFilter string sessionActive map[string]time.Time + inputHistory []string + historyCursor int + historyDraft string status string globalErr string } @@ -153,6 +156,10 @@ func newTUIModel(client *tuiClient, runtime *tuiRuntime, opts tuiOptions) tuiMod input.Focus() input.CharLimit = 0 input.Prompt = "› " + input.PromptStyle = lipgloss.NewStyle().Bold(true).Foreground(tuiColorShell) + input.TextStyle = lipgloss.NewStyle().Foreground(tuiColorText) + input.PlaceholderStyle = lipgloss.NewStyle().Foreground(tuiColorTextSoft) + input.Cursor.Style = lipgloss.NewStyle().Foreground(tuiColorShell) pane := newTUIPane(1, opts.session) if opts.noHistory { @@ -160,16 +167,17 @@ func newTUIModel(client *tuiClient, runtime *tuiRuntime, opts tuiOptions) tuiMod } return tuiModel{ - client: client, - runtime: runtime, - input: input, - panes: []tuiPane{pane}, - focus: 0, - nextPaneID: 2, + client: client, + runtime: runtime, + input: input, + panes: []tuiPane{pane}, + focus: 0, + nextPaneID: 2, + historyCursor: -1, sessionActive: map[string]time.Time{ strings.TrimSpace(opts.session): time.Now(), }, - status: "Tab/Shift+Tab: focus • Ctrl+O: open selected session • Ctrl+R: replace pane • Enter: send • Ctrl+C: quit", + status: "Tab/Shift+Tab: focus • Ctrl+↑/↓: select session • Ctrl+O: open selected • Enter: send • Ctrl+C: quit", } } @@ -267,13 +275,23 @@ func (m tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, nil case "up": + if m.recallHistory(-1) { + return m, nil + } + return m, nil + case "down": + if m.recallHistory(1) { + return m, nil + } + return m, nil + case "ctrl+up": filtered := m.filteredSessions() if len(filtered) > 0 && m.sessionCursor > 0 { m.sessionCursor-- m.status = "Selected session: " + filtered[m.sessionCursor].Key } return m, nil - case "down": + case "ctrl+down": filtered := m.filteredSessions() if len(filtered) > 0 && m.sessionCursor < len(filtered)-1 { m.sessionCursor++ @@ -323,6 +341,7 @@ func (m tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if len(m.panes) == 0 || m.focusedPane().Sending || m.focusedPane().Loading { return m, nil } + m.pushInputHistory(value) m.input.SetValue("") if strings.HasPrefix(value, "/") { return m.handleCommand(value) @@ -486,7 +505,8 @@ func (m *tuiModel) resize() { } m.bodyHeight = bodyHeight m.sidebarWidth = min(28, max(20, m.width/5)) - availablePaneWidth := m.width - m.sidebarWidth - 1 + gapSize := lipgloss.Width(m.paneGap()) + availablePaneWidth := m.width - m.sidebarWidth - gapSize if availablePaneWidth < 24 { availablePaneWidth = 24 } @@ -495,23 +515,23 @@ func (m *tuiModel) resize() { case 1: m.setPaneSize(0, availablePaneWidth, bodyHeight) case 2: - leftWidth := (availablePaneWidth - 1) / 2 - rightWidth := availablePaneWidth - 1 - leftWidth + leftWidth := (availablePaneWidth - gapSize) / 2 + rightWidth := availablePaneWidth - gapSize - leftWidth m.setPaneSize(0, leftWidth, bodyHeight) m.setPaneSize(1, rightWidth, bodyHeight) case 3: - leftWidth := (availablePaneWidth - 1) / 2 - rightWidth := availablePaneWidth - 1 - leftWidth - topHeight := (bodyHeight - 1) / 2 - bottomHeight := bodyHeight - 1 - topHeight + leftWidth := (availablePaneWidth - gapSize) / 2 + rightWidth := availablePaneWidth - gapSize - leftWidth + topHeight := (bodyHeight - gapSize) / 2 + bottomHeight := bodyHeight - gapSize - topHeight m.setPaneSize(0, leftWidth, bodyHeight) m.setPaneSize(1, rightWidth, topHeight) m.setPaneSize(2, rightWidth, bottomHeight) default: - leftWidth := (availablePaneWidth - 1) / 2 - rightWidth := availablePaneWidth - 1 - leftWidth - topHeight := (bodyHeight - 1) / 2 - bottomHeight := bodyHeight - 1 - topHeight + leftWidth := (availablePaneWidth - gapSize) / 2 + rightWidth := availablePaneWidth - gapSize - leftWidth + topHeight := (bodyHeight - gapSize) / 2 + bottomHeight := bodyHeight - gapSize - topHeight m.setPaneSize(0, leftWidth, topHeight) m.setPaneSize(1, rightWidth, topHeight) m.setPaneSize(2, leftWidth, bottomHeight) @@ -523,7 +543,7 @@ func (m *tuiModel) resize() { func (m *tuiModel) renderPanes() string { sidebar := m.renderSessionSidebar() panes := m.renderPaneGrid() - return lipgloss.JoinHorizontal(lipgloss.Top, sidebar, tuiPaneGapStyle.Render(" "), panes) + return lipgloss.JoinHorizontal(lipgloss.Top, sidebar, tuiPaneGapStyle.Render(m.paneGap()), panes) } func (m *tuiModel) renderPane(index int, pane tuiPane) string { @@ -610,7 +630,8 @@ func (m *tuiModel) renderSessionSidebar() string { if strings.TrimSpace(m.sessionFilter) != "" { lines = append(lines, tuiMutedStyle.Render("Filter: "+m.sessionFilter)) } - lines = append(lines, tuiMutedStyle.Render("Up/Down: select")) + lines = append(lines, tuiMutedStyle.Render("Ctrl+Up/Down: select")) + lines = append(lines, tuiMutedStyle.Render("Up/Down: input history")) lines = append(lines, tuiMutedStyle.Render("Enter: open selected")) lines = append(lines, tuiMutedStyle.Render("Ctrl+O: open pane")) lines = append(lines, tuiMutedStyle.Render("Ctrl+N: new pane")) @@ -628,45 +649,99 @@ func (m *tuiModel) renderPaneGrid() string { if len(m.panes) == 0 { return tuiViewportStyle.Width(max(20, m.width-m.sidebarWidth-1)).Height(m.bodyHeight).Render("No panes.") } + gap := tuiPaneGapStyle.Render(m.paneGap()) switch len(m.panes) { case 1: return m.renderPane(0, m.panes[0]) case 2: return lipgloss.JoinHorizontal(lipgloss.Top, m.renderPane(0, m.panes[0]), - tuiPaneGapStyle.Render(" "), + gap, m.renderPane(1, m.panes[1]), ) case 3: right := lipgloss.JoinVertical(lipgloss.Left, m.renderPane(1, m.panes[1]), - tuiPaneGapStyle.Render(" "), + gap, m.renderPane(2, m.panes[2]), ) return lipgloss.JoinHorizontal(lipgloss.Top, m.renderPane(0, m.panes[0]), - tuiPaneGapStyle.Render(" "), + gap, right, ) default: top := lipgloss.JoinHorizontal(lipgloss.Top, m.renderPane(0, m.panes[0]), - tuiPaneGapStyle.Render(" "), + gap, m.renderPane(1, m.panes[1]), ) bottom := lipgloss.JoinHorizontal(lipgloss.Top, m.renderPane(2, m.panes[2]), - tuiPaneGapStyle.Render(" "), + gap, m.renderPane(3, m.panes[3]), ) return lipgloss.JoinVertical(lipgloss.Left, top, - tuiPaneGapStyle.Render(" "), + gap, bottom, ) } } +func (m *tuiModel) paneGap() string { + if len(m.panes) >= 4 { + return "" + } + return " " +} + +func (m *tuiModel) pushInputHistory(value string) { + value = strings.TrimSpace(value) + if value == "" { + return + } + if n := len(m.inputHistory); n > 0 && m.inputHistory[n-1] == value { + m.historyCursor = len(m.inputHistory) + m.historyDraft = "" + return + } + m.inputHistory = append(m.inputHistory, value) + m.historyCursor = len(m.inputHistory) + m.historyDraft = "" +} + +func (m *tuiModel) recallHistory(delta int) bool { + if len(m.inputHistory) == 0 { + return false + } + if m.historyCursor < 0 || m.historyCursor > len(m.inputHistory) { + m.historyCursor = len(m.inputHistory) + } + if m.historyCursor == len(m.inputHistory) { + m.historyDraft = m.input.Value() + } + next := m.historyCursor + delta + if next < 0 { + next = 0 + } + if next > len(m.inputHistory) { + next = len(m.inputHistory) + } + if next == m.historyCursor { + return true + } + m.historyCursor = next + if m.historyCursor == len(m.inputHistory) { + m.input.SetValue(m.historyDraft) + } else { + m.input.SetValue(m.inputHistory[m.historyCursor]) + } + m.input.CursorEnd() + m.input.Focus() + return true +} + func (m tuiModel) loadHistoryCmd(paneID int, session string) tea.Cmd { return func() tea.Msg { messages, err := m.client.history(context.Background(), session) @@ -1312,108 +1387,124 @@ func (c *tuiClient) newRequest(ctx context.Context, method, path string, body io } var ( + tuiColorBorder = lipgloss.Color("#F7B08A") + tuiColorClaw = lipgloss.Color("#FFB36B") + tuiColorShell = lipgloss.Color("#F05A28") + tuiColorShellDeep = lipgloss.Color("#D9481C") + tuiColorAccent = lipgloss.Color("#C44B21") + tuiColorAccentDeep = lipgloss.Color("#A43A18") + tuiColorText = lipgloss.Color("#472018") + tuiColorTextSoft = lipgloss.Color("#6E3B2E") + tuiColorError = lipgloss.Color("#B93815") + tuiColorSurfaceText = lipgloss.Color("#FFF4EE") + tuiHeaderStyle = lipgloss.NewStyle(). Padding(0, 1). BorderBottom(true). BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")) + Foreground(tuiColorText). + BorderForeground(tuiColorBorder) tuiTitleStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("212")) + Foreground(tuiColorShellDeep) tuiBannerClawStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("69")) + Foreground(tuiColorClaw) tuiBannerGoStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("204")) + Foreground(tuiColorShell) tuiMutedStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("245")) + Foreground(tuiColorTextSoft) tuiInputBoxStyle = lipgloss.NewStyle(). Padding(0, 1). BorderTop(true). BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")) + Foreground(tuiColorText). + BorderForeground(tuiColorBorder) tuiViewportStyle = lipgloss.NewStyle().Padding(0, 1) tuiFooterStyle = lipgloss.NewStyle(). Padding(0, 1). - Foreground(lipgloss.Color("245")) + Foreground(tuiColorText) tuiErrorStyle = lipgloss.NewStyle(). Padding(0, 1). - Foreground(lipgloss.Color("203")) + Foreground(tuiColorError) tuiPaneStyle = lipgloss.NewStyle(). Padding(0, 1). BorderStyle(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("240")) + Foreground(tuiColorText). + BorderForeground(tuiColorBorder) tuiPaneActiveStyle = lipgloss.NewStyle(). Padding(0, 1). BorderStyle(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("69")) + Foreground(tuiColorText). + BorderForeground(tuiColorShell) tuiPaneTitleStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("252")) + Foreground(tuiColorAccentDeep) tuiPaneTitleActiveStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("69")) + Foreground(tuiColorShellDeep) tuiPaneTitleActiveBadgeStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("16")). - Background(lipgloss.Color("69")) + Foreground(tuiColorSurfaceText). + Background(tuiColorShell) tuiPaneGapStyle = lipgloss.NewStyle() tuiSidebarStyle = lipgloss.NewStyle(). Padding(0, 1). BorderStyle(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("240")) + Foreground(tuiColorText). + BorderForeground(tuiColorBorder) tuiSidebarItemStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("252")) + Foreground(tuiColorText) tuiSidebarItemActiveStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("69")) + Foreground(tuiColorShellDeep) tuiFooterHintStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("241")) + Foreground(tuiColorTextSoft) tuiPaneReplyingStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("230")). - Background(lipgloss.Color("62")) + Foreground(tuiColorSurfaceText). + Background(tuiColorShellDeep) tuiPaneLoadingStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("16")). - Background(lipgloss.Color("221")) + Foreground(tuiColorText). + Background(tuiColorClaw) tuiPaneErrorBadgeStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("255")). - Background(lipgloss.Color("160")) + Foreground(tuiColorSurfaceText). + Background(tuiColorError) tuiLabelStyle = lipgloss.NewStyle(). Bold(true). - Foreground(lipgloss.Color("249")) + Foreground(tuiColorAccent) - tuiContentStyle = lipgloss.NewStyle().PaddingLeft(1) - tuiUserStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("87")) + tuiContentStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(tuiColorText) + tuiUserStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(tuiColorShellDeep) tuiAssistantStyle = lipgloss.NewStyle(). PaddingLeft(1). - Foreground(lipgloss.Color("230")) - tuiSystemStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("221")) - tuiToolStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("141")) + Foreground(tuiColorText) + tuiSystemStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(tuiColorAccent) + tuiToolStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(tuiColorAccentDeep) ) func renderTUIBanner(width int) string { diff --git a/pkg/api/server.go b/pkg/api/server.go index dfeef99..ca7e219 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -445,51 +445,50 @@ func (s *Server) Start(ctx context.Context) error { mux.HandleFunc("/nodes/register", s.handleRegister) mux.HandleFunc("/nodes/heartbeat", s.handleHeartbeat) mux.HandleFunc("/nodes/connect", s.handleNodeConnect) - mux.HandleFunc("/webui", s.handleWebUI) - mux.HandleFunc("/webui/", s.handleWebUIAsset) - mux.HandleFunc("/webui/api/config", s.handleWebUIConfig) - mux.HandleFunc("/webui/api/chat", s.handleWebUIChat) - mux.HandleFunc("/webui/api/chat/history", s.handleWebUIChatHistory) - mux.HandleFunc("/webui/api/chat/stream", s.handleWebUIChatStream) - mux.HandleFunc("/webui/api/chat/live", s.handleWebUIChatLive) - mux.HandleFunc("/webui/api/runtime", s.handleWebUIRuntime) - mux.HandleFunc("/webui/api/version", s.handleWebUIVersion) - mux.HandleFunc("/webui/api/provider/oauth/start", s.handleWebUIProviderOAuthStart) - mux.HandleFunc("/webui/api/provider/oauth/complete", s.handleWebUIProviderOAuthComplete) - mux.HandleFunc("/webui/api/provider/oauth/import", s.handleWebUIProviderOAuthImport) - mux.HandleFunc("/webui/api/provider/oauth/accounts", s.handleWebUIProviderOAuthAccounts) - mux.HandleFunc("/webui/api/provider/models", s.handleWebUIProviderModels) - mux.HandleFunc("/webui/api/provider/runtime", s.handleWebUIProviderRuntime) - mux.HandleFunc("/webui/api/provider/runtime/summary", s.handleWebUIProviderRuntimeSummary) - mux.HandleFunc("/webui/api/whatsapp/status", s.handleWebUIWhatsAppStatus) - mux.HandleFunc("/webui/api/whatsapp/logout", s.handleWebUIWhatsAppLogout) - mux.HandleFunc("/webui/api/whatsapp/qr.svg", s.handleWebUIWhatsAppQR) - mux.HandleFunc("/webui/api/upload", s.handleWebUIUpload) - mux.HandleFunc("/webui/api/nodes", s.handleWebUINodes) - mux.HandleFunc("/webui/api/node_dispatches", s.handleWebUINodeDispatches) - mux.HandleFunc("/webui/api/node_dispatches/replay", s.handleWebUINodeDispatchReplay) - mux.HandleFunc("/webui/api/node_artifacts", s.handleWebUINodeArtifacts) - mux.HandleFunc("/webui/api/node_artifacts/export", s.handleWebUINodeArtifactsExport) - mux.HandleFunc("/webui/api/node_artifacts/download", s.handleWebUINodeArtifactDownload) - mux.HandleFunc("/webui/api/node_artifacts/delete", s.handleWebUINodeArtifactDelete) - mux.HandleFunc("/webui/api/node_artifacts/prune", s.handleWebUINodeArtifactPrune) - mux.HandleFunc("/webui/api/cron", s.handleWebUICron) - mux.HandleFunc("/webui/api/skills", s.handleWebUISkills) - mux.HandleFunc("/webui/api/sessions", s.handleWebUISessions) - mux.HandleFunc("/webui/api/memory", s.handleWebUIMemory) - mux.HandleFunc("/webui/api/subagent_profiles", s.handleWebUISubagentProfiles) - mux.HandleFunc("/webui/api/subagents_runtime", s.handleWebUISubagentsRuntime) - mux.HandleFunc("/webui/api/subagents_runtime/live", s.handleWebUISubagentsRuntimeLive) - mux.HandleFunc("/webui/api/tool_allowlist_groups", s.handleWebUIToolAllowlistGroups) - mux.HandleFunc("/webui/api/tools", s.handleWebUITools) - mux.HandleFunc("/webui/api/mcp/install", s.handleWebUIMCPInstall) - mux.HandleFunc("/webui/api/task_audit", s.handleWebUITaskAudit) - mux.HandleFunc("/webui/api/task_queue", s.handleWebUITaskQueue) - mux.HandleFunc("/webui/api/ekg_stats", s.handleWebUIEKGStats) - mux.HandleFunc("/webui/api/exec_approvals", s.handleWebUIExecApprovals) - mux.HandleFunc("/webui/api/logs/stream", s.handleWebUILogsStream) - mux.HandleFunc("/webui/api/logs/live", s.handleWebUILogsLive) - mux.HandleFunc("/webui/api/logs/recent", s.handleWebUILogsRecent) + mux.HandleFunc("/", s.handleWebUIAsset) + mux.HandleFunc("/api/config", s.handleWebUIConfig) + mux.HandleFunc("/api/chat", s.handleWebUIChat) + mux.HandleFunc("/api/chat/history", s.handleWebUIChatHistory) + mux.HandleFunc("/api/chat/stream", s.handleWebUIChatStream) + mux.HandleFunc("/api/chat/live", s.handleWebUIChatLive) + mux.HandleFunc("/api/runtime", s.handleWebUIRuntime) + mux.HandleFunc("/api/version", s.handleWebUIVersion) + mux.HandleFunc("/api/provider/oauth/start", s.handleWebUIProviderOAuthStart) + mux.HandleFunc("/api/provider/oauth/complete", s.handleWebUIProviderOAuthComplete) + mux.HandleFunc("/api/provider/oauth/import", s.handleWebUIProviderOAuthImport) + mux.HandleFunc("/api/provider/oauth/accounts", s.handleWebUIProviderOAuthAccounts) + mux.HandleFunc("/api/provider/models", s.handleWebUIProviderModels) + mux.HandleFunc("/api/provider/runtime", s.handleWebUIProviderRuntime) + mux.HandleFunc("/api/provider/runtime/summary", s.handleWebUIProviderRuntimeSummary) + mux.HandleFunc("/api/whatsapp/status", s.handleWebUIWhatsAppStatus) + mux.HandleFunc("/api/whatsapp/logout", s.handleWebUIWhatsAppLogout) + mux.HandleFunc("/api/whatsapp/qr.svg", s.handleWebUIWhatsAppQR) + mux.HandleFunc("/api/upload", s.handleWebUIUpload) + mux.HandleFunc("/api/nodes", s.handleWebUINodes) + mux.HandleFunc("/api/node_dispatches", s.handleWebUINodeDispatches) + mux.HandleFunc("/api/node_dispatches/replay", s.handleWebUINodeDispatchReplay) + mux.HandleFunc("/api/node_artifacts", s.handleWebUINodeArtifacts) + mux.HandleFunc("/api/node_artifacts/export", s.handleWebUINodeArtifactsExport) + mux.HandleFunc("/api/node_artifacts/download", s.handleWebUINodeArtifactDownload) + mux.HandleFunc("/api/node_artifacts/delete", s.handleWebUINodeArtifactDelete) + mux.HandleFunc("/api/node_artifacts/prune", s.handleWebUINodeArtifactPrune) + mux.HandleFunc("/api/cron", s.handleWebUICron) + mux.HandleFunc("/api/skills", s.handleWebUISkills) + mux.HandleFunc("/api/sessions", s.handleWebUISessions) + mux.HandleFunc("/api/memory", s.handleWebUIMemory) + mux.HandleFunc("/api/subagent_profiles", s.handleWebUISubagentProfiles) + mux.HandleFunc("/api/subagents_runtime", s.handleWebUISubagentsRuntime) + mux.HandleFunc("/api/subagents_runtime/live", s.handleWebUISubagentsRuntimeLive) + mux.HandleFunc("/api/tool_allowlist_groups", s.handleWebUIToolAllowlistGroups) + mux.HandleFunc("/api/tools", s.handleWebUITools) + mux.HandleFunc("/api/mcp/install", s.handleWebUIMCPInstall) + mux.HandleFunc("/api/task_audit", s.handleWebUITaskAudit) + mux.HandleFunc("/api/task_queue", s.handleWebUITaskQueue) + mux.HandleFunc("/api/ekg_stats", s.handleWebUIEKGStats) + mux.HandleFunc("/api/exec_approvals", s.handleWebUIExecApprovals) + mux.HandleFunc("/api/logs/stream", s.handleWebUILogsStream) + mux.HandleFunc("/api/logs/live", s.handleWebUILogsLive) + mux.HandleFunc("/api/logs/recent", s.handleWebUILogsRecent) base := strings.TrimRight(strings.TrimSpace(s.whatsAppBase), "/") if base == "" { base = "/whatsapp" @@ -702,7 +701,7 @@ func (s *Server) handleWebUI(w http.ResponseWriter, r *http.Request) { MaxAge: 86400, }) } - if s.tryServeWebUIDist(w, r, "/webui/index.html") { + if s.tryServeWebUIDist(w, r, "/index.html") { return } w.Header().Set("Content-Type", "text/html; charset=utf-8") @@ -718,11 +717,19 @@ func (s *Server) handleWebUIAsset(w http.ResponseWriter, r *http.Request) { http.Error(w, "unauthorized", http.StatusUnauthorized) return } + if strings.HasPrefix(r.URL.Path, "/api/") { + http.NotFound(w, r) + return + } + if r.URL.Path == "/" { + s.handleWebUI(w, r) + return + } if s.tryServeWebUIDist(w, r, r.URL.Path) { return } // SPA fallback - if s.tryServeWebUIDist(w, r, "/webui/index.html") { + if s.tryServeWebUIDist(w, r, "/index.html") { return } http.NotFound(w, r) @@ -733,8 +740,8 @@ func (s *Server) tryServeWebUIDist(w http.ResponseWriter, r *http.Request, reqPa if dir == "" { return false } - p := strings.TrimPrefix(reqPath, "/webui/") - if reqPath == "/webui" || reqPath == "/webui/" || reqPath == "/webui/index.html" { + p := strings.TrimPrefix(reqPath, "/") + if reqPath == "/" || reqPath == "/index.html" { p = "index.html" } p = filepath.Clean(strings.TrimPrefix(p, "/")) @@ -1600,7 +1607,7 @@ func (s *Server) handleWebUIChatHistory(w http.ResponseWriter, r *http.Request) func (s *Server) handleWebUIChatStream(w http.ResponseWriter, r *http.Request) { w.Header().Set("Deprecation", "true") - w.Header().Set("X-Clawgo-Replaced-By", "/webui/api/chat/live") + w.Header().Set("X-Clawgo-Replaced-By", "/api/chat/live") if !s.checkAuth(r) { http.Error(w, "unauthorized", http.StatusUnauthorized) return @@ -3559,7 +3566,7 @@ func (s *Server) fetchRemoteNodeRegistry(ctx context.Context, node nodes.NodeInf if baseURL == "" { return nil, fmt.Errorf("node %s endpoint missing", strings.TrimSpace(node.ID)) } - reqURL := baseURL + "/webui/api/subagents_runtime?action=registry" + reqURL := baseURL + "/api/subagents_runtime?action=registry" if tok := strings.TrimSpace(node.Token); tok != "" { reqURL += "&token=" + url.QueryEscape(tok) } @@ -6023,7 +6030,7 @@ func (s *Server) handleWebUILogsLive(w http.ResponseWriter, r *http.Request) { func (s *Server) handleWebUILogsStream(w http.ResponseWriter, r *http.Request) { w.Header().Set("Deprecation", "true") - w.Header().Set("X-Clawgo-Replaced-By", "/webui/api/logs/live") + w.Header().Set("X-Clawgo-Replaced-By", "/api/logs/live") if !s.checkAuth(r) { http.Error(w, "unauthorized", http.StatusUnauthorized) return @@ -6132,13 +6139,13 @@ const webUIHTML = `