feat: RAG, memory, plugins, lessons, file editor, split panes, Markdown rendering, PWA + UI overhaul
All checks were successful
Stable Release / stable (push) Successful in 1m34s
All checks were successful
Stable Release / stable (push) Successful in 1m34s
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>
This commit is contained in:
210
internal/api/handlers_skills_advanced.go
Normal file
210
internal/api/handlers_skills_advanced.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/muyue/muyue/internal/skills"
|
||||
)
|
||||
|
||||
func (s *Server) handleSkillAutoCreate(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
writeError(w, "POST only", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Snippets []struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
} `json:"snippets"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var snippets []skills.ConversationSnippet
|
||||
for _, s := range body.Snippets {
|
||||
snippets = append(snippets, skills.ConversationSnippet{
|
||||
Role: s.Role,
|
||||
Content: s.Content,
|
||||
})
|
||||
}
|
||||
|
||||
proposals := skills.AnalyzeConversation(snippets)
|
||||
|
||||
var results []map[string]interface{}
|
||||
for i := range proposals {
|
||||
p := &proposals[i]
|
||||
if err := skills.SaveProposal(p); err != nil {
|
||||
continue
|
||||
}
|
||||
results = append(results, map[string]interface{}{
|
||||
"name": p.Name,
|
||||
"description": p.Description,
|
||||
"confidence": p.Confidence,
|
||||
"category": p.Category,
|
||||
"tags": p.SuggestedTags,
|
||||
})
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"proposals": results,
|
||||
"count": len(results),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleSkillDetail(w http.ResponseWriter, r *http.Request) {
|
||||
path := strings.TrimPrefix(r.URL.Path, "/api/skills/detail/")
|
||||
|
||||
if strings.HasSuffix(path, "/improve") {
|
||||
name := strings.TrimSuffix(path, "/improve")
|
||||
s.handleSkillImprove(w, r, name)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, "/history") {
|
||||
name := strings.TrimSuffix(path, "/history")
|
||||
s.handleSkillHistoryGet(w, r, name)
|
||||
return
|
||||
}
|
||||
|
||||
writeError(w, "unknown skill action", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *Server) handleSkillImprove(w http.ResponseWriter, r *http.Request, name string) {
|
||||
if r.Method != "POST" {
|
||||
writeError(w, "POST only", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
skill, err := skills.Get(name)
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Context string `json:"context,omitempty"`
|
||||
Apply bool `json:"apply,omitempty"`
|
||||
}
|
||||
if r.Body != nil {
|
||||
json.NewDecoder(r.Body).Decode(&body)
|
||||
}
|
||||
|
||||
improver, err := skills.NewSkillImprover()
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
suggestions, err := improver.Analyze(skill, body.Context)
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if body.Apply && len(suggestions) > 0 {
|
||||
if err := improver.ApplyImprovement(name, suggestions[0]); err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
updated, _ := skills.Get(name)
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"applied": true,
|
||||
"suggestion": suggestions[0],
|
||||
"updated": updated,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"skill": skill.Name,
|
||||
"suggestions": suggestions,
|
||||
"count": len(suggestions),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleSkillHistoryGet(w http.ResponseWriter, r *http.Request, name string) {
|
||||
if r.Method != "GET" {
|
||||
writeError(w, "GET only", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
improver, err := skills.NewSkillImprover()
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
history, err := improver.GetHistory(name)
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"skill": name,
|
||||
"history": history,
|
||||
"count": len(history),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleSkillProposals(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
proposals, err := skills.LoadProposals()
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"proposals": proposals,
|
||||
"count": len(proposals),
|
||||
})
|
||||
|
||||
case "POST":
|
||||
var body struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
writeError(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
proposals, err := skills.LoadProposals()
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var target *skills.AutoCreateProposal
|
||||
for i := range proposals {
|
||||
if proposals[i].Name == body.Name {
|
||||
target = &proposals[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if target == nil {
|
||||
writeError(w, "proposal not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
skill, err := skills.CreateFromProposal(target)
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
skills.DeleteProposal(body.Name)
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"created": true,
|
||||
"skill": skill,
|
||||
})
|
||||
|
||||
default:
|
||||
writeError(w, "GET or POST only", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user