Files
HomeAgent/internal/plugins/webui/handler_memory.go
HomeAgent Agent 642e1c39b1 refactor(webui): handler.go 2993 行按资源拆成 11 个同包文件
拆法:按「资源面」搬家,每个顶层声明(func/type/var/const)整体搬到目标文件,
声明体一字未改,各文件按实际用到的包重新生成 import。文件头加一行说明本文件负责哪一面。

  handler.go              骨架:嵌入前端资源、Handler/构造、路由表、鉴权会话日志中间件、静态页
  handler_chat.go         对话面:消息模型与内存历史、SSE 事件订阅、对话/历史接口
  handler_upload.go       上传面:handleChatFile / handleUploads / 中断对话
  handler_memory.go       记忆面:图/文档/文本记忆、知识库、LLM 源、变更追踪
  handler_agents.go       内核与代理面:状态、kernel、人格、代理/快照/回滚
  handler_settings.go     设置与插件面:配置读写、插件列表详情(含 pluginmgr 反代)
  handler_terminal.go     终端面:终端会话、终端接口、命令历史
  handler_sse.go          SSE 环形缓冲(断线重连补发)
  handler_openai.go       OpenAI 兼容面:/v1/chat/completions
  handler_device.go       设备网关反代(HTTP + WS 升级透传)
  handler_files.go        /files/ 与 /uploads/ 下载

零漂移校验:拿重构前的 handler.go 与新 11 个文件逐行比对(忽略空行、package/import 头),
**丢失行 0**;新增行恰好是 11 个文件头注释(14 行)。

顺带修掉 import 里两处假使用:handler_openai 的 sdk 只作为 Handler 字段名出现(h.sdk.),
handler_settings 的 fmt 只出现在注释里 —— 都从 import 里去掉。

验证:go build ./... / go vet / webui+config+sdk 测试全绿;
起真实实例(沿用已有 data 目录)后 /status /settings /chat/history /plugins /terminals
/kernel /memory /config /login 全部 200,设置在注入 5MB 历史的情况下仍是 33,921 字节。

最大文件从 2993 → 706 行(handler_chat.go)。
2026-09-14 07:03:47 +08:00

337 lines
11 KiB
Go

