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)。
77 lines
2.2 KiB
Go
77 lines
2.2 KiB
Go
package webui
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
"sync"
|
||
"time"
|
||
|
||
"encoding/json"
|
||
)
|
||
|
||
// SSE 环形缓冲:/api/v1/chat/events 断线重连时按 Last-Event-ID 补发。
|
||
|
||
// sseEventRecord 保存一条 SSE 事件元数据,供断线重连时按 Last-Event-ID 重放遗漏事件。
|
||
type sseEventRecord struct {
|
||
id string // SSE 事件 id 值(如 "1234567890-5")
|
||
eventType string // 事件类型(agent_output, reasoning 等)
|
||
data json.RawMessage // 序列化后的 payload JSON
|
||
}
|
||
|
||
// sseEventRing 是一个固定大小的环状缓冲区,保持最近 cap 条 SSE 事件。
|
||
type sseEventRing struct {
|
||
mu sync.Mutex
|
||
buf []sseEventRecord
|
||
cap int
|
||
}
|
||
|
||
func newSSEEventRing(cap int) *sseEventRing {
|
||
return &sseEventRing{cap: cap}
|
||
}
|
||
|
||
// Append 追加一条事件,超过容量时丢弃最旧条目。
|
||
func (r *sseEventRing) Append(id, eventType string, data json.RawMessage) {
|
||
r.mu.Lock()
|
||
defer r.mu.Unlock()
|
||
r.buf = append(r.buf, sseEventRecord{id: id, eventType: eventType, data: data})
|
||
if len(r.buf) > r.cap {
|
||
r.buf = r.buf[len(r.buf)-r.cap:]
|
||
}
|
||
}
|
||
|
||
// After 返回所有在指定 id 之后的事件(按写入顺序),若 id 不在缓冲区中则返回全部。
|
||
func (r *sseEventRing) After(id string) []sseEventRecord {
|
||
r.mu.Lock()
|
||
defer r.mu.Unlock()
|
||
for i := len(r.buf) - 1; i >= 0; i-- {
|
||
if r.buf[i].id == id {
|
||
result := make([]sseEventRecord, len(r.buf)-i-1)
|
||
copy(result, r.buf[i+1:])
|
||
return result
|
||
}
|
||
}
|
||
// ID 不在缓冲区(可能是太旧或从未收到),返回全部
|
||
result := make([]sseEventRecord, len(r.buf))
|
||
copy(result, r.buf)
|
||
return result
|
||
}
|
||
|
||
// sendSSE 向 writeCh 发送一条 SSE 事件;队列满时等 100ms 再试,
|
||
// 比立即 drop 更友好,避免密集 tool_call/delta 期间前端丢帧。
|
||
func sendSSE(writeCh chan string, id, eventType, data string) {
|
||
line := fmt.Sprintf("id: %s\nevent: %s\ndata: %s\n", id, eventType, data)
|
||
select {
|
||
case writeCh <- line:
|
||
return
|
||
default:
|
||
}
|
||
// 队列满:等 100ms 让 writer flush,再试一次
|
||
timer := time.NewTimer(100 * time.Millisecond)
|
||
defer timer.Stop()
|
||
select {
|
||
case writeCh <- line:
|
||
case <-timer.C:
|
||
log.Printf("[SSE] DROPPED %s id=%s (writeCh full 100ms, len=%d)", eventType, id, len(writeCh))
|
||
}
|
||
}
|