diff --git a/internal/agent/definitions.go b/internal/agent/definitions.go
index 0c0b534..898b421 100644
--- a/internal/agent/definitions.go
+++ b/internal/agent/definitions.go
@@ -3,6 +3,7 @@ package agent
import (
"context"
"fmt"
+ "os"
"os/exec"
"path/filepath"
"strings"
@@ -14,6 +15,13 @@ type TerminalParams struct {
Timeout int `json:"timeout,omitempty" description:"Timeout in seconds (default 60, max 300)"`
}
+type TerminalResponse struct {
+ Content string `json:"content"`
+ IsError bool `json:"is_error"`
+ SudoBlocked bool `json:"sudo_blocked,omitempty"`
+ Command string `json:"command,omitempty"`
+}
+
func NewTerminalTool() (*ToolDefinition, error) {
return NewTool("terminal",
"Execute a shell command on the local system and return the output. Use for running builds, tests, git operations, package management, system info, or any CLI task. Commands run in the user's home directory by default. Long-running commands are auto-terminated.",
@@ -22,6 +30,18 @@ func NewTerminalTool() (*ToolDefinition, error) {
return TextErrorResponse("command is required"), nil
}
+ if os.Geteuid() != 0 {
+ trimmed := strings.TrimSpace(p.Command)
+ lower := strings.ToLower(trimmed)
+ if strings.HasPrefix(lower, "sudo ") || strings.HasPrefix(lower, "doas ") || strings.HasPrefix(lower, "run0 ") || strings.HasPrefix(lower, "pkexec ") {
+ return ToolResponse{
+ Content: fmt.Sprintf("BLOCKED: Command '%s' requires elevated privileges (%s). The current user is not root. Do NOT retry with sudo. Explain to the user that this command needs admin privileges and suggest an alternative, or tell them to run it manually in their terminal.", trimmed, strings.Fields(trimmed)[0]),
+ IsError: true,
+ Meta: map[string]string{"sudo_blocked": "true", "command": trimmed},
+ }, nil
+ }
+ }
+
timeout := time.Duration(p.Timeout) * time.Second
if timeout == 0 {
timeout = 60 * time.Second
diff --git a/internal/agent/prompts/studio_system.md b/internal/agent/prompts/studio_system.md
index d32560a..d81b6bb 100644
--- a/internal/agent/prompts/studio_system.md
+++ b/internal/agent/prompts/studio_system.md
@@ -2,6 +2,16 @@ Tu es l'assistant IA de **Muyue Studio**, le centre de commandement de l'environ
Tu es intégré dans Muyue, un gestionnaire d'environnement de développement de bureau. Ton rôle est d'aider l'utilisateur à configurer, gérer et optimiser son environnement dev.
+
+1. **AGIS, ne décris pas** — Si l'utilisateur demande de faire quelque chose, utilise les outils immédiatement. Ne dis pas "je pourrais faire X" — fais-le.
+2. **SOIS AUTONOME** — Ne pose pas de questions si tu peux chercher, lire, déduire. Essaie plusieurs approches avant de bloquer. Ne t'arrête que pour les erreurs bloquantes réelles (credentials manquants, permissions, etc.).
+3. **SOIS CONCIS** — Max 4 lignes par défaut. Pas de préambule ("Voici...", "Je vais..."), pas de postambule ("N'hésitez pas...", "J'espère que..."). Réponse directe. Un mot quand c'est suffisant.
+4. **GÈRE LES ERREURS** — Si un outil échoue, essaie 2-3 approches alternatives avant de rapporter l'échec. Lis le message d'erreur complet, isole la cause racine.
+5. **NE DEVINE PAS** — Lis les fichiers avant d'éditer. Utilise les outils pour obtenir les informations manquantes (lire, chercher, grep).
+6. **CONFIDENTIALITÉ** — Ne révèle jamais les clés API, mots de passe, tokens ou informations sensibles.
+7. **LANGUE** — Réponds dans la même langue que l'utilisateur.
+
+
## Environnement
Muyue gère :
@@ -13,32 +23,70 @@ Muyue gère :
## Outils disponibles
-Tu as accès à des outils. Utilise-les concrètement, ne décris pas ce que tu ferais — fais-le.
+| Outil | Usage |
+|-------|-------|
+| **terminal** | Exécuter des commandes shell (builds, tests, git, etc.) |
+| **crush_run** | Déléguer une tâche complexe à Crush (édition de fichiers, refactoring, debug) — préfère cet outil pour les tâches multi-fichiers ou l'écriture de code |
+| **read_file** | Lire le contenu d'un fichier |
+| **list_files** | Lister les fichiers d'un répertoire |
+| **search_files** | Chercher des fichiers par motif (glob) |
+| **grep_content** | Chercher du texte dans les fichiers |
+| **get_config** | Lire la configuration Muyue |
+| **set_provider** | Configurer un fournisseur IA |
+| **manage_ssh** | Gérer les connexions SSH |
+| **web_fetch** | Récupérer le contenu d'une URL |
-- **terminal** : Exécuter des commandes shell (builds, tests, git, etc.)
-- **crush_run** : Déléguer une tâche complexe à l'agent Crush (édition de fichiers, refactoring, debug)
-- **read_file** : Lire le contenu d'un fichier
-- **list_files** : Lister les fichiers d'un répertoire
-- **search_files** : Chercher des fichiers par motif (glob)
-- **grep_content** : Chercher du texte dans le contenu des fichiers
-- **get_config** : Lire la configuration Muyue
-- **set_provider** : Configurer un fournisseur IA
-- **manage_ssh** : Gérer les connexions SSH
-- **web_fetch** : Récupérer le contenu d'une URL
+
+- **Recherche avant action** — Utilise `search_files`, `grep_content`, `read_file` avant de supposer quoi que ce soit sur l'état du système
+- **Délégation intelligente** — Pour les tâches complexes (refactoring, création de fichiers, debug multi-fichiers), utilise `crush_run` au lieu d'enchaîner des commandes terminal
+- **Parallélisme** — Lance plusieurs appels d'outils en parallèle quand les opérations sont indépendantes
+- **Troncature** — Si un résultat d'outil dépasse 2000 caractères, résume les points clés au lieu de tout afficher
+- **Une chose à la fois** — Sauf si les opérations sont indépendantes, exécute séquentiellement
+
-## Règles
+
+- Décide par toi-même : cherche, lis, déduis, agis
+- Ne demande confirmation que pour : actions destructrices (suppression, overwrite), plusieurs approches valides avec des trade-offs importants
+- Si bloqué : documente (a) ce que tu as essayé, (b) pourquoi tu es bloqué, (c) l'action minimale requise
+- Ne t'arrête jamais pour : tâche trop grosse (découpe), trop de fichiers (change-les), complexité (gère-la)
+
-1. **AGIS, ne décris pas** — Si l'utilisateur demande de faire quelque chose, utilise les outils pour le faire. Ne dis pas "je pourrais faire X" — fais-le.
-2. **Sois concis** — Pas de préambule, pas de blabla. Réponse directe.
-3. **Une chose à la fois** — N'appelle pas plusieurs outils simultanément sauf si c'est nécessaire.
-4. **Gère les erreurs** — Si un outil échoue, essaie une approche différente avant de le dire à l'utilisateur.
-5. **Ne devine pas** — Si tu n'as pas assez d'informations, utilise les outils pour les obtenir (lire un fichier, chercher, etc.)
-6. **Confidentialité** — Ne révèle jamais les clés API, mots de passe ou informations sensibles dans tes réponses.
-7. **Langue** — Réponds dans la même langue que l'utilisateur.
+
+1. Lis le message d'erreur complet
+2. Comprends la cause racine
+3. Essaie une approche différente (pas la même)
+4. Cherche du code similaire qui fonctionne
+5. Applique un correctif ciblé
+6. Vérifie que ça marche
+7. Pour chaque erreur, essaie au moins 2-3 stratégies avant de conclure que c'est bloquant
+
## Format des réponses
-- Code : utilise des blocs markdown
-- Résultats d'outils : résume les points clés, ne colle pas des milliers de lignes
-- Erreurs : explique clairement et propose une solution
-- Succès : confirme brièvement ce qui a été fait
+- **Code** : blocs markdown avec le langage spécifié
+- **Résultats d'outils** : résume les points clés, max 2000 caractères, ne copie pas des milliers de lignes
+- **Erreurs** : explique clairement la cause et propose une solution concrète
+- **Succès** : confirme brièvement ce qui a été fait (1 ligne)
+- **Multi-fichiers** : liste les fichiers modifiés avec `fichier:ligne` pour les références
+
+## Diagrammes Mermaid
+
+Tu peux utiliser des diagrammes Mermaid pour visualiser des architectures, flux, séquences, etc.
+Utilise un bloc code avec le langage `mermaid` :
+
+```mermaid
+graph TD
+ A[Début] --> B{Décision}
+ B -->|Oui| C[Action]
+ B -->|Non| D[Fin]
+```
+
+Types utiles :
+- `graph TD/LR` — Architecture, flux de données
+- `sequenceDiagram` — Interactions entre composants
+- `flowchart` — Processus et décisions
+- `classDiagram` — Structures de données
+- `erDiagram` — Schémas de base de données
+- `gantt` — Planning et timelines
+
+Utilise Mermaid quand ça apporte de la clarté : architecture complexe, flux multi-étapes, relations entre entités. Ne l'utilise pas pour du texte simple.
diff --git a/internal/api/chat_engine.go b/internal/api/chat_engine.go
index 79193a0..65d01f6 100644
--- a/internal/api/chat_engine.go
+++ b/internal/api/chat_engine.go
@@ -21,6 +21,7 @@ type ChatEngine struct {
tools json.RawMessage
onChunk func(map[string]interface{})
stream bool
+ TotalTokens int
}
// NewChatEngine creates a new ChatEngine instance.
@@ -71,6 +72,10 @@ func (ce *ChatEngine) RunWithTools(ctx context.Context, messages []orchestrator.
return finalContent, allToolCalls, allToolResults, err
}
+ if resp.Usage.TotalTokens > 0 {
+ ce.TotalTokens += resp.Usage.TotalTokens
+ }
+
choice := resp.Choices[0]
content := cleanThinkingTags(choice.Message.Content)
@@ -123,6 +128,11 @@ func (ce *ChatEngine) RunWithTools(ctx context.Context, messages []orchestrator.
"content": result.Content,
"is_error": result.IsError,
}
+ if result.Meta != nil {
+ for k, v := range result.Meta {
+ resultData[k] = v
+ }
+ }
allToolResults = append(allToolResults, map[string]interface{}{
"tool_call_id": tc.ID,
"name": tc.Function.Name,
@@ -149,6 +159,11 @@ func (ce *ChatEngine) RunWithTools(ctx context.Context, messages []orchestrator.
return finalContent, allToolCalls, allToolResults, nil
}
+// ProviderName returns the name of the active provider used by the engine.
+func (ce *ChatEngine) ProviderName() string {
+ return ce.orchestrator.ProviderName()
+}
+
// RunNonStream executes chat without streaming content to client.
func (ce *ChatEngine) RunNonStream(ctx context.Context, messages []orchestrator.Message) (string, error) {
var finalContent string
@@ -159,6 +174,10 @@ func (ce *ChatEngine) RunNonStream(ctx context.Context, messages []orchestrator.
return finalContent, err
}
+ if resp.Usage.TotalTokens > 0 {
+ ce.TotalTokens += resp.Usage.TotalTokens
+ }
+
choice := resp.Choices[0]
content := cleanThinkingTags(choice.Message.Content)
diff --git a/internal/api/consumption.go b/internal/api/consumption.go
new file mode 100644
index 0000000..b618c1d
--- /dev/null
+++ b/internal/api/consumption.go
@@ -0,0 +1,127 @@
+package api
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+
+ "github.com/muyue/muyue/internal/config"
+)
+
+type consumptionEntry struct {
+ Date string `json:"date"`
+ Tokens int `json:"tokens"`
+ Requests int `json:"requests"`
+}
+
+type providerConsumption struct {
+ Name string `json:"name"`
+ Daily []consumptionEntry `json:"daily"`
+ Total int `json:"total_tokens"`
+ Requests int `json:"total_requests"`
+}
+
+type consumptionStore struct {
+ mu sync.Mutex
+ providers map[string]*providerConsumption
+}
+
+func newConsumptionStore() *consumptionStore {
+ cs := &consumptionStore{
+ providers: make(map[string]*providerConsumption),
+ }
+ cs.load()
+ cs.prune()
+ return cs
+}
+
+func (cs *consumptionStore) Record(providerName string, tokens int) {
+ if tokens <= 0 || providerName == "" {
+ return
+ }
+ cs.mu.Lock()
+ defer cs.mu.Unlock()
+
+ today := time.Now().UTC().Format("2006-01-02")
+
+ p, ok := cs.providers[providerName]
+ if !ok {
+ p = &providerConsumption{Name: providerName}
+ cs.providers[providerName] = p
+ }
+
+ p.Total += tokens
+ p.Requests++
+
+ if len(p.Daily) > 0 && p.Daily[len(p.Daily)-1].Date == today {
+ p.Daily[len(p.Daily)-1].Tokens += tokens
+ p.Daily[len(p.Daily)-1].Requests++
+ } else {
+ p.Daily = append(p.Daily, consumptionEntry{
+ Date: today,
+ Tokens: tokens,
+ Requests: 1,
+ })
+ }
+
+ cs.save()
+}
+
+func (cs *consumptionStore) GetAll() map[string]*providerConsumption {
+ cs.mu.Lock()
+ defer cs.mu.Unlock()
+
+ result := make(map[string]*providerConsumption)
+ for k, v := range cs.providers {
+ pc := *v
+ daily := make([]consumptionEntry, len(v.Daily))
+ copy(daily, v.Daily)
+ pc.Daily = daily
+ result[k] = &pc
+ }
+ return result
+}
+
+func (cs *consumptionStore) prune() {
+ cutoff := time.Now().UTC().AddDate(0, 0, -7).Format("2006-01-02")
+ for _, p := range cs.providers {
+ filtered := make([]consumptionEntry, 0)
+ for _, d := range p.Daily {
+ if d.Date >= cutoff {
+ filtered = append(filtered, d)
+ }
+ }
+ p.Daily = filtered
+ }
+}
+
+func (cs *consumptionStore) filePath() string {
+ dir, err := config.ConfigDir()
+ if err != nil {
+ return ""
+ }
+ return filepath.Join(dir, "consumption.json")
+}
+
+func (cs *consumptionStore) load() {
+ fp := cs.filePath()
+ if fp == "" {
+ return
+ }
+ data, err := os.ReadFile(fp)
+ if err != nil {
+ return
+ }
+ json.Unmarshal(data, &cs.providers)
+}
+
+func (cs *consumptionStore) save() {
+ fp := cs.filePath()
+ if fp == "" {
+ return
+ }
+ data, _ := json.Marshal(cs.providers)
+ os.WriteFile(fp, data, 0644)
+}
diff --git a/internal/api/conversation.go b/internal/api/conversation.go
index 505d7cb..195c846 100644
--- a/internal/api/conversation.go
+++ b/internal/api/conversation.go
@@ -32,9 +32,10 @@ type Conversation struct {
}
type ConversationStore struct {
- mu sync.RWMutex
- path string
- conv *Conversation
+ mu sync.RWMutex
+ path string
+ conv *Conversation
+ realTokens int
}
type TokenCount struct {
@@ -133,6 +134,7 @@ func (cs *ConversationStore) Clear() {
cs.conv.Summary = ""
cs.conv.CreatedAt = time.Now().Format(time.RFC3339)
cs.conv.UpdatedAt = time.Now().Format(time.RFC3339)
+ cs.realTokens = 0
cs.save()
}
@@ -154,9 +156,22 @@ func (cs *ConversationStore) TrimOld(keepCount int) {
}
func (cs *ConversationStore) ApproxTokenCount() int {
+ if cs.realTokens > 0 {
+ return cs.realTokens
+ }
return cs.ApproxTokenCountDetailed().total
}
+// AddRealTokens accumulates actual token counts from the API response.
+func (cs *ConversationStore) AddRealTokens(tokens int) {
+ if tokens <= 0 {
+ return
+ }
+ cs.mu.Lock()
+ cs.realTokens += tokens
+ cs.mu.Unlock()
+}
+
func (cs *ConversationStore) ApproxTokenCountDetailed() TokenCount {
cs.mu.RLock()
defer cs.mu.RUnlock()
diff --git a/internal/api/handlers_chat.go b/internal/api/handlers_chat.go
index 58cb135..673f3a3 100644
--- a/internal/api/handlers_chat.go
+++ b/internal/api/handlers_chat.go
@@ -3,9 +3,12 @@ package api
import (
"context"
"encoding/json"
+ "fmt"
"net/http"
+ "os"
"regexp"
"strings"
+ "time"
"github.com/muyue/muyue/internal/agent"
"github.com/muyue/muyue/internal/orchestrator"
@@ -42,7 +45,14 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) {
writeError(w, err.Error(), http.StatusServiceUnavailable)
return
}
- orb.SetSystemPrompt(agent.StudioSystemPrompt())
+ var studioPrompt strings.Builder
+ studioPrompt.WriteString(agent.StudioSystemPrompt())
+ studioPrompt.WriteString(fmt.Sprintf("\nDate: %s\nHeure: %s\n", time.Now().Format("02/01/2006"), time.Now().Format("15:04:05")))
+ studioPrompt.WriteString(fmt.Sprintf("Root: %t\n", os.Geteuid() == 0))
+ if os.Geteuid() != 0 {
+ studioPrompt.WriteString("⚠️ Session utilisateur standard — les commandes sudo/doas nécessitent une autorisation. N'utilise PAS sudo ou doas sans demander.\n")
+ }
+ orb.SetSystemPrompt(studioPrompt.String())
orb.SetTools(s.agentToolsJSON)
if body.Stream {
@@ -91,6 +101,9 @@ func (s *Server) handleStreamChat(w http.ResponseWriter, orb *orchestrator.Orche
storeContent = string(storeJSON)
}
s.convStore.Add("assistant", storeContent)
+ s.convStore.AddRealTokens(engine.TotalTokens)
+
+ s.consumption.Record(engine.ProviderName(), engine.TotalTokens)
sseWriter.Write(map[string]interface{}{"done": "true"})
}
@@ -107,6 +120,10 @@ func (s *Server) handleNonStreamChat(w http.ResponseWriter, orb *orchestrator.Or
}
s.convStore.Add("assistant", finalContent)
+ s.convStore.AddRealTokens(engine.TotalTokens)
+
+ s.consumption.Record(engine.ProviderName(), engine.TotalTokens)
+
writeJSON(w, map[string]string{"content": finalContent})
}
diff --git a/internal/api/handlers_common.go b/internal/api/handlers_common.go
index 50ae5f8..75d0560 100644
--- a/internal/api/handlers_common.go
+++ b/internal/api/handlers_common.go
@@ -5,7 +5,21 @@ import (
"net/http"
)
-const summarizePrompt = `Résume la conversation suivante de manière concise et structurée. Garde les points clés, les décisions prises, le contexte technique important. Le résumé doit permettre de continuer la conversation sans perte de contexte. Réponds uniquement avec le résumé, sans meta-commentaire.`
+const summarizePrompt = `Résume cette conversation de manière ultra-concise et structurée.
+
+CONSERVE :
+- Les décisions techniques prises et leur rationale
+- Les configurations modifiées (noms exacts, valeurs)
+- Les fichiers/chemins manipulés
+- Les erreurs rencontrées et leurs résolutions
+- Le contexte nécessaire pour continuer
+
+ÉLIMINE :
+- Les échanges de politesse
+- Les tentatives infructueuses (sauf si la solution n'a pas été trouvée)
+- Les sorties d'outils brutes (garde seulement les conclusions)
+
+FORMAT : Markdown structuré avec sections. Max 500 mots. Pas de méta-commentaire.`
func writeJSON(w http.ResponseWriter, data interface{}) {
json.NewEncoder(w).Encode(data)
diff --git a/internal/api/handlers_info.go b/internal/api/handlers_info.go
index 921481a..33850c4 100644
--- a/internal/api/handlers_info.go
+++ b/internal/api/handlers_info.go
@@ -534,6 +534,39 @@ func (s *Server) handleProvidersQuota(w http.ResponseWriter, r *http.Request) {
q.Healthy = p.APIKey != ""
if p.APIKey == "" {
q.Error = "no API key"
+ results = append(results, q)
+ continue
+ }
+ mimoBase := p.BaseURL
+ if mimoBase == "" {
+ mimoBase = "https://token-plan-ams.xiaomimimo.com/v1"
+ }
+ req, _ := http.NewRequest("GET", strings.TrimRight(mimoBase, "/")+"/models", nil)
+ req.Header.Set("Authorization", "Bearer "+p.APIKey)
+ resp, err := client.Do(req)
+ if err != nil {
+ q.Error = err.Error()
+ } else {
+ body, _ := io.ReadAll(resp.Body)
+ resp.Body.Close()
+ var data map[string]interface{}
+ if json.Unmarshal(body, &data) == nil {
+ if modelList, ok := data["data"].([]interface{}); ok {
+ models := make([]map[string]interface{}, 0)
+ for _, m := range modelList {
+ if mm, ok := m.(map[string]interface{}); ok {
+ id, _ := mm["id"].(string)
+ if id != "" {
+ models = append(models, map[string]interface{}{
+ "model": id,
+ })
+ }
+ }
+ }
+ q.Data = map[string]interface{}{"models": models, "available": len(models)}
+ q.Healthy = true
+ }
+ }
}
case "claude", "anthropic":
// Claude Code n'a pas d'API externe, vérifier l'installation
@@ -551,6 +584,15 @@ func (s *Server) handleProvidersQuota(w http.ResponseWriter, r *http.Request) {
writeJSON(w, map[string]interface{}{"providers": results})
}
+func (s *Server) handleProvidersConsumption(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "GET" {
+ writeError(w, "GET only", http.StatusMethodNotAllowed)
+ return
+ }
+ data := s.consumption.GetAll()
+ writeJSON(w, map[string]interface{}{"providers": data})
+}
+
func (s *Server) handleRecentCommands(w http.ResponseWriter, r *http.Request) {
home, _ := os.UserHomeDir()
type cmdEntry struct {
diff --git a/internal/api/handlers_shell_chat.go b/internal/api/handlers_shell_chat.go
index 7eebaab..d1cd38a 100644
--- a/internal/api/handlers_shell_chat.go
+++ b/internal/api/handlers_shell_chat.go
@@ -9,6 +9,7 @@ import (
"os/exec"
"runtime"
"strings"
+ "time"
"github.com/muyue/muyue/internal/agent"
"github.com/muyue/muyue/internal/orchestrator"
@@ -62,35 +63,36 @@ func (s *Server) handleShellChat(w http.ResponseWriter, r *http.Request) {
}
}
-func (s *Server) buildShellSystemPrompt(_ ShellChatRequest) string {
+func (s *Server) buildShellSystemPrompt(req ShellChatRequest) string {
var sb strings.Builder
- sb.WriteString(`Tu es l'Analyste Système de Muyue. Tu es un expert en administration système et développement.
-Tu aides l'utilisateur à comprendre son système, diagnostiquer des problèmes, et optimiser son environnement.
-
-OUTILS DISPONIBLES:
-- terminal: Exécute des commandes shell sur le système local et retourne le résultat
-
-RÈGLES:
-- Utilise l'outil terminal pour exécuter des commandes quand c'est nécessaire
-- Analyse les résultats et explique-les clairement
-- Formate tes réponses en markdown avec des blocs de code quand approprié
-- Sois concis et technique
-- Quand tu proposes des commandes alternatives, utilise des blocs de code markdown
-
-`)
+ sb.WriteString(shellSystemPromptBase)
analysis := LoadSystemAnalysis()
if analysis != "" {
- sb.WriteString("=== ANALYSE SYSTÈME ACTUELLE ===\n")
+ sb.WriteString("\n")
sb.WriteString(analysis)
- sb.WriteString("\n=== FIN DE L'ANALYSE ===\n\n")
+ sb.WriteString("\n\n\n")
}
sb.WriteString(fmt.Sprintf("OS: %s/%s\n", runtime.GOOS, runtime.GOARCH))
if hostname, err := os.Hostname(); err == nil {
sb.WriteString("Hostname: " + hostname + "\n")
}
+ if user := os.Getenv("USER"); user != "" {
+ sb.WriteString("User: " + user + "\n")
+ }
+
+ isRoot := os.Geteuid() == 0
+ sb.WriteString(fmt.Sprintf("Root: %t\n", isRoot))
+ if isRoot {
+ sb.WriteString("⚠️ Session en root — toutes les commandes ont les privilèges administrateur.\n")
+ } else {
+ sb.WriteString("⚠️ Session utilisateur standard — les commandes sudo/doas nécessitent une autorisation. N'utilise PAS sudo ou doas sans demander.\n")
+ }
+
+ now := time.Now()
+ sb.WriteString(fmt.Sprintf("Date: %s\nHeure: %s\n", now.Format("02/01/2006"), now.Format("15:04:05")))
return sb.String()
}
@@ -131,6 +133,9 @@ func (s *Server) handleShellChatStream(w http.ResponseWriter, orb *orchestrator.
storeContent = string(storeJSON)
}
s.shellConvStore.Add("assistant", storeContent)
+ s.shellConvStore.AddRealTokens(engine.TotalTokens)
+
+ s.consumption.Record(engine.ProviderName(), engine.TotalTokens)
sseWriter.Write(map[string]interface{}{
"done": "true",
@@ -150,6 +155,10 @@ func (s *Server) handleShellChatNonStream(w http.ResponseWriter, orb *orchestrat
}
s.shellConvStore.Add("assistant", finalContent)
+ s.shellConvStore.AddRealTokens(engine.TotalTokens)
+
+ s.consumption.Record(engine.ProviderName(), engine.TotalTokens)
+
writeJSON(w, map[string]interface{}{
"content": finalContent,
"tokens": s.shellConvStore.ApproxTokens(),
@@ -289,15 +298,33 @@ func (s *Server) handleShellAnalyze(w http.ResponseWriter, r *http.Request) {
}
orb.SetSystemPrompt(agent.StudioSystemPrompt())
- analysisPrompt := `Tu es un expert en administration système. Analyse les informations suivantes sur le système de l'utilisateur.
-Génère un rapport d'analyse concis et structuré en markdown qui inclut:
-1. Un résumé de l'état du système
-2. Les points d'attention (performance, sécurité, configuration)
-3. Des recommandations spécifiques d'optimisation
-4. Les outils manquants qui pourraient être utiles
-5. L'état du réseau et des connexions
+ analysisPrompt := `Tu es un expert en administration système. Analyse les informations suivantes et génère un rapport structuré en markdown.
-Sois concret et technique. Le rapport sera utilisé comme contexte pour un assistant terminal.
+STRUCTURE REQUISE :
+
+## État du système
+- Résumé en 2-3 phrases de l'état général (OK/Attention/Critique)
+
+## Points d'attention
+Liste les problèmes détectés par priorité :
+- **CRITIQUE** : problèmes de sécurité, espace disque < 10%, mémoire < 10%
+- **ATTENTION** : CPU élevé, services en échec, config non-optimale
+- **INFO** : améliorations possibles, mises à jour disponibles
+
+## Recommandations
+Pour chaque point d'attention, donne UNE commande ou action corrective concrète.
+
+## Outils manquants
+Liste les outils utiles non installés avec la commande d'installation.
+
+## Réseau
+- Interfaces actives, ports en écoute, connectivité
+
+RÈGLES :
+- Pas de blabla générique — sois spécifique à CE système
+- Inclus les valeurs numériques réelles (%, Go, MHz)
+- Max 1500 mots
+- Le rapport sert de contexte persistant pour un assistant terminal
` + sysInfo.String()
diff --git a/internal/api/server.go b/internal/api/server.go
index e07e75c..1b37478 100644
--- a/internal/api/server.go
+++ b/internal/api/server.go
@@ -18,6 +18,7 @@ type Server struct {
mux *http.ServeMux
convStore *ConversationStore
shellConvStore *ShellConvStore
+ consumption *consumptionStore
agentRegistry *agent.Registry
agentToolsJSON json.RawMessage
shellAgentRegistry *agent.Registry
@@ -50,6 +51,7 @@ func NewServer(cfg *config.MuyueConfig) *Server {
s.scanResult = scanner.ScanSystem()
s.convStore = NewConversationStore()
s.shellConvStore = NewShellConvStore()
+ s.consumption = newConsumptionStore()
s.agentRegistry = agent.DefaultRegistry()
tools := s.agentRegistry.OpenAITools()
toolsJSON, _ := json.Marshal(tools)
@@ -131,6 +133,7 @@ func (s *Server) routes() {
s.mux.HandleFunc("/api/skills/import", s.handleSkillImport)
s.mux.HandleFunc("/api/dashboard/status", s.handleDashboardStatus)
s.mux.HandleFunc("/api/providers/quota", s.handleProvidersQuota)
+ s.mux.HandleFunc("/api/providers/consumption", s.handleProvidersConsumption)
s.mux.HandleFunc("/api/recent-commands", s.handleRecentCommands)
s.mux.HandleFunc("/api/running-processes", s.handleRunningProcesses)
s.mux.HandleFunc("/api/system/metrics", s.handleSystemMetrics)
diff --git a/internal/api/shell_conversation.go b/internal/api/shell_conversation.go
index 657cfee..141ca0a 100644
--- a/internal/api/shell_conversation.go
+++ b/internal/api/shell_conversation.go
@@ -14,6 +14,63 @@ import (
const shellMaxTokens = 100000
const shellCharsPerToken = 4
+const shellSystemPromptBase = `Tu es l'**Analyste Système** de Muyue. Tu es un expert en administration système, DevOps et développement.
+
+
+1. **AGIS, ne décris pas** — Utilise l'outil terminal pour exécuter, ne te contente pas de proposer des commandes.
+2. **SOIS AUTONOME** — Cherche les infos manquantes via des commandes avant de demander à l'utilisateur. Essaie plusieurs approches avant de bloquer.
+3. **SOIS CONCIS** — Max 4 lignes par défaut. Pas de préambule. Réponse directe et technique.
+4. **GÈRE LES ERREURS** — Si une commande échoue, lis l'erreur, comprends la cause, essaie une approche alternative. 2-3 tentatives avant de rapporter.
+5. **SÉCURITÉ** — Ne révèle jamais de credentials. Demande confirmation avant les commandes destructrices (rm -rf, format, etc.).
+6. **LANGUE** — Réponds dans la même langue que l'utilisateur.
+
+
+
+Outil disponible : **terminal** — Exécute des commandes shell sur le système local.
+
+Stratégies :
+- **Diagnostique** — Enchaîne les commandes de diagnostic (ps, df, free, top, journalctl, dmesg, netstat, ss, etc.)
+- **Parallélisme** — Combine les commandes avec && ou ; quand elles sont indépendantes
+- **Filtrage** — Utilise grep, awk, sort, head pour extraire l'essentiel des sorties volumineuses
+- **Non-interactif** — Préfère les commandes non-interactives (apt install -y, non pas apt install)
+- **Troncature** — Si le résultat dépasse 2000 caractères, résume les points clés au lieu de tout afficher
+
+
+
+- Décide par toi-même : exécute des commandes pour comprendre l'état du système
+- Ne demande confirmation que pour les actions destructrices
+- Si tu ne connais pas la commande exacte, exécute la commande avec --help pour la trouver
+- Si bloqué : documente ce que tu as essayé, pourquoi, et l'action minimale requise
+- Ne t'arrête jamais pour une tâche complexe — découpe en étapes et exécute-les
+
+
+
+1. Lis le message d'erreur complet (stderr + stdout)
+2. Identifie la cause racine (permissions, paquet manquant, config, service)
+3. Essaie : vérifier le service, vérifier les logs, chercher le paquet, tester la connexion
+4. Propose une solution concrète, pas générique
+
+
+
+- **Commandes** : blocs markdown avec le langage (bash, sh, etc.)
+- **Résultats** : résume les métriques clés, pas de dump complet
+- **Erreurs** : cause + solution en 1-2 lignes
+- **Succès** : confirmation en 1 ligne
+- **Analyses** : markdown structuré avec sections si nécessaire
+
+
+
+Tu peux utiliser des diagrammes Mermaid pour visualiser :
+- Architecture système (graph TD/LR)
+- Flux réseau (sequenceDiagram)
+- Processus (flowchart)
+- Timeline (gantt)
+
+Utilise un bloc de code avec le langage mermaid quand ça clarifie l'explication. Pas pour du texte simple.
+
+
+`
+
type ShellMessage struct {
ID string `json:"id"`
Role string `json:"role"`
@@ -22,9 +79,10 @@ type ShellMessage struct {
}
type ShellConvStore struct {
- mu sync.RWMutex
- path string
- msgs []ShellMessage
+ mu sync.RWMutex
+ path string
+ msgs []ShellMessage
+ realTokens int
}
func NewShellConvStore() *ShellConvStore {
@@ -82,19 +140,37 @@ func (s *ShellConvStore) Clear() {
s.mu.Lock()
defer s.mu.Unlock()
s.msgs = []ShellMessage{}
+ s.realTokens = 0
s.save()
}
func (s *ShellConvStore) ApproxTokens() int {
+ if s.realTokens > 0 {
+ return s.realTokens
+ }
s.mu.RLock()
defer s.mu.RUnlock()
total := 0
for _, m := range s.msgs {
total += utf8.RuneCountInString(m.Content) / shellCharsPerToken
}
+ total += utf8.RuneCountInString(shellSystemPromptBase) / shellCharsPerToken
+ if analysis := LoadSystemAnalysis(); analysis != "" {
+ total += utf8.RuneCountInString(analysis) / shellCharsPerToken
+ }
return total
}
+// AddRealTokens accumulates actual token counts from the API response.
+func (s *ShellConvStore) AddRealTokens(tokens int) {
+ if tokens <= 0 {
+ return
+ }
+ s.mu.Lock()
+ s.realTokens += tokens
+ s.mu.Unlock()
+}
+
func (s *ShellConvStore) AtLimit() bool {
return s.ApproxTokens() >= shellMaxTokens
}
diff --git a/internal/config/config.go b/internal/config/config.go
index d0e143e..b2941d9 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -289,7 +289,7 @@ func Default() *MuyueConfig {
},
{
Name: "mimo",
- Model: "MiMo-V2.5-Pro",
+ Model: "mimo-v2.5-pro",
BaseURL: "https://token-plan-ams.xiaomimimo.com/v1",
Active: false,
},
diff --git a/internal/workflow/planner.go b/internal/workflow/planner.go
index ccec148..3beeba0 100644
--- a/internal/workflow/planner.go
+++ b/internal/workflow/planner.go
@@ -159,14 +159,18 @@ func parsePlanResponse(content string) ([]Step, error) {
return steps, nil
}
-const plannerSystemPrompt = `Tu es un assistant de planification de workflows pour Muyue. Tu génères des plans d'exécution sous forme de JSON. Chaque plan est une séquence d'étapes (steps) représentant des appels d'outils.
+const plannerSystemPrompt = `Tu es un planificateur de workflows pour Muyue. Tu génères des plans d'exécution sous forme de tableaux JSON.
-Pour générer un plan:
-1. Comprends l'objectif de l'utilisateur
-2. Identifie les outils nécessaires
-3. Décompose en étapes logiques
-4. Spécifie les paramètres de chaque outil
+RÈGLES :
+1. Analyse l'objectif → identifie les outils → décompose en étapes
+2. Chaque étape : {"name": string, "tool": string, "args": object}
+3. Max 10 étapes par plan
+4. Ordonne par dépendances (les lectures avant les écritures)
+5. Préfère les commandes non-interactives
+6. Utilise crush_run pour les tâches complexes multi-fichiers
-Réponds toujours en JSON valide, sans texte additionnel.`
+Outils : terminal, crush_run, read_file, list_files, search_files, grep_content, get_config, set_provider, manage_ssh, web_fetch
-var _ = plannerSystemPrompt
\ No newline at end of file
+Réponds UNIQUEMENT en JSON valide, sans texte avant/après.`
+
+const _ = plannerSystemPrompt
\ No newline at end of file
diff --git a/web/package-lock.json b/web/package-lock.json
index cc97aa7..22b7341 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -14,6 +14,7 @@
"@xterm/addon-webgl": "^0.20.0-beta.202",
"@xterm/xterm": "^6.1.0-beta.203",
"lucide-react": "^1.8.0",
+ "mermaid": "^11.14.0",
"react": "^19.2.5",
"react-dom": "^19.2.5"
},
@@ -22,6 +23,62 @@
"vite": "^8.0.9"
}
},
+ "node_modules/@antfu/install-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
+ "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "package-manager-detector": "^1.3.0",
+ "tinyexec": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@braintree/sanitize-url": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz",
+ "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==",
+ "license": "MIT"
+ },
+ "node_modules/@chevrotain/cst-dts-gen": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-12.0.0.tgz",
+ "integrity": "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/gast": "12.0.0",
+ "@chevrotain/types": "12.0.0"
+ }
+ },
+ "node_modules/@chevrotain/gast": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-12.0.0.tgz",
+ "integrity": "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/types": "12.0.0"
+ }
+ },
+ "node_modules/@chevrotain/regexp-to-ast": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-12.0.0.tgz",
+ "integrity": "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/types": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-12.0.0.tgz",
+ "integrity": "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/utils": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-12.0.0.tgz",
+ "integrity": "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==",
+ "license": "Apache-2.0"
+ },
"node_modules/@emnapi/core": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
@@ -56,6 +113,32 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@iconify/types": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
+ "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
+ "license": "MIT"
+ },
+ "node_modules/@iconify/utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.1.tgz",
+ "integrity": "sha512-MwzoDtw9rO1x+qfgLTV/IVXsHDBqeYZoMIQC8SfxfYSlaSUG+oWiAcoiB1yajAda6mqblm4/1/w2E8tRu7a7Tw==",
+ "license": "MIT",
+ "dependencies": {
+ "@antfu/install-pkg": "^1.1.0",
+ "@iconify/types": "^2.0.0",
+ "mlly": "^1.8.2"
+ }
+ },
+ "node_modules/@mermaid-js/parser": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.0.tgz",
+ "integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==",
+ "license": "MIT",
+ "dependencies": {
+ "langium": "^4.0.0"
+ }
+ },
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
@@ -378,6 +461,282 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/d3": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/d3-axis": "*",
+ "@types/d3-brush": "*",
+ "@types/d3-chord": "*",
+ "@types/d3-color": "*",
+ "@types/d3-contour": "*",
+ "@types/d3-delaunay": "*",
+ "@types/d3-dispatch": "*",
+ "@types/d3-drag": "*",
+ "@types/d3-dsv": "*",
+ "@types/d3-ease": "*",
+ "@types/d3-fetch": "*",
+ "@types/d3-force": "*",
+ "@types/d3-format": "*",
+ "@types/d3-geo": "*",
+ "@types/d3-hierarchy": "*",
+ "@types/d3-interpolate": "*",
+ "@types/d3-path": "*",
+ "@types/d3-polygon": "*",
+ "@types/d3-quadtree": "*",
+ "@types/d3-random": "*",
+ "@types/d3-scale": "*",
+ "@types/d3-scale-chromatic": "*",
+ "@types/d3-selection": "*",
+ "@types/d3-shape": "*",
+ "@types/d3-time": "*",
+ "@types/d3-time-format": "*",
+ "@types/d3-timer": "*",
+ "@types/d3-transition": "*",
+ "@types/d3-zoom": "*"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-axis": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-brush": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-chord": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-contour": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-dispatch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
+ "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-dsv": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-fetch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-dsv": "*"
+ }
+ },
+ "node_modules/@types/d3-force": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-format": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
+ "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-geo": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-hierarchy": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-polygon": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-quadtree": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-random": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-time-format": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
+ "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@upsetjs/venn.js": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz",
+ "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "d3-selection": "^3.0.0",
+ "d3-transition": "^3.0.1"
+ }
+ },
"node_modules/@vitejs/plugin-react": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
@@ -461,6 +820,584 @@
"addons/*"
]
},
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/chevrotain": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-12.0.0.tgz",
+ "integrity": "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/cst-dts-gen": "12.0.0",
+ "@chevrotain/gast": "12.0.0",
+ "@chevrotain/regexp-to-ast": "12.0.0",
+ "@chevrotain/types": "12.0.0",
+ "@chevrotain/utils": "12.0.0"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/chevrotain-allstar": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.4.1.tgz",
+ "integrity": "sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash-es": "^4.17.21"
+ },
+ "peerDependencies": {
+ "chevrotain": "^12.0.0"
+ }
+ },
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "license": "MIT"
+ },
+ "node_modules/cose-base": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
+ "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
+ "license": "MIT",
+ "dependencies": {
+ "layout-base": "^1.0.0"
+ }
+ },
+ "node_modules/cytoscape": {
+ "version": "3.33.2",
+ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.2.tgz",
+ "integrity": "sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/cytoscape-cose-bilkent": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
+ "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cose-base": "^1.0.0"
+ },
+ "peerDependencies": {
+ "cytoscape": "^3.2.0"
+ }
+ },
+ "node_modules/cytoscape-fcose": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
+ "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cose-base": "^2.2.0"
+ },
+ "peerDependencies": {
+ "cytoscape": "^3.2.0"
+ }
+ },
+ "node_modules/cytoscape-fcose/node_modules/cose-base": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
+ "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
+ "license": "MIT",
+ "dependencies": {
+ "layout-base": "^2.0.0"
+ }
+ },
+ "node_modules/cytoscape-fcose/node_modules/layout-base": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
+ "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==",
+ "license": "MIT"
+ },
+ "node_modules/d3": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
+ "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "3",
+ "d3-axis": "3",
+ "d3-brush": "3",
+ "d3-chord": "3",
+ "d3-color": "3",
+ "d3-contour": "4",
+ "d3-delaunay": "6",
+ "d3-dispatch": "3",
+ "d3-drag": "3",
+ "d3-dsv": "3",
+ "d3-ease": "3",
+ "d3-fetch": "3",
+ "d3-force": "3",
+ "d3-format": "3",
+ "d3-geo": "3",
+ "d3-hierarchy": "3",
+ "d3-interpolate": "3",
+ "d3-path": "3",
+ "d3-polygon": "3",
+ "d3-quadtree": "3",
+ "d3-random": "3",
+ "d3-scale": "4",
+ "d3-scale-chromatic": "3",
+ "d3-selection": "3",
+ "d3-shape": "3",
+ "d3-time": "3",
+ "d3-time-format": "4",
+ "d3-timer": "3",
+ "d3-transition": "3",
+ "d3-zoom": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-axis": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
+ "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-brush": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
+ "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "3",
+ "d3-transition": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-chord": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
+ "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-contour": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
+ "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+ "license": "ISC",
+ "dependencies": {
+ "delaunator": "5"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dispatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-drag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dsv": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
+ "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+ "license": "ISC",
+ "dependencies": {
+ "commander": "7",
+ "iconv-lite": "0.6",
+ "rw": "1"
+ },
+ "bin": {
+ "csv2json": "bin/dsv2json.js",
+ "csv2tsv": "bin/dsv2dsv.js",
+ "dsv2dsv": "bin/dsv2dsv.js",
+ "dsv2json": "bin/dsv2json.js",
+ "json2csv": "bin/json2dsv.js",
+ "json2dsv": "bin/json2dsv.js",
+ "json2tsv": "bin/json2dsv.js",
+ "tsv2csv": "bin/dsv2dsv.js",
+ "tsv2json": "bin/dsv2json.js"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-fetch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
+ "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dsv": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-force": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
+ "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-quadtree": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-geo": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
+ "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.5.0 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-hierarchy": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+ "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-polygon": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
+ "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-quadtree": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
+ "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-random": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
+ "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-sankey": {
+ "version": "0.12.3",
+ "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
+ "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "d3-array": "1 - 2",
+ "d3-shape": "^1.2.0"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/d3-path": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
+ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/d3-sankey/node_modules/d3-shape": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
+ "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "d3-path": "1"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
+ "license": "ISC"
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-interpolate": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-selection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-transition": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-dispatch": "1 - 3",
+ "d3-ease": "1 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "d3-selection": "2 - 3"
+ }
+ },
+ "node_modules/d3-zoom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "2 - 3",
+ "d3-transition": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dagre-d3-es": {
+ "version": "7.0.14",
+ "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz",
+ "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==",
+ "license": "MIT",
+ "dependencies": {
+ "d3": "^7.9.0",
+ "lodash-es": "^4.17.21"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.20",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
+ "license": "MIT"
+ },
+ "node_modules/delaunator": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
+ "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==",
+ "license": "ISC",
+ "dependencies": {
+ "robust-predicates": "^3.0.2"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -471,6 +1408,15 @@
"node": ">=8"
}
},
+ "node_modules/dompurify": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.1.tgz",
+ "integrity": "sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -504,6 +1450,87 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/hachure-fill": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
+ "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==",
+ "license": "MIT"
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/katex": {
+ "version": "0.16.45",
+ "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.45.tgz",
+ "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==",
+ "funding": [
+ "https://opencollective.com/katex",
+ "https://github.com/sponsors/katex"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^8.3.0"
+ },
+ "bin": {
+ "katex": "cli.js"
+ }
+ },
+ "node_modules/katex/node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/khroma": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
+ "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
+ },
+ "node_modules/langium": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.2.tgz",
+ "integrity": "sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@chevrotain/regexp-to-ast": "~12.0.0",
+ "chevrotain": "~12.0.0",
+ "chevrotain-allstar": "~0.4.1",
+ "vscode-languageserver": "~9.0.1",
+ "vscode-languageserver-textdocument": "~1.0.11",
+ "vscode-uri": "~3.1.0"
+ },
+ "engines": {
+ "node": ">=20.10.0",
+ "npm": ">=10.2.3"
+ }
+ },
+ "node_modules/layout-base": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
+ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
+ "license": "MIT"
+ },
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
@@ -777,6 +1804,12 @@
"url": "https://opencollective.com/parcel"
}
},
+ "node_modules/lodash-es": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
+ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
+ "license": "MIT"
+ },
"node_modules/lucide-react": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.8.0.tgz",
@@ -786,6 +1819,59 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/marked": {
+ "version": "16.4.2",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz",
+ "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/mermaid": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz",
+ "integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@braintree/sanitize-url": "^7.1.1",
+ "@iconify/utils": "^3.0.2",
+ "@mermaid-js/parser": "^1.1.0",
+ "@types/d3": "^7.4.3",
+ "@upsetjs/venn.js": "^2.0.0",
+ "cytoscape": "^3.33.1",
+ "cytoscape-cose-bilkent": "^4.1.0",
+ "cytoscape-fcose": "^2.2.0",
+ "d3": "^7.9.0",
+ "d3-sankey": "^0.12.3",
+ "dagre-d3-es": "7.0.14",
+ "dayjs": "^1.11.19",
+ "dompurify": "^3.3.1",
+ "katex": "^0.16.25",
+ "khroma": "^2.1.0",
+ "lodash-es": "^4.17.23",
+ "marked": "^16.3.0",
+ "roughjs": "^4.6.6",
+ "stylis": "^4.3.6",
+ "ts-dedent": "^2.2.0",
+ "uuid": "^11.1.0"
+ }
+ },
+ "node_modules/mlly": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
+ "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "pathe": "^2.0.3",
+ "pkg-types": "^1.3.1",
+ "ufo": "^1.6.3"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -805,6 +1891,24 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/package-manager-detector": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz",
+ "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
+ "license": "MIT"
+ },
+ "node_modules/path-data-parser": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz",
+ "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==",
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -825,6 +1929,33 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/points-on-curve": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz",
+ "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==",
+ "license": "MIT"
+ },
+ "node_modules/points-on-path": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz",
+ "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==",
+ "license": "MIT",
+ "dependencies": {
+ "path-data-parser": "0.1.0",
+ "points-on-curve": "0.2.0"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
@@ -875,6 +2006,12 @@
"react": "^19.2.5"
}
},
+ "node_modules/robust-predicates": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz",
+ "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
+ "license": "Unlicense"
+ },
"node_modules/rolldown": {
"version": "1.0.0-rc.16",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.16.tgz",
@@ -916,6 +2053,30 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/roughjs": {
+ "version": "4.6.6",
+ "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz",
+ "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "hachure-fill": "^0.5.2",
+ "path-data-parser": "^0.1.0",
+ "points-on-curve": "^0.2.0",
+ "points-on-path": "^0.2.1"
+ }
+ },
+ "node_modules/rw": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -932,6 +2093,21 @@
"node": ">=0.10.0"
}
},
+ "node_modules/stylis": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
+ "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz",
+ "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
@@ -949,6 +2125,15 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/ts-dedent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
+ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.10"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -957,6 +2142,25 @@
"license": "0BSD",
"optional": true
},
+ "node_modules/ufo": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
+ "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
+ "license": "MIT"
+ },
+ "node_modules/uuid": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
+ },
"node_modules/vite": {
"version": "8.0.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz",
@@ -1034,6 +2238,55 @@
"optional": true
}
}
+ },
+ "node_modules/vscode-jsonrpc": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
+ "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/vscode-languageserver": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
+ "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-languageserver-protocol": "3.17.5"
+ },
+ "bin": {
+ "installServerIntoExtension": "bin/installServerIntoExtension"
+ }
+ },
+ "node_modules/vscode-languageserver-protocol": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
+ "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-jsonrpc": "8.2.0",
+ "vscode-languageserver-types": "3.17.5"
+ }
+ },
+ "node_modules/vscode-languageserver-textdocument": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
+ "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
+ "license": "MIT"
+ },
+ "node_modules/vscode-languageserver-types": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
+ "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
+ "license": "MIT"
+ },
+ "node_modules/vscode-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
+ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
+ "license": "MIT"
}
}
}
diff --git a/web/package.json b/web/package.json
index 5f53677..73add98 100644
--- a/web/package.json
+++ b/web/package.json
@@ -16,6 +16,7 @@
"@xterm/addon-webgl": "^0.20.0-beta.202",
"@xterm/xterm": "^6.1.0-beta.203",
"lucide-react": "^1.8.0",
+ "mermaid": "^11.14.0",
"react": "^19.2.5",
"react-dom": "^19.2.5"
},
diff --git a/web/src/api/client.js b/web/src/api/client.js
index 5148a10..0c1defa 100644
--- a/web/src/api/client.js
+++ b/web/src/api/client.js
@@ -38,6 +38,7 @@ const api = {
importSkill: (path) => request('/skills/import', { method: 'POST', body: JSON.stringify({ import_path: path }) }),
getDashboardStatus: () => request('/dashboard/status'),
getProvidersQuota: () => request('/providers/quota'),
+ getProvidersConsumption: () => request('/providers/consumption'),
getRecentCommands: () => request('/recent-commands'),
getRunningProcesses: () => request('/running-processes'),
getSystemMetrics: () => request('/system/metrics'),
diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx
index 563a7ba..6d707b4 100644
--- a/web/src/components/App.jsx
+++ b/web/src/components/App.jsx
@@ -94,10 +94,8 @@ export default function App() {
shell: [
{ keys: `${layout.keys.ctrl}+${layout.keys.shift}+C`, desc: t('statusbar.copy') },
{ keys: `${layout.keys.ctrl}+${layout.keys.shift}+V`, desc: t('statusbar.paste') },
- { keys: `${layout.keys.ctrl}+F`, desc: t('statusbar.search') },
- { keys: `${layout.keys.ctrl}+/Ctrl−`, desc: t('statusbar.zoom') },
- { keys: `Alt+1-7`, desc: t('statusbar.switchTab') },
- { keys: `${layout.keys.shift}+Tab`, desc: t('statusbar.nextTab') },
+ { keys: `${layout.keys.ctrl}+${layout.keys.shift}+F`, desc: t('statusbar.search') },
+ { keys: `${layout.keys.ctrl}++/${layout.keys.ctrl}+−`, desc: t('statusbar.zoom') },
{ keys: layout.keys.enter, desc: t('statusbar.runCommand') },
{ keys: `${layout.keys.up}/${layout.keys.down}`, desc: t('statusbar.commandHistory') },
],
diff --git a/web/src/components/Dashboard.jsx b/web/src/components/Dashboard.jsx
index 44111b5..bbadee7 100644
--- a/web/src/components/Dashboard.jsx
+++ b/web/src/components/Dashboard.jsx
@@ -6,6 +6,12 @@ const MAX_POINTS = 30
const POLL_INTERVAL = 5000
const MAX_IDLE_POLLS = 3
+function formatTokens(n) {
+ if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M'
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'K'
+ return String(n)
+}
+
function MiniGraph({ data, max, color, label, unit }) {
if (!data || data.length < 2) return
collecting...
const m = max || Math.max(...data, 1)
@@ -37,9 +43,28 @@ function MiniGraph({ data, max, color, label, unit }) {
)
}
+function BarChart({ data, max, color }) {
+ if (!data || data.length === 0) return null
+ const barW = 100 / 7
+ const m = max || Math.max(...data.map(d => d.tokens), 1)
+ return (
+
+ )
+}
+
export default function Dashboard({ api, refreshRef }) {
const { t } = useI18n()
const [quota, setQuota] = useState(null)
+ const [consumption, setConsumption] = useState(null)
const [recentCmds, setRecentCmds] = useState([])
const [processes, setProcesses] = useState([])
const [metrics, setMetrics] = useState(null)
@@ -51,13 +76,15 @@ export default function Dashboard({ api, refreshRef }) {
const loadData = useCallback(async () => {
try {
- const [quotaData, cmdData, procData, metricsData] = await Promise.all([
+ const [quotaData, consumData, cmdData, procData, metricsData] = await Promise.all([
api.getProvidersQuota().catch(() => null),
+ api.getProvidersConsumption().catch(() => null),
api.getRecentCommands().catch(() => ({ commands: [] })),
api.getRunningProcesses().catch(() => ({ processes: [] })),
api.getSystemMetrics().catch(() => null),
])
setQuota(quotaData?.providers || [])
+ setConsumption(consumData?.providers || {})
setRecentCmds(cmdData.commands || [])
setProcesses(procData.processes || [])
if (metricsData) {
@@ -91,7 +118,6 @@ export default function Dashboard({ api, refreshRef }) {
}, [loadData, refreshRef])
const minimax = (quota || []).find(p => p.name === 'minimax')
- const mimo = (quota || []).find(p => p.name === 'mimo')
const EXCLUDE_CMDS = ['ls', 'cd', 'pwd', 'clear', 'exit', 'history', 'cat', 'echo', 'grep', 'export', 'alias', 'unalias', 'set', 'unset', 'source', '.', 'fg', 'bg', 'jobs', 'wait', 'true', 'false', 'yes', 'sleep', 'date', 'whoami', 'id', 'uname', 'hostname', 'uptime', 'df', 'free', 'top', 'htop', 'nano', 'vi', 'vim', 'less', 'more', 'tail', 'head', 'man', 'info', 'which', 'whereis', 'type', 'command', 'hash', 'builtin', 'help']
@@ -135,6 +161,12 @@ export default function Dashboard({ api, refreshRef }) {
})
})()
+ const providerEntries = consumption ? Object.entries(consumption) : []
+ const colors = ['var(--accent)', '#34d399', '#a78bfa', '#f59e0b', '#f472b6']
+ const maxDaily = providerEntries.length > 0
+ ? Math.max(...providerEntries.map(([, p]) => Math.max(...(p.daily || []).map(d => d.tokens), 0)), 1)
+ : 1
+
return (
{/* CPU */}
@@ -165,43 +197,36 @@ export default function Dashboard({ api, refreshRef }) {
- {/* API Quota */}
+ {/* Consommation */}
- API Quota
+ Consommation
+ 7j
-
- {minimax && minimax.data?.models?.map((m, i) => (
-
-
{String(m.model).replace('MiniMax-', '')}
-
-
+
+ {providerEntries.length === 0 && (
+
Aucune donnée
+ )}
+ {providerEntries.map(([name, p], pi) => (
+
+
+
+ {name.toUpperCase()}
+
+
+ {formatTokens(p.total_tokens)} tokens · {p.total_requests} req
+
+
+
+
+ {(p.daily || []).map((d, i) => (
+
+ {d.date.slice(5)} {formatTokens(d.tokens)}
+
+ ))}
-
{m.used}/{m.total}
))}
- {minimax && minimax.data?.models?.length === 0 && (
-
- MiniMax
- {minimax.error || 'no data'}
-
- )}
- {mimo && mimo.data?.models?.map((m, i) => (
-
-
{String(m.model).replace('MiMo-', '')}
-
-
{m.used}/{m.total}
-
- ))}
- {mimo && !mimo.data?.models?.length && (
-
- MiMo
- {mimo.error || (mimo.healthy ? '✓ configured' : 'no key')}
-
- )}
- {!minimax && !mimo &&
No providers}
diff --git a/web/src/components/Shell.jsx b/web/src/components/Shell.jsx
index 2d14767..3b6f346 100644
--- a/web/src/components/Shell.jsx
+++ b/web/src/components/Shell.jsx
@@ -9,10 +9,14 @@ import { ImageAddon } from '@xterm/addon-image'
import { Plus, X, Monitor, Globe, ChevronDown, Pencil, Trash2, Search, Copy, Send, Eye, Bot } from 'lucide-react'
import '@xterm/xterm/css/xterm.css'
import { useI18n } from '../i18n'
+import mermaid from 'mermaid'
+
+mermaid.initialize({ startOnLoad: false, theme: 'dark', securityLevel: 'loose', fontFamily: 'var(--font-mono)' })
const AI_TAB_ID = 0
const MAX_TABS = 7
const SHELL_MAX_TOKENS = 100000
+const SHELL_AI_COMMANDS = ['/clear', '/help', '/model', '/model change']
const TABS_STORAGE_KEY = 'muyue_shell_tabs'
const TERMINAL_BUFFER_KEY = 'muyue_terminal_buffers'
@@ -51,6 +55,15 @@ function formatText(text) {
let html = text
.replace(/&/g, '&').replace(//g, '>')
+ html = html.replace(/^(\|.+\|)\n(\|[\s\-:|]+\|)\n((?:\|.+\|\n?)+)/gm, (match, headerRow, sepRow, bodyRows) => {
+ const headers = headerRow.split('|').filter(c => c.trim() !== '').map(c => `
${c.trim()} | `).join('')
+ const rows = bodyRows.trim().split('\n').map(row => {
+ const cells = row.split('|').filter(c => c.trim() !== '').map(c => `
${c.trim()} | `).join('')
+ return `
${cells}
`
+ }).join('')
+ return `
`
+ })
+
html = html
.replace(/\*\*(.+?)\*\*/g, '
$1')
.replace(/`([^`]+)`/g, '
$1')
@@ -63,8 +76,8 @@ function formatText(text) {
html = html
.replace(/
\s*
/g, '
')
- .replace(/
\s*(
|<\/table>)\s*
/g, '$1')
.replace(/\s+on\w+=["'][^"']*["']/gi, '')
.replace(/javascript:/gi, '')
.replace(/data:/gi, '')
@@ -200,11 +213,13 @@ function getTheme(themeName) {
}
function createTerminal(container, settings = {}) {
+ console.log('[Shell] createTerminal called with settings:', JSON.stringify({ fontSize: settings.fontSize, fontFamily: settings.fontFamily?.slice(0, 30), theme: settings.theme }))
const theme = getTheme(settings.theme || 'system')
+ const actualFontSize = settings.fontSize || 14
const term = new XTerm({
cursorBlink: true,
allowProposedApi: true,
- fontSize: settings.fontSize || 14,
+ fontSize: actualFontSize,
fontFamily: settings.fontFamily || "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', 'Menlo', monospace",
theme,
allowTransparency: false,
@@ -404,12 +419,13 @@ export default function Shell({ api }) {
theme: 'system',
})
+ const [configLoaded, setConfigLoaded] = useState(false)
const [showSearch, setShowSearch] = useState(false)
const [searchText, setSearchText] = useState('')
const searchInputRef = useRef(null)
const searchDecorationsRef = useRef(null)
const [zoomLevel, setZoomLevel] = useState(0)
- const baseFontSizeRef = useRef(12)
+ const baseFontSizeRef = useRef(14)
useEffect(() => { settingsRef.current = terminalSettings }, [terminalSettings])
@@ -450,6 +466,7 @@ export default function Shell({ api }) {
const [analyzing, setAnalyzing] = useState(false)
const [showAnalysis, setShowAnalysis] = useState(false)
const [analysisContent, setAnalysisContent] = useState('')
+ const [sudoModal, setSudoModal] = useState(null)
const aiMessagesRef = useRef(null)
const aiLoadedRef = useRef(false)
const aiLoadingRef = useRef(false)
@@ -473,14 +490,10 @@ export default function Shell({ api }) {
api.getShellChatHistory().then(d => {
if (d.messages && d.messages.length > 0) {
setAiMessages(d.messages)
- } else {
- setAiMessages([{ role: 'assistant', content: t('shell.aiWelcome') || 'Système Analyste prêt. Tapez /help pour les commandes.' }])
}
setAiTokens(d.tokens || 0)
setAiAtLimit(d.at_limit || false)
- }).catch(() => {
- setAiMessages([{ role: 'assistant', content: 'Système Analyste prêt.' }])
- })
+ }).catch(() => {})
}, [])
useEffect(() => {
@@ -494,14 +507,21 @@ export default function Shell({ api }) {
setSystemTerminals(d.system || [])
}).catch(() => {})
api.getConfig().then(d => {
+ console.log('[Shell] config response terminal:', JSON.stringify(d?.terminal))
if (d.terminal) {
- setTerminalSettings({
- fontSize: d.terminal.font_size || 14,
- fontFamily: d.terminal.font_family || "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', 'Menlo', monospace",
- theme: d.terminal.theme || 'system',
- })
+ const fontSize = d.terminal.font_size || 14
+ const fontFamily = d.terminal.font_family || "'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', 'Menlo', monospace"
+ const theme = d.terminal.theme || 'system'
+ console.log('[Shell] setting fontSize to:', fontSize, 'from config')
+ setTerminalSettings({ fontSize, fontFamily, theme })
+ settingsRef.current = { fontSize, fontFamily, theme }
+ baseFontSizeRef.current = fontSize
+ } else {
+ console.log('[Shell] no terminal config in response, using defaults')
}
- }).catch(() => {})
+ setConfigLoaded(true)
+ console.log('[Shell] configLoaded = true, settingsRef:', JSON.stringify(settingsRef.current))
+ }).catch((err) => { console.warn('[Shell] getConfig failed:', err); setConfigLoaded(true) })
}, [])
const initTerminal = useCallback((tabId, tab) => {
@@ -512,6 +532,7 @@ export default function Shell({ api }) {
const s = settingsRef.current
const effectiveFontSize = s.fontSize + zoomLevel * 2
+ console.log(`[Shell] initTerminal tab=${tabId}: settingsRef.fontSize=${s.fontSize}, zoomLevel=${zoomLevel}, effectiveFontSize=${effectiveFontSize}`)
const { term, fitAddon, searchAddon } = createTerminal(container, {
fontSize: effectiveFontSize,
fontFamily: s.fontFamily,
@@ -621,6 +642,7 @@ export default function Shell({ api }) {
}, [])
const initPendingTabs = useCallback(() => {
+ if (!configLoaded) return
for (const tab of tabsRef.current._tabList || []) {
if (!tabsRef.current[tab.id]) {
const container = document.getElementById(`terminal-${tab.id}`)
@@ -641,7 +663,7 @@ export default function Shell({ api }) {
}
}, 150)
})
- }, [initTerminal])
+ }, [initTerminal, configLoaded])
useEffect(() => {
tabsRef.current._tabList = tabs
@@ -689,6 +711,7 @@ export default function Shell({ api }) {
}
if (!tabsRef.current[tab.id]) {
+ console.log(`[Shell] tryInitTab: calling initTerminal for tab ${tab.id}, configLoaded=${configLoaded}`)
initTerminal(tab.id, tab)
}
@@ -707,8 +730,9 @@ export default function Shell({ api }) {
})
}
+ console.log(`[Shell] init effect: tabs=${tabs.length}, configLoaded=${configLoaded}`)
for (const tab of tabs) {
- if (!tabsRef.current[tab.id]) {
+ if (configLoaded && !tabsRef.current[tab.id]) {
tryInitTab(tab, 0)
}
}
@@ -729,7 +753,7 @@ export default function Shell({ api }) {
pending.forEach(clearTimeout)
observer?.disconnect()
}
- }, [tabs, initTerminal, initPendingTabs])
+ }, [tabs, initTerminal, initPendingTabs, configLoaded])
useEffect(() => {
const entry = tabsRef.current[activeTab]
@@ -976,9 +1000,10 @@ export default function Shell({ api }) {
if (entry) entry.term.focus()
}, [])
- const _sendAiMessage = useCallback(async (text, fromEvent = false) => {
- if (!text || !text.trim() || aiLoadingRef.current || aiAtLimit) return
+ const _sendAiMessage = useCallback(async (text, fromEvent = false, isAnalysis = false) => {
+ if (!text || !text.trim() || aiLoadingRef.current) return
const trimmed = text.trim()
+ if (aiAtLimit && !trimmed.startsWith('/')) return
aiLoadingRef.current = true
if (!fromEvent) {
@@ -986,10 +1011,21 @@ export default function Shell({ api }) {
setTimeout(() => focusAiTerminal(), 0)
}
+ const isSlashCommand = (t) => /^\/(clear|help|model(?:\s+\S+)?)$/.test(t)
+
+ if (trimmed.startsWith('/') && !isSlashCommand(trimmed)) {
+ setAiMessages(prev => [...prev,
+ { role: 'user', content: trimmed },
+ { role: 'assistant', content: 'Commande inconnue. Tapez `/help` pour la liste des commandes.' }
+ ])
+ aiLoadingRef.current = false
+ return
+ }
+
if (trimmed === '/clear') {
try {
await api.clearShellChat()
- setAiMessages([{ role: 'assistant', content: t('shell.aiWelcome') || 'Contexte effacé. Prêt.' }])
+ setAiMessages([])
setAiTokens(0)
setAiAtLimit(false)
} catch {}
@@ -1000,15 +1036,50 @@ export default function Shell({ api }) {
if (trimmed === '/help') {
setAiMessages(prev => [...prev,
{ role: 'user', content: trimmed },
- { role: 'assistant', content: 'Commandes disponibles:\n• /clear — Effacer la conversation\n• /help — Afficher l\'aide\n\nJe peux exécuter des commandes via l\'outil terminal. Les blocs de code proposés peuvent aussi être copiés ou envoyés directement au terminal actif.' }
+ { role: 'assistant', content: '## Commandes Terminal\n\n- `/clear` — Effacer la conversation\n- `/help` — Afficher cette aide\n- `/model` — Afficher le provider et modèle actifs\n- `/model change` — Basculer entre les providers disponibles' }
])
aiLoadingRef.current = false
return
}
+ if (trimmed === '/model' || trimmed === '/model change') {
+ if (trimmed === '/model change') {
+ api.getProviders().then(data => {
+ const providers = data.providers || []
+ const minimax = providers.find(p => p.name.toUpperCase() === 'MINIMAX')
+ const mimo = providers.find(p => p.name.toUpperCase() === 'MIMO')
+ if (!minimax || !mimo) {
+ setAiMessages(prev => [...prev, { role: 'assistant', content: 'MiniMax et MiMo doivent être configurés pour utiliser `/model change`.' }])
+ return
+ }
+ const active = providers.find(p => p.active)
+ const activeName = active ? active.name.toUpperCase() : ''
+ const switchTo = activeName === 'MINIMAX' ? 'MIMO' : 'MINIMAX'
+ const target = switchTo === 'MINIMAX' ? minimax : mimo
+ api.saveProvider({ name: target.name, active: true }).then(() => {
+ setAiMessages(prev => [...prev, { role: 'assistant', content: `✓ Provider changé: **${target.name}** (${target.model})` }])
+ }).catch(() => {
+ setAiMessages(prev => [...prev, { role: 'assistant', content: 'Erreur lors du changement de provider.' }])
+ })
+ }).catch(() => {
+ setAiMessages(prev => [...prev, { role: 'assistant', content: 'Erreur: impossible de récupérer les providers' }])
+ })
+ } else {
+ api.getProviders().then(data => {
+ const active = data.providers?.find(p => p.active)
+ const modelMsg = active ? `**${active.name}** — ${active.model}` : 'Aucun provider actif configuré'
+ setAiMessages(prev => [...prev, { role: 'assistant', content: modelMsg }])
+ }).catch(() => {
+ setAiMessages(prev => [...prev, { role: 'assistant', content: 'Erreur: impossible de récupérer les providers' }])
+ })
+ }
+ aiLoadingRef.current = false
+ return
+ }
+
const currentTab = activeTabRef.current
console.log(`[Shell] _sendAiMessage: activeTab=${currentTab}, fromEvent=${fromEvent}, text="${trimmed.slice(0, 50)}"`)
- setAiMessages(prev => [...prev, { role: 'user', content: trimmed, _tabId: currentTab }])
+ setAiMessages(prev => [...prev, { role: 'user', content: trimmed, _tabId: currentTab, _analysis: isAnalysis || undefined }])
setAiLoading(true)
try {
@@ -1026,6 +1097,9 @@ export default function Shell({ api }) {
return
}
if (event && event.tool_result) {
+ if (event.tool_result.sudo_blocked) {
+ setSudoModal({ command: event.tool_result.command || event.tool_result.content })
+ }
const idx = toolCalls.findIndex(tc => tc.call && tc.call.tool_call_id === event.tool_result.tool_call_id)
if (idx >= 0) {
toolCalls[idx] = { ...toolCalls[idx], result: event.tool_result }
@@ -1091,23 +1165,20 @@ export default function Shell({ api }) {
return () => window.removeEventListener('ask-ai-terminal', handler)
}, [_sendAiMessage])
- const handleAnalyze = async () => {
- setAnalyzing(true)
- setAiMessages(prev => [...prev, { role: 'system', content: 'Analyse du système en cours...' }])
- try {
- const d = await api.analyzeSystem()
- if (d.analysis) {
- setAnalysisContent(d.analysis)
- localStorage.setItem('shell_analysis', d.analysis)
- }
- setAiMessages(prev => [...prev.filter(m => m.content !== 'Analyse du système en cours...'), {
- role: 'system',
- content: 'Analyse système terminée et sauvegardée. Le contexte système est maintenant disponible.'
- }])
- } catch (err) {
- setAiMessages(prev => prev.filter(m => m.content !== 'Analyse du système en cours...'))
- }
- setAnalyzing(false)
+ const handleAnalyze = () => {
+ _sendAiMessage(`Fais une analyse complète du système. Utilise l'outil terminal pour explorer et rédige un rapport structuré en markdown. Couvre:
+
+1. **OS & Matériel** — distrib, kernel, CPU, RAM, GPU, hostname
+2. **Disque & Partitions** — occupation, points de montage, inodes
+3. **Réseau** — interfaces, IP, ports en écoute, DNS, pare-feu, SSH actif ?
+4. **Utilisateurs & Sécurité** — utilisateurs, groupes sudo, last logins, fail2ban, règles sudo
+5. **Services & Processus** — systemd actifs, top processus par CPU/RAM, load average, uptime
+6. **Outils installés** — IDE (VSCode, JetBrains...), langages (go, python, node, rust...), gestionnaire de paquets, conteneurs (docker, podman), git, outils CLI
+7. **Environnement utilisateur** — shell, $HOME (arborescence top-level, nombre de fichiers/projects), dotfiles, config notable (.bashrc, .zshrc, starship)
+8. **Paquets & Mises à jour** — paquets obsolètes, kernel à jour, security patches
+9. **Recommandations** — outils manquants, optimisations, points d'attention
+
+Sois concret : cite les vraies versions, les vrais chemins, les vrais nombres. Le rapport fera office de carte d'identité du système (max ~40k tokens).`, true, true)
}
return (
@@ -1291,7 +1362,37 @@ export default function Shell({ api }) {
setAiInput(e.target.value)}
- onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); handleAiSend() } }}
+ onKeyDown={e => {
+ if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); handleAiSend(); return }
+ if (e.key === 'Tab') {
+ e.preventDefault()
+ const val = aiInput
+ const pos = e.target.selectionStart
+ const before = val.slice(0, pos)
+ const afterSlash = before.match(/\/[\w ]*$/)
+ if (afterSlash) {
+ const partial = afterSlash[0]
+ const matches = SHELL_AI_COMMANDS.filter(c => c.startsWith(partial) && c !== partial)
+ if (matches.length >= 1) {
+ let completed = matches[0]
+ for (const m of matches) {
+ while (!m.startsWith(completed)) completed = completed.slice(0, -1)
+ }
+ if (completed === partial && matches.length === 1) completed = matches[0]
+ if (completed.length > partial.length) {
+ const suffix = completed[completed.length - 1] === ' ' ? '' : (matches.length === 1 ? ' ' : '')
+ completed += suffix
+ const newText = val.slice(0, pos - afterSlash[0].length) + completed + val.slice(pos)
+ setAiInput(newText)
+ requestAnimationFrame(() => {
+ e.target.selectionStart = e.target.selectionEnd = pos - afterSlash[0].length + completed.length
+ })
+ }
+ }
+ }
+ return
+ }
+ }}
placeholder={aiAtLimit ? '/clear pour continuer' : t('shell.askAi')}
disabled={aiAtLimit && aiInput !== '/clear'}
/>
@@ -1308,7 +1409,12 @@ export default function Shell({ api }) {
{renderContent(analysisContent).map((part, i) =>
- part.type === 'code' ? (
+ part.type === 'code' && part.lang === 'mermaid' ? (
+
+ ) : part.type === 'code' ? (
{part.lang &&
{part.lang}
}
{part.content}
@@ -1371,6 +1477,22 @@ export default function Shell({ api }) {
)}
+
+ {sudoModal && (
+
setSudoModal(null)}>
+
e.stopPropagation()}>
+
Commande bloquée
+
+
L'IA a tenté d'exécuter une commande nécessitant des privilèges administrateur :
+
{sudoModal.command}
+
La commande a été bloquée. L'IA en a été informée et cherchera une alternative.
+
+
+
+
+
+
+ )}
)
}
@@ -1409,12 +1531,36 @@ function ShellToolBlock({ call, result }) {
)
}
+let mermaidIdCounter = 0
+
+function MermaidBlock({ code }) {
+ const ref = useRef(null)
+ const [svg, setSvg] = useState('')
+ const [error, setError] = useState(false)
+
+ useEffect(() => {
+ let cancelled = false
+ const id = `mermaid-${++mermaidIdCounter}`
+ mermaid.render(id, code).then(({ svg }) => {
+ if (!cancelled) setSvg(svg)
+ }).catch(() => {
+ if (!cancelled) setError(true)
+ })
+ return () => { cancelled = true }
+ }, [code])
+
+ if (error) return
{code}
+ if (!svg) return
Chargement du diagramme...
+ return
+}
+
function ShellAIMessage({ msg, sendToTerminal, terminalTabId }) {
const role = msg.role === 'user' ? 'user' : msg.role === 'system' ? 'system' : 'assistant'
const content = msg.content || ''
+ const [copiedIdx, setCopiedIdx] = useState(null)
if (role === 'user') {
- return
{content}
+ return
}
if (role === 'system') {
@@ -1426,16 +1572,16 @@ function ShellAIMessage({ msg, sendToTerminal, terminalTabId }) {
let displayContent = content
let streamingToolCalls = msg._toolCalls || null
- if (!streamingToolCalls) {
- try {
- const parsed = JSON.parse(content)
- if (parsed && Array.isArray(parsed.tool_calls)) {
+ try {
+ const parsed = JSON.parse(content)
+ if (parsed && Array.isArray(parsed.tool_calls)) {
+ if (!streamingToolCalls) {
parsedToolCalls = parsed.tool_calls
parsedToolResults = parsed.tool_results || null
- displayContent = parsed.content || ''
}
- } catch {}
- }
+ displayContent = parsed.content || ''
+ }
+ } catch {}
const parts = renderContent(displayContent)
@@ -1454,14 +1600,26 @@ function ShellAIMessage({ msg, sendToTerminal, terminalTabId }) {
return
})}
{parts.map((part, i) => {
+ if (part.type === 'code' && part.lang === 'mermaid') {
+ return (
+
+ )
+ }
if (part.type === 'code') {
return (
{part.lang &&
{part.lang}
}
{part.content}
-
)
}
diff --git a/web/src/styles/global.css b/web/src/styles/global.css
index f168472..50eae3b 100644
--- a/web/src/styles/global.css
+++ b/web/src/styles/global.css
@@ -458,6 +458,8 @@ input::placeholder { color: var(--text-disabled); }
.shell-ai-token-text { font-size: 10px; font-family: var(--font-mono); color: var(--text-tertiary); white-space: nowrap; }
.ai-panel-messages { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px; }
.ai-message { padding: 8px 12px; border-radius: var(--radius); font-size: 13px; line-height: 1.5; word-break: break-word; }
+.ai-message.user { background: var(--bg-elevated); border-left: 3px solid var(--accent-bright); color: var(--text-primary); }
+.ai-message.user.analysis { border-left-color: var(--info); background: color-mix(in srgb, var(--info) 8%, var(--bg-elevated)); }
.ai-message.assistant { background: var(--bg-card); border-left: 3px solid var(--accent); }
.ai-message.system { background: var(--bg-elevated); border-left: 3px solid var(--info); font-style: italic; color: var(--text-tertiary); font-size: 12px; }
.ai-message.assistant { background: var(--bg-card); border-left: 3px solid var(--accent); }
@@ -492,6 +494,23 @@ input::placeholder { color: var(--text-disabled); }
}
.shell-code-actions button:last-child { border-right: none; }
.shell-code-actions button:hover { background: var(--accent-bg); color: var(--accent); }
+.shell-code-actions button.copied { background: var(--accent-bg); color: var(--accent); animation: copy-flash 0.3s ease; }
+
+.shell-mermaid-container { padding: 12px; background: var(--bg); overflow-x: auto; display: flex; justify-content: center; }
+.shell-mermaid-container svg { max-width: 100%; height: auto; }
+.shell-mermaid-loading { padding: 16px; text-align: center; color: var(--text-tertiary); font-size: 12px; }
+.shell-mermaid-error { padding: 10px 12px; color: var(--accent-bright); font-family: var(--font-mono); font-size: 12px; white-space: pre-wrap; }
+
+.ai-message table { width: 100%; border-collapse: collapse; margin: 6px 0; font-size: 12px; }
+.ai-message th { background: var(--bg-surface); padding: 6px 10px; text-align: left; font-weight: 600; border: 1px solid var(--border); color: var(--text-secondary); }
+.ai-message td { padding: 5px 10px; border: 1px solid var(--border); color: var(--text-primary); }
+.ai-message tr:nth-child(even) td { background: var(--bg-surface); }
+
+@keyframes copy-flash {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.05); background: color-mix(in srgb, var(--accent) 20%, transparent); }
+ 100% { transform: scale(1); }
+}
.shell-analysis-modal {
background: var(--bg-elevated); border: 1px solid var(--border);
@@ -720,6 +739,25 @@ input::placeholder { color: var(--text-disabled); }
white-space: nowrap;
}
+/* Consumption */
+.dash-consumption-list { display: flex; flex-direction: column; gap: 10px; max-height: 270px; overflow-y: auto; }
+.dash-consumption-provider { display: flex; flex-direction: column; gap: 4px; }
+.dash-consumption-head { display: flex; align-items: center; justify-content: space-between; }
+.dash-consumption-name {
+ font-size: 11px; font-weight: 700; letter-spacing: 0.5px;
+}
+.dash-consumption-total {
+ font-size: 10px; font-family: var(--font-mono); color: var(--text-tertiary);
+}
+.dash-consumption-days {
+ display: flex; gap: 4px; flex-wrap: wrap;
+}
+.dash-consumption-day {
+ font-size: 9px; font-family: var(--font-mono); color: var(--text-tertiary);
+ background: var(--bg-input); padding: 1px 5px; border-radius: 4px;
+}
+.dash-consumption-day strong { color: var(--text-secondary); }
+
/* Processes */
.dash-proc-list { display: flex; flex-direction: column; gap: 4px; max-height: 270px; overflow-y: auto; }
.dash-proc-row {
@@ -933,11 +971,33 @@ input::placeholder { color: var(--text-disabled); }
background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
overflow: hidden; margin: 8px 0;
}
+.studio-code-header {
+ display: flex; align-items: center; justify-content: space-between;
+ background: var(--bg-surface); border-bottom: 1px solid var(--border);
+}
.studio-code-block pre { padding: 12px 16px; font-family: var(--font-mono); font-size: 13px; line-height: 1.5; overflow-x: auto; color: var(--text-primary); margin: 0; }
.studio-code-lang {
padding: 4px 12px; font-size: 11px; font-weight: 600; color: var(--text-tertiary);
- background: var(--bg-surface); border-bottom: 1px solid var(--border); text-transform: uppercase; letter-spacing: 0.5px;
+ background: var(--bg-surface); text-transform: uppercase; letter-spacing: 0.5px;
}
+.studio-copy-btn {
+ padding: 3px 10px; font-size: 10px; font-weight: 600; color: var(--text-tertiary);
+ background: transparent; border: none; border-left: 1px solid var(--border);
+ cursor: pointer; transition: all 0.15s; font-family: var(--font-sans);
+ white-space: nowrap;
+}
+.studio-copy-btn:hover { background: var(--accent-bg); color: var(--accent); }
+.studio-copy-btn.copied { background: var(--accent-bg); color: var(--accent); }
+
+.studio-mermaid-container { padding: 12px; background: var(--bg); overflow-x: auto; display: flex; justify-content: center; }
+.studio-mermaid-container svg { max-width: 100%; height: auto; }
+.studio-mermaid-loading { padding: 12px; text-align: center; color: var(--text-tertiary); font-size: 12px; }
+.studio-mermaid-error { padding: 10px 12px; color: var(--accent-bright); font-family: var(--font-mono); font-size: 12px; white-space: pre-wrap; }
+
+.feed-content table { width: 100%; border-collapse: collapse; margin: 8px 0; font-size: 13px; }
+.feed-content th { background: var(--bg-surface); padding: 6px 12px; text-align: left; font-weight: 600; border: 1px solid var(--border); color: var(--text-secondary); }
+.feed-content td { padding: 5px 12px; border: 1px solid var(--border); color: var(--text-primary); }
+.feed-content tr:nth-child(even) td { background: var(--bg-surface); }
.inline-code { background: var(--bg-input); padding: 2px 6px; border-radius: 4px; font-family: var(--font-mono); font-size: 13px; color: var(--accent-muted); }
.msg-h3 { font-size: 16px; font-weight: 700; color: var(--text-primary); margin: 10px 0 4px; display: block; }
.msg-h4 { font-size: 14px; font-weight: 700; color: var(--text-secondary); margin: 8px 0 3px; display: block; }