package webui
import (
"strconv"
"strings"
"encoding/json"
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
"net/http"
)
// 记忆面:图记忆 / 文档记忆 / 文本记忆 / 知识库 / LLM 源 / 变更追踪。
func (h *Handler) handleMemory(w http.ResponseWriter, r *http.Request) {
if h.memory == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "memory system not available"})
return
}
switch r.Method {
case http.MethodGet:
userInput := r.URL.Query().Get("q")
keywords := strings.Split(userInput, ",")
depth, _ := strconv.Atoi(r.URL.Query().Get("depth"))
if depth <= 0 {
depth = 2
}
entities, relations, err := h.memory.Recall(keywords, depth)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{"entities": entities, "relations": relations})
case http.MethodPost:
var req struct {
Triples []sdk.Triple `json:"triples"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
return
}
if err := h.memory.Commit(req.Triples); err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{"status": "committed", "committed": len(req.Triples)})
case http.MethodDelete:
var req struct {
Criteria map[string]string `json:"criteria"`
Mode string `json:"mode"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
return
}
deleted, err := h.memory.Purge(req.Criteria, req.Mode)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]int{"deleted": deleted})
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleMemoryContext(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if h.indexer == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "indexer not available"})
return
}
userInput := r.URL.Query().Get("q")
injected, err := h.indexer.BuildContext(userInput)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"context": h.indexer.FormatContext(injected),
"summary": injected.Summary,
"entities": injected.Entities,
"token_estimate": injected.TokenEstimate,
"tool_prompt": h.indexer.BuildToolPrompt(),
})
}
func (h *Handler) handleMemoryTools(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if h.indexer == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "indexer not available"})
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"tools": h.indexer.GetToolDefinitions(),
"tool_prompt": h.indexer.BuildToolPrompt(),
})
}
func (h *Handler) handleMemoryGraph(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if h.memory == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "memory system not available"})
return
}
data, err := h.memory.GraphData()
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{"success": true, "data": data})
}
func (h *Handler) handleKnowledge(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
if h.knowledge == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "knowledge not available"})
return
}
query := r.URL.Query().Get("q")
if query != "" {
results, err := h.knowledge.Search(query, 10)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{"results": results})
return
}
categories, err := h.knowledge.List()
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"categories": categories,
"stats": h.knowledge.Stats(),
})
case http.MethodPost:
if h.knowledge == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "knowledge not available"})
return
}
ct := r.Header.Get("Content-Type")
if strings.HasPrefix(ct, "multipart/form-data") {
if err := r.ParseMultipartForm(10 << 20); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
name := r.FormValue("name")
file, _, err := r.FormFile("file")
if err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "file required"})
return
}
defer file.Close()
buf := make([]byte, 10<<20)
n, _ := file.Read(buf)
content := string(buf[:n])
if err := h.knowledge.Add(name, content); err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusCreated, map[string]string{"status": "created", "name": name})
return
}
var req struct {
Name string `json:"name"`
Content string `json:"content"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
return
}
if req.Name == "" || req.Content == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "name and content required"})
return
}
if err := h.knowledge.Add(req.Name, req.Content); err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusCreated, map[string]string{"status": "created", "name": req.Name})
case http.MethodDelete:
name := r.URL.Query().Get("name")
if name == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "name query param required"})
return
}
if err := h.knowledge.Remove(name); err != nil {
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted", "name": name})
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleTextMemory(w http.ResponseWriter, r *http.Request) {
if h.textMem == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "text memory not available"})
return
}
switch r.Method {
case http.MethodGet:
recent, _ := h.textMem.RecentEvents(50)
stats := h.textMem.Stats()
writeJSON(w, http.StatusOK, map[string]interface{}{
"stats": stats,
"recent": recent,
})
case http.MethodDelete:
writeJSON(w, http.StatusAccepted, map[string]string{"status": "not_implemented"})
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleAdapters(w http.ResponseWriter, r *http.Request) {
if h.adapter == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "lua vm not available"})
return
}
switch r.Method {
case http.MethodGet:
writeJSON(w, http.StatusOK, map[string]interface{}{"adapters": h.adapter.List()})
case http.MethodPost:
var req struct {
Name string `json:"name"`
Code string `json:"code"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
return
}
if err := h.adapter.Load(req.Name, req.Code); err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusCreated, map[string]string{"status": "loaded", "name": req.Name})
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleAdapterByID(w http.ResponseWriter, r *http.Request) {
if h.adapter == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "lua vm not available"})
return
}
name := strings.TrimPrefix(r.URL.Path, "/api/v1/adapters/")
if name == "" {
http.NotFound(w, r)
return
}
switch r.Method {
case http.MethodGet:
for _, a := range h.adapter.List() {
if a.Name == name {
writeJSON(w, http.StatusOK, a)
return
}
}
http.NotFound(w, r)
case http.MethodDelete:
if err := h.adapter.Remove(name); err != nil {
writeJSON(w, http.StatusNotFound, map[string]string{"error": "adapter not found"})
return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted", "name": name})
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *Handler) handleNetwork(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"network_status": "monitoring",
"endpoints": h.config.Get().Defaults.LLMEndpoints,
})
}
func (h *Handler) handleTracker(w http.ResponseWriter, r *http.Request) {
if h.tracker == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "tracker not available"})
return
}
path := strings.TrimPrefix(r.URL.Path, "/api/v1/tracker")
path = strings.TrimPrefix(path, "/")
switch {
case path == "changesets" && r.Method == http.MethodGet:
writeJSON(w, http.StatusOK, map[string]interface{}{
"changesets": h.tracker.ChangeSets(),
"count": len(h.tracker.ChangeSets()),
})
case path == "rollback" && r.Method == http.MethodPost:
if err := h.tracker.Rollback(); err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "rollback_complete"})
case path == "" && r.Method == http.MethodGet:
writeJSON(w, http.StatusOK, map[string]interface{}{
"stats": h.tracker.Stats(),
"has_changes": h.tracker.HasChanges(),
"changesets": len(h.tracker.ChangeSets()),
})
case path == "" && r.Method == http.MethodDelete:
if err := h.tracker.Rollback(); err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "cleared"})
default:
http.Error(w, "not found", http.StatusNotFound)
}
}