All checks were successful
Beta Release / beta (push) Successful in 5m9s
Major additions: - RAG pipeline (indexing, chunking, search) with sidebar upload button - Memory system with CRUD API - Plugins and lessons modules - MCP discovery and MCP server - Advanced skills (auto-create, conditional, improver) - Agent browser/image support, delegate, sessions - File editor with CodeMirror in split panes - Markdown rendering via react-markdown + KaTeX + highlight.js - Raw markdown toggle - PWA manifest + service worker - Extension UI redesign with new design tokens and studio-style chat - Pipeline API for chat streaming - Mobile responsive layout 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
257 lines
5.7 KiB
Go
257 lines
5.7 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/muyue/muyue/internal/memory"
|
|
)
|
|
|
|
func (s *Server) ensureMemoryStore() (*memory.Store, error) {
|
|
if s.memoryStore == nil {
|
|
store, err := memory.NewStore()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.memoryStore = store
|
|
}
|
|
return s.memoryStore, nil
|
|
}
|
|
|
|
func (s *Server) handleMemoryList(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" {
|
|
writeError(w, "GET only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
store, err := s.ensureMemoryStore()
|
|
if err != nil {
|
|
writeError(w, "memory store: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var memType memory.MemoryType
|
|
if t := r.URL.Query().Get("type"); t != "" {
|
|
memType = memory.MemoryType(t)
|
|
}
|
|
|
|
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
|
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
|
|
|
|
memories, err := store.List(memType, limit, offset)
|
|
if err != nil {
|
|
writeError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
count, _ := store.Count()
|
|
|
|
writeJSON(w, map[string]interface{}{
|
|
"memories": memories,
|
|
"count": len(memories),
|
|
"total": count,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleMemoryCreate(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "POST" {
|
|
writeError(w, "POST only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var body struct {
|
|
Type string `json:"type"`
|
|
Key string `json:"key"`
|
|
Content string `json:"content"`
|
|
Tags string `json:"tags,omitempty"`
|
|
Source string `json:"source,omitempty"`
|
|
Confidence float64 `json:"confidence,omitempty"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
writeError(w, "invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if body.Key == "" || body.Content == "" {
|
|
writeError(w, "key and content are required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
store, err := s.ensureMemoryStore()
|
|
if err != nil {
|
|
writeError(w, "memory store: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
memType := memory.MemoryType(body.Type)
|
|
if memType == "" {
|
|
memType = memory.TypeFact
|
|
}
|
|
|
|
m := &memory.Memory{
|
|
Type: memType,
|
|
Key: body.Key,
|
|
Content: body.Content,
|
|
Tags: body.Tags,
|
|
Source: body.Source,
|
|
Confidence: body.Confidence,
|
|
}
|
|
if m.Confidence == 0 {
|
|
m.Confidence = 0.5
|
|
}
|
|
|
|
if err := store.Store(m); err != nil {
|
|
writeError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, map[string]interface{}{
|
|
"created": true,
|
|
"memory": m,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleMemoryDelete(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "DELETE" {
|
|
writeError(w, "DELETE only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
id := strings.TrimPrefix(r.URL.Path, "/api/memory/")
|
|
if id == "" {
|
|
writeError(w, "memory id required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
store, err := s.ensureMemoryStore()
|
|
if err != nil {
|
|
writeError(w, "memory store: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if err := store.Delete(id); err != nil {
|
|
writeError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, map[string]interface{}{
|
|
"deleted": true,
|
|
"id": id,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleMemoryOperation(w http.ResponseWriter, r *http.Request) {
|
|
path := strings.TrimPrefix(r.URL.Path, "/api/memory/")
|
|
|
|
if path == "" {
|
|
s.handleMemoryList(w, r)
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case "DELETE":
|
|
s.handleMemoryDelete(w, r)
|
|
case "GET":
|
|
store, err := s.ensureMemoryStore()
|
|
if err != nil {
|
|
writeError(w, "memory store: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
m, err := store.Get(path)
|
|
if err != nil {
|
|
writeError(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
writeJSON(w, m)
|
|
default:
|
|
writeError(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleMemorySearch(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" {
|
|
writeError(w, "GET only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
query := r.URL.Query().Get("q")
|
|
if query == "" {
|
|
writeError(w, "query parameter 'q' is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
store, err := s.ensureMemoryStore()
|
|
if err != nil {
|
|
writeError(w, "memory store: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
|
results, err := store.Search(query, limit)
|
|
if err != nil {
|
|
writeError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, map[string]interface{}{
|
|
"results": results,
|
|
"count": len(results),
|
|
"query": query,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleMemoryRecall(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" {
|
|
writeError(w, "GET only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
query := r.URL.Query().Get("q")
|
|
|
|
store, err := s.ensureMemoryStore()
|
|
if err != nil {
|
|
writeError(w, "memory store: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
injector := memory.NewInjector(store)
|
|
contextBlock, err := injector.BuildContextBlock(query)
|
|
if err != nil {
|
|
writeError(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, map[string]interface{}{
|
|
"context": contextBlock,
|
|
"query": query,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleMemoryContext(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != "GET" {
|
|
writeError(w, "GET only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
store, err := s.ensureMemoryStore()
|
|
if err != nil {
|
|
writeError(w, "memory store: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
preferences, _ := store.RecallPreferences()
|
|
facts, _ := store.RecallFacts()
|
|
|
|
recentCutoff := time.Now().Add(-24 * time.Hour)
|
|
recent, _ := store.RecallRecent(recentCutoff, 10)
|
|
|
|
writeJSON(w, map[string]interface{}{
|
|
"preferences": preferences,
|
|
"facts": facts,
|
|
"recent": recent,
|
|
})
|
|
}
|