All checks were successful
Beta Release / beta (push) Successful in 45s
- New ShellConvStore with persistent history (shell_conversation.json) - 100k token limit — input grays out, must /clear to continue - Commands limited to /clear and /help only - Shell AI has NO tools — read-only analysis, never executes code - "Analyste Système" panel with system analysis button - System analysis uses Studio AI to write system_analysis.md, prepended as context on every conversation start - Code blocks show "Copier" and "Terminal" buttons to copy or send code directly to the active terminal via WebSocket - Token bar shows usage with warning at 80% 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
122 lines
2.3 KiB
Go
122 lines
2.3 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"github.com/muyue/muyue/internal/config"
|
|
)
|
|
|
|
const shellMaxTokens = 100000
|
|
const shellCharsPerToken = 4
|
|
|
|
type ShellMessage struct {
|
|
ID string `json:"id"`
|
|
Role string `json:"role"`
|
|
Content string `json:"content"`
|
|
Time string `json:"time"`
|
|
}
|
|
|
|
type ShellConvStore struct {
|
|
mu sync.RWMutex
|
|
path string
|
|
msgs []ShellMessage
|
|
}
|
|
|
|
func NewShellConvStore() *ShellConvStore {
|
|
dir, err := config.ConfigDir()
|
|
if err != nil {
|
|
dir = "/tmp/muyue"
|
|
}
|
|
path := filepath.Join(dir, "shell_conversation.json")
|
|
s := &ShellConvStore{path: path}
|
|
s.load()
|
|
return s
|
|
}
|
|
|
|
func (s *ShellConvStore) load() {
|
|
data, err := os.ReadFile(s.path)
|
|
if err != nil {
|
|
s.msgs = []ShellMessage{}
|
|
return
|
|
}
|
|
json.Unmarshal(data, &s.msgs)
|
|
if s.msgs == nil {
|
|
s.msgs = []ShellMessage{}
|
|
}
|
|
}
|
|
|
|
func (s *ShellConvStore) save() {
|
|
data, _ := json.MarshalIndent(s.msgs, "", " ")
|
|
os.MkdirAll(filepath.Dir(s.path), 0755)
|
|
os.WriteFile(s.path, data, 0600)
|
|
}
|
|
|
|
func (s *ShellConvStore) Get() []ShellMessage {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
out := make([]ShellMessage, len(s.msgs))
|
|
copy(out, s.msgs)
|
|
return out
|
|
}
|
|
|
|
func (s *ShellConvStore) Add(role, content string) ShellMessage {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
msg := ShellMessage{
|
|
ID: time.Now().Format("20060102150405.000"),
|
|
Role: role,
|
|
Content: content,
|
|
Time: time.Now().Format(time.RFC3339),
|
|
}
|
|
s.msgs = append(s.msgs, msg)
|
|
s.save()
|
|
return msg
|
|
}
|
|
|
|
func (s *ShellConvStore) Clear() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.msgs = []ShellMessage{}
|
|
s.save()
|
|
}
|
|
|
|
func (s *ShellConvStore) ApproxTokens() int {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
total := 0
|
|
for _, m := range s.msgs {
|
|
total += utf8.RuneCountInString(m.Content) / shellCharsPerToken
|
|
}
|
|
return total
|
|
}
|
|
|
|
func (s *ShellConvStore) AtLimit() bool {
|
|
return s.ApproxTokens() >= shellMaxTokens
|
|
}
|
|
|
|
func LoadSystemAnalysis() string {
|
|
dir, err := config.ConfigDir()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(dir, "system_analysis.md"))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(data)
|
|
}
|
|
|
|
func SaveSystemAnalysis(content string) error {
|
|
dir, err := config.ConfigDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
os.MkdirAll(dir, 0755)
|
|
return os.WriteFile(filepath.Join(dir, "system_analysis.md"), []byte(content), 0644)
|
|
}
|