mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-22 18:08:04 +00:00
拆法:按「资源面」搬家,每个顶层声明(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)。
337 lines
11 KiB
Go
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)
|
|
}
|
|
}
|