package main import ( "bufio" "fmt" "os" "strings" ) // cmdChat is the interactive terminal chat loop. // First positional arg (if any) becomes the system prompt. func cmdChat(args []string) error { if !healthCheck() { return fmt.Errorf("serveur injoignable sur :%d — jean start d'abord", LLMPort()) } sysPrompt := strings.Join(args, " ") msgs := []Message{} if sysPrompt != "" { msgs = append(msgs, Message{Role: "system", Content: sysPrompt}) } fmt.Printf("\n%s — /reset pour vider, /sys pour changer le system, /quit ou Ctrl-D pour sortir\n", cyan("jean chat")) if sysPrompt != "" { fmt.Println(dim("system: " + sysPrompt)) } fmt.Println() sc := bufio.NewScanner(os.Stdin) sc.Buffer(make([]byte, 0, 64*1024), 1<<20) for { fmt.Print(bold("you") + " > ") if !sc.Scan() { fmt.Println() return nil } user := sc.Text() u := strings.TrimSpace(user) if u == "" { continue } if u == "/quit" || u == "/exit" { return nil } if u == "/reset" { kept := []Message{} for _, m := range msgs { if m.Role == "system" { kept = append(kept, m) } } msgs = kept fmt.Println(dim("[contexte vidé]")) continue } if strings.HasPrefix(u, "/sys ") { newSys := strings.TrimSpace(u[5:]) msgs = msgs[:0] if newSys != "" { msgs = append(msgs, Message{Role: "system", Content: newSys}) } fmt.Println(dim("[system mis à jour]")) continue } msgs = append(msgs, Message{Role: "user", Content: user}) full := strings.Builder{} inReason := false var stats *StatsEvent // Print the assistant prefix once; reasoning is shown inline with a tag. fmt.Print(cyan("jean") + " > ") err := runChat(InjectSkills(msgs), 0.7, func(ev StreamEvent) bool { switch { case ev.Err != nil: fmt.Printf("\n%s\n", red("[erreur] "+ev.Err.Error())) case ev.Stats != nil: stats = ev.Stats case ev.ToolUsed != nil: icon := "📖" verb := "lecture du skill" if ev.ToolUsed.Name == "run_shell" { icon = "⚙️" verb = "exécution" } if inReason { fmt.Print("\n") inReason = false } fmt.Printf("\n%s %s : %s\n%s ", dim(icon+" "+verb), "", magenta(ev.ToolUsed.Label), cyan("jean")+" >") case ev.Reasoning != "": if !inReason { fmt.Print(magenta("[reasoning] ") + dim("")) inReason = true } fmt.Print(dim(ev.Reasoning)) case ev.Content != "": if inReason { fmt.Print("\n" + cyan("jean") + " > ") inReason = false } full.WriteString(ev.Content) fmt.Print(ev.Content) } return true }) if inReason { fmt.Println() } fmt.Println() if stats != nil { fmt.Printf("%s prefill %d tok · %.0f tok/s decode %d tok · %.1f tok/s\n", dim("→"), stats.PromptTokens, stats.PromptPerSecond, stats.GenTokens, stats.GenPerSecond) } fmt.Println() if err == nil { msgs = append(msgs, Message{Role: "assistant", Content: full.String()}) } } }