diff --git a/internal/api/handlers_chat.go b/internal/api/handlers_chat.go index aef47ba..58cb135 100644 --- a/internal/api/handlers_chat.go +++ b/internal/api/handlers_chat.go @@ -206,8 +206,11 @@ func (s *Server) handleChatHistory(w http.ResponseWriter, r *http.Request) { } messages := s.convStore.Get() writeJSON(w, map[string]interface{}{ - "messages": messages, - "tokens": s.convStore.ApproxTokenCount(), + "messages": messages, + "tokens": s.convStore.ApproxTokenCount(), + "max_tokens": maxTokensApprox, + "summarize_at": summarizeThreshold, + "summary": s.convStore.GetSummary(), }) } @@ -219,3 +222,16 @@ func (s *Server) handleChatClear(w http.ResponseWriter, r *http.Request) { s.convStore.Clear() writeJSON(w, map[string]string{"status": "ok"}) } + +func (s *Server) handleChatSummarize(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + writeError(w, "POST only", http.StatusMethodNotAllowed) + return + } + s.autoSummarize() + writeJSON(w, map[string]interface{}{ + "status": "ok", + "tokens": s.convStore.ApproxTokenCount(), + "summary": s.convStore.GetSummary(), + }) +} diff --git a/internal/api/server.go b/internal/api/server.go index 57e4dfb..2e6f9fb 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -85,6 +85,7 @@ func (s *Server) routes() { s.mux.HandleFunc("/api/chat", s.handleChat) s.mux.HandleFunc("/api/chat/history", s.handleChatHistory) s.mux.HandleFunc("/api/chat/clear", s.handleChatClear) + s.mux.HandleFunc("/api/chat/summarize", s.handleChatSummarize) s.mux.HandleFunc("/api/tool/call", s.handleToolCall) s.mux.HandleFunc("/api/tools/list", s.handleToolList) s.mux.HandleFunc("/api/shell/chat", s.handleShellChat) diff --git a/internal/config/config.go b/internal/config/config.go index a8b5036..cb26ec0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -12,66 +12,66 @@ import ( ) type Profile struct { - Name string `yaml:"name"` - Pseudo string `yaml:"pseudo"` - Email string `yaml:"email"` - Languages []string `yaml:"languages"` + Name string `yaml:"name" json:"name"` + Pseudo string `yaml:"pseudo" json:"pseudo"` + Email string `yaml:"email" json:"email"` + Languages []string `yaml:"languages" json:"languages"` Preferences struct { - Editor string `yaml:"editor"` - Shell string `yaml:"shell"` - Theme string `yaml:"theme"` - DefaultAI string `yaml:"default_ai"` - AutoUpdate bool `yaml:"auto_update"` - CheckOnStart bool `yaml:"check_on_start"` - Language string `yaml:"language"` - KeyboardLayout string `yaml:"keyboard_layout"` - } `yaml:"preferences"` + Editor string `yaml:"editor" json:"editor"` + Shell string `yaml:"shell" json:"shell"` + Theme string `yaml:"theme" json:"theme"` + DefaultAI string `yaml:"default_ai" json:"default_ai"` + AutoUpdate bool `yaml:"auto_update" json:"auto_update"` + CheckOnStart bool `yaml:"check_on_start" json:"check_on_start"` + Language string `yaml:"language" json:"language"` + KeyboardLayout string `yaml:"keyboard_layout" json:"keyboard_layout"` + } `yaml:"preferences" json:"preferences"` } type AIProvider struct { - Name string `yaml:"name"` - APIKey string `yaml:"api_key,omitempty"` - BaseURL string `yaml:"base_url,omitempty"` - Model string `yaml:"model"` - Active bool `yaml:"active"` + Name string `yaml:"name" json:"name"` + APIKey string `yaml:"api_key,omitempty" json:"api_key,omitempty"` + BaseURL string `yaml:"base_url,omitempty" json:"base_url,omitempty"` + Model string `yaml:"model" json:"model"` + Active bool `yaml:"active" json:"active"` } type ToolConfig struct { - Name string `yaml:"name"` - Installed bool `yaml:"installed"` - Version string `yaml:"version"` - AutoUpdate bool `yaml:"auto_update"` + Name string `yaml:"name" json:"name"` + Installed bool `yaml:"installed" json:"installed"` + Version string `yaml:"version" json:"version"` + AutoUpdate bool `yaml:"auto_update" json:"auto_update"` } type SSHConnection struct { - Name string `yaml:"name"` - Host string `yaml:"host"` - Port int `yaml:"port"` - User string `yaml:"user"` - Password string `yaml:"password,omitempty"` - KeyPath string `yaml:"key_path,omitempty"` + Name string `yaml:"name" json:"name"` + Host string `yaml:"host" json:"host"` + Port int `yaml:"port" json:"port"` + User string `yaml:"user" json:"user"` + Password string `yaml:"password,omitempty" json:"password,omitempty"` + KeyPath string `yaml:"key_path,omitempty" json:"key_path,omitempty"` } type MuyueConfig struct { - Version string `yaml:"version"` - Profile Profile `yaml:"profile"` + Version string `yaml:"version" json:"version"` + Profile Profile `yaml:"profile" json:"profile"` AI struct { - Providers []AIProvider `yaml:"providers"` - } `yaml:"ai"` - Tools []ToolConfig `yaml:"tools"` + Providers []AIProvider `yaml:"providers" json:"providers"` + } `yaml:"ai" json:"ai"` + Tools []ToolConfig `yaml:"tools" json:"tools"` BMAD struct { - Installed bool `yaml:"installed"` - Version string `yaml:"version"` - Global bool `yaml:"global"` - } `yaml:"bmad"` + Installed bool `yaml:"installed" json:"installed"` + Version string `yaml:"version" json:"version"` + Global bool `yaml:"global" json:"global"` + } `yaml:"bmad" json:"bmad"` Terminal struct { - CustomPrompt bool `yaml:"custom_prompt"` - PromptTheme string `yaml:"prompt_theme"` - SSH []SSHConnection `yaml:"ssh"` - FontSize int `yaml:"font_size"` - FontFamily string `yaml:"font_family"` - Theme string `yaml:"theme"` - } `yaml:"terminal"` + CustomPrompt bool `yaml:"custom_prompt" json:"custom_prompt"` + PromptTheme string `yaml:"prompt_theme" json:"prompt_theme"` + SSH []SSHConnection `yaml:"ssh" json:"ssh"` + FontSize int `yaml:"font_size" json:"font_size"` + FontFamily string `yaml:"font_family" json:"font_family"` + Theme string `yaml:"theme" json:"theme"` + } `yaml:"terminal" json:"terminal"` } type TerminalTheme struct { diff --git a/internal/platform/platform.go b/internal/platform/platform.go index c34c488..6aa8cb5 100644 --- a/internal/platform/platform.go +++ b/internal/platform/platform.go @@ -24,12 +24,12 @@ const ( ) type SystemInfo struct { - OS OS - Arch Arch - IsWSL bool - Shell string - Terminal string - PackageManager string + OS OS `json:"os"` + Arch Arch `json:"arch"` + IsWSL bool `json:"is_wsl"` + Shell string `json:"shell"` + Terminal string `json:"terminal"` + PackageManager string `json:"package_manager"` } func Detect() SystemInfo { diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 9721c7b..9898def 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -14,27 +14,27 @@ import ( ) type ToolStatus struct { - Name string `yaml:"name"` - Installed bool `yaml:"installed"` - Version string `yaml:"version"` - Path string `yaml:"path"` - Latest string `yaml:"latest"` - NeedsUpdate bool `yaml:"needs_update"` - Category string `yaml:"category"` + Name string `yaml:"name" json:"name"` + Installed bool `yaml:"installed" json:"installed"` + Version string `yaml:"version" json:"version"` + Path string `yaml:"path" json:"path"` + Latest string `yaml:"latest" json:"latest"` + NeedsUpdate bool `yaml:"needs_update" json:"needs_update"` + Category string `yaml:"category" json:"category"` } type RuntimeStatus struct { - Name string `yaml:"name"` - Installed bool `yaml:"installed"` - Version string `yaml:"version"` + Name string `yaml:"name" json:"name"` + Installed bool `yaml:"installed" json:"installed"` + Version string `yaml:"version" json:"version"` } type ScanResult struct { - System platform.SystemInfo `yaml:"system"` - Tools []ToolStatus `yaml:"tools"` - Runtimes []RuntimeStatus `yaml:"runtimes"` - ShellSetup bool `yaml:"shell_setup"` - GitConfigured bool `yaml:"git_configured"` + System platform.SystemInfo `yaml:"system" json:"system"` + Tools []ToolStatus `yaml:"tools" json:"tools"` + Runtimes []RuntimeStatus `yaml:"runtimes" json:"runtimes"` + ShellSetup bool `yaml:"shell_setup" json:"shell_setup"` + GitConfigured bool `yaml:"git_configured" json:"git_configured"` } var ( diff --git a/web/src/api/client.js b/web/src/api/client.js index 9affabf..6536001 100644 --- a/web/src/api/client.js +++ b/web/src/api/client.js @@ -56,6 +56,7 @@ const api = { saveTerminalSettings: (settings) => request('/terminal/settings', { method: 'PUT', body: JSON.stringify(settings) }), getChatHistory: () => request('/chat/history'), clearChat: () => request('/chat/clear', { method: 'POST' }), + summarizeChat: () => request('/chat/summarize', { method: 'POST' }), sendChat: (message, stream = true, onChunk, signal) => { if (!stream) { return request('/chat', { method: 'POST', body: JSON.stringify({ message, stream: false }) }) diff --git a/web/src/components/Dashboard.jsx b/web/src/components/Dashboard.jsx index 9be3e84..1c98f70 100644 --- a/web/src/components/Dashboard.jsx +++ b/web/src/components/Dashboard.jsx @@ -21,6 +21,13 @@ function MiniGraph({ data, max, color, label, unit }) { {last.toFixed(1)}{unit} + + + + + + + @@ -29,7 +36,6 @@ function MiniGraph({ data, max, color, label, unit }) { export default function Dashboard({ api, refreshRef }) { const { t } = useI18n() - const [dashboardStatus, setDashboardStatus] = useState(null) const [quota, setQuota] = useState(null) const [recentCmds, setRecentCmds] = useState([]) const [processes, setProcesses] = useState([]) @@ -41,14 +47,12 @@ export default function Dashboard({ api, refreshRef }) { const loadData = useCallback(async () => { try { - const [dashData, quotaData, cmdData, procData, metricsData] = await Promise.all([ - api.getDashboardStatus().catch(() => null), + const [quotaData, cmdData, procData, metricsData] = await Promise.all([ api.getProvidersQuota().catch(() => null), api.getRecentCommands().catch(() => ({ commands: [] })), api.getRunningProcesses().catch(() => ({ processes: [] })), api.getSystemMetrics().catch(() => null), ]) - setDashboardStatus(dashData) setQuota(quotaData?.providers || []) setRecentCmds(cmdData.commands || []) setProcesses(procData.processes || []) @@ -76,7 +80,7 @@ export default function Dashboard({ api, refreshRef }) { return (
- {/* CPU / RAM / Network Graphs */} + {/* CPU */}
CPU @@ -85,18 +89,20 @@ export default function Dashboard({ api, refreshRef }) {
+ {/* RAM */}
RAM - {metrics ? `${metrics.mem_used_mb.toFixed(0)}/${metrics.mem_total_mb.toFixed(0)} MB` : '—'} + {metrics ? `${metrics.mem_used_mb.toFixed(0)}/${metrics.mem_total_mb.toFixed(0)}` : '—'}
+ {/* Network */}
Network - {metrics ? `↓${metrics.net_rx_kbs.toFixed(0)} ↑${metrics.net_tx_kbs.toFixed(0)} KB/s` : '—'} + {metrics ? `↓${metrics.net_rx_kbs.toFixed(0)} ↑${metrics.net_tx_kbs.toFixed(0)}` : '—'}
@@ -114,7 +120,7 @@ export default function Dashboard({ api, refreshRef }) {
- {m.remaining}/{m.total} + {m.used}/{m.total}
))} {minimax && minimax.data?.models?.length === 0 && ( @@ -136,12 +142,12 @@ export default function Dashboard({ api, refreshRef }) { {/* Running Processes */}
- Running Processes + Processes {processes.length}
{processes.length === 0 && No relevant processes} - {processes.slice(0, 8).map((p, i) => ( + {processes.map((p, i) => (
{p.name} cpu {p.cpu}% · mem {p.mem}% @@ -157,7 +163,7 @@ export default function Dashboard({ api, refreshRef }) {
{recentCmds.length === 0 && No history} - {recentCmds.slice(0, 8).map((c, i) => ( + {recentCmds.map((c, i) => (
{c.shell} {c.cmd.length > 45 ? c.cmd.slice(0, 42) + '...' : c.cmd} @@ -165,38 +171,6 @@ export default function Dashboard({ api, refreshRef }) { ))}
- - {/* Services */} -
-
- Services -
- {dashboardStatus ? ( -
-
- MCP - {dashboardStatus.mcp?.healthy || 0}/{dashboardStatus.mcp?.total || 0} healthy -
-
- LSP - {dashboardStatus.lsp?.installed || 0}/{dashboardStatus.lsp?.total || 0} installed -
-
- Skills - {dashboardStatus.skills?.total || 0} deployed -
- {(dashboardStatus.skills?.issues || []).length > 0 && ( -
- {(dashboardStatus.skills.issues || []).slice(0, 3).map((issue, i) => ( -
⚠ {issue}
- ))} -
- )} -
- ) : ( - Loading... - )} -
) } diff --git a/web/src/components/Studio.jsx b/web/src/components/Studio.jsx index fd4899d..63cff2a 100644 --- a/web/src/components/Studio.jsx +++ b/web/src/components/Studio.jsx @@ -284,6 +284,7 @@ export default function Studio({ api }) { const [streamThinking, setStreamThinking] = useState('') const [streamToolCalls, setStreamToolCalls] = useState([]) const [loaded, setLoaded] = useState(false) + const [tokenInfo, setTokenInfo] = useState({ used: 0, max: 100000, summarizeAt: 80000 }) const messagesEnd = useRef(null) const textareaRef = useRef(null) const abortRef = useRef(null) @@ -297,6 +298,11 @@ export default function Studio({ api }) { { id: 'welcome', role: 'system', content: t('studio.welcomeNew'), time: new Date().toISOString() }, ]) } + setTokenInfo({ + used: data.tokens || 0, + max: data.max_tokens || 100000, + summarizeAt: data.summarize_at || 80000, + }) setLoaded(true) }).catch(() => { setMessages([ @@ -317,6 +323,28 @@ export default function Studio({ api }) { } }, [input]) + const refreshTokens = useCallback(async () => { + try { + const data = await api.getChatHistory() + setTokenInfo({ + used: data.tokens || 0, + max: data.max_tokens || 100000, + summarizeAt: data.summarize_at || 80000, + }) + } catch {} + }, [api]) + + const handleSummarize = useCallback(async () => { + setMessages(prev => [...prev, { id: Date.now().toString(), role: 'system', content: 'Résumé de la conversation en cours...', time: new Date().toISOString() }]) + try { + const data = await api.summarizeChat() + setTokenInfo(prev => ({ ...prev, used: data.tokens || 0 })) + setMessages(prev => [...prev, { id: (Date.now() + 1).toString(), role: 'system', content: '✓ Conversation résumée automatiquement. Le contexte a été compressé.', time: new Date().toISOString() }]) + } catch (err) { + setMessages(prev => [...prev, { id: (Date.now() + 1).toString(), role: 'system', content: `Erreur de résumé: ${err.message}`, time: new Date().toISOString() }]) + } + }, [api]) + const handleClear = useCallback(async () => { try { await api.clearChat() @@ -341,6 +369,7 @@ export default function Studio({ api }) { '## Commandes Studio', '', '- `/clear` - Effacer la conversation', + '- `/summarize` - Résumer la conversation précédente', '- `/help` - Afficher cette aide', '- `/plan ` - Demander un plan structuré', '- `/export` - Exporter la conversation en Markdown', @@ -359,6 +388,11 @@ export default function Studio({ api }) { return } + if (text === '/summarize') { + handleSummarize() + return + } + if (text === '/model') { api.getProviders().then(data => { const active = data.providers?.find(p => p.active) @@ -474,8 +508,9 @@ export default function Studio({ api }) { setStreamThinking('') setStreamToolCalls([]) abortRef.current = null + refreshTokens() } - }, [input, loading, api, t, handleClear, streaming]) + }, [input, loading, api, t, handleClear, streaming, refreshTokens, handleSummarize]) const handleStop = useCallback(() => { if (abortRef.current) { @@ -515,6 +550,18 @@ export default function Studio({ api }) {
+
+
+
= tokenInfo.summarizeAt ? 'warn' : ''}`} + style={{ width: `${Math.min(100, (tokenInfo.used / tokenInfo.max) * 100)}%` }} + /> +
+ + {(tokenInfo.used / 1000).toFixed(1)}k / {(tokenInfo.max / 1000).toFixed(0)}k tokens + {tokenInfo.used >= tokenInfo.summarizeAt && ' · résumé automatique déclenché'} + +