mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-05 00:37:32 +00:00
Merge remote-tracking branch 'refs/remotes/gh/main' into fix/webui-gzip-race
This commit is contained in:
@ -349,7 +349,7 @@ func TestServerHasReadSideTimeouts(t *testing.T) {
|
||||
if srv.IdleTimeout <= 0 {
|
||||
t.Errorf("IdleTimeout = %v,必须 > 0(keep-alive 空闲连接会无限累积)", srv.IdleTimeout)
|
||||
}
|
||||
// ReadTimeout 限制「读完整请求」的<EFBFBD><EFBFBD>间(含 body),防慢速上传。
|
||||
// ReadTimeout 限制「读完整请求」的时间(含 body),防慢速上传。
|
||||
if srv.ReadTimeout <= 0 {
|
||||
t.Errorf("ReadTimeout = %v,必须 > 0(慢速上传会长期占用连接)", srv.ReadTimeout)
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>HomeAgent Dashboard</title>
|
||||
<title>HologramAgent Dashboard</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||
<script
|
||||
src="/static/three.min.js"
|
||||
@ -40,7 +40,7 @@
|
||||
<h1>
|
||||
<img
|
||||
src="/logo.svg"
|
||||
alt="HomeAgent"
|
||||
alt="HologramAgent"
|
||||
style="
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
@ -49,7 +49,7 @@
|
||||
flex-shrink: 0;
|
||||
border-radius: 6px;
|
||||
"
|
||||
/>HomeAgent
|
||||
/>HologramAgent
|
||||
</h1>
|
||||
<div class="nav-links">
|
||||
<!--
|
||||
@ -201,7 +201,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
<div class="breadcrumb" id="breadcrumb">
|
||||
<span class="crumb-home">HomeAgent</span>
|
||||
<span class="crumb-home">HologramAgent</span>
|
||||
<span class="crumb-sep">/</span>
|
||||
<span class="crumb-current" data-i18n="navOverview">概览</span>
|
||||
</div>
|
||||
|
||||
@ -2009,7 +2009,7 @@ function updateOverview() {
|
||||
ovSet(
|
||||
"ov-version-sub",
|
||||
kv
|
||||
? (kb.kernel_name || "HomeAgent") +
|
||||
? (kb.kernel_name || "HologramAgent") +
|
||||
(kb.commit && kb.commit !== "unknown"
|
||||
? " · " + String(kb.commit).slice(0, 7)
|
||||
: "")
|
||||
@ -5590,7 +5590,7 @@ function renderProxyServicesCard() {
|
||||
html +=
|
||||
'<div style="font-size:12px;color:var(--text-muted);margin-bottom:10px">' +
|
||||
__(
|
||||
"插件服务经 HomeAgent 同一端口反代。子域形态需要 DNS 能解析 ",
|
||||
"插件服务经 HologramAgent 同一端口反代。子域形态需要 DNS 能解析 ",
|
||||
"Proxied through the same port. The subdomain form needs DNS for ",
|
||||
) +
|
||||
"<b>" +
|
||||
@ -6281,7 +6281,7 @@ function renderKernel() {
|
||||
'<div class="kv-row"><span class="key">' +
|
||||
__("内核", "Kernel") +
|
||||
'</span><span class="val">' +
|
||||
escHtml(b.kernel_name || "HomeAgent") +
|
||||
escHtml(b.kernel_name || "HologramAgent") +
|
||||
"</span></div>" +
|
||||
'<div class="kv-row"><span class="key">' +
|
||||
__("内核版本", "Kernel version") +
|
||||
|
||||
@ -36,7 +36,7 @@ var dashboardFS embed.FS
|
||||
// 看到「3D 星图不可用(CDN 加载失败)」—— 服务端 curl 同一 URL 是 200,
|
||||
// 说明是**浏览器**访问不到公网 CDN(内网 / 出口受限 / 断网)。
|
||||
//
|
||||
// 换 CDN 只是把同一个赌注重下<EFBFBD><EFBFBD><EFBFBD>遍:HomeAgent 明确支持离线与内网部署,
|
||||
// 换 CDN 只是把同一个赌注重下一遍:HologramAgent 明确支持离线与内网部署,
|
||||
// 前端却有 4 个硬依赖在公网上,断网即坏且用户无从修复。
|
||||
//
|
||||
// 连带的两个理由:
|
||||
@ -50,7 +50,7 @@ var staticFS embed.FS
|
||||
|
||||
var dashboardHTML string
|
||||
|
||||
const loginHTML = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>HomeAgent 登录</title><style>
|
||||
const loginHTML = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>HologramAgent 登录</title><style>
|
||||
:root{--sakura-300:#ffb3c8;--sakura-400:#ff7fac;--sakura-500:#f33b7c;--frost-300:#88c0d0;--text-primary:#e8e6ee;--text-secondary:#a0a3b5;--text-muted:#6e7284;--bg-primary:#0d0d16;--bg-card:rgba(24,24,38,0.72);--bg-input:rgba(13,13,22,0.6);--border-color:rgba(255,255,255,0.09);--glass-border:rgba(255,255,255,0.12);--glass-blur:20px;--radius-lg:18px;--radius-md:12px;--radius-pill:999px;--shadow-glow:0 0 18px rgba(243,59,124,0.35);--ease-out:cubic-bezier(.22,.61,.36,1)}
|
||||
[data-theme="light"]{--text-primary:#23252e;--text-secondary:#5b5f73;--text-muted:#9aa0b5;--bg-primary:#f6f3f8;--bg-card:rgba(255,255,255,0.72);--bg-input:rgba(255,255,255,0.8);--border-color:rgba(35,37,46,0.1);--glass-border:rgba(255,255,255,0.75);--shadow-glow:0 0 18px rgba(243,59,124,0.28)}
|
||||
*{box-sizing:border-box}
|
||||
@ -74,7 +74,7 @@ button:disabled{opacity:.6;cursor:not-allowed;transform:none}
|
||||
.foot{margin-top:20px;font-size:11px;color:var(--text-muted);text-align:center}
|
||||
.foot svg{width:12px;height:12px;vertical-align:-2px}
|
||||
@media(max-width:480px){.login-card{padding:28px 22px 22px}}
|
||||
</style></head><body><div class="login-wrap"><div class="login-card"><div class="logo"><img src="/logo.svg" alt="HomeAgent"></div><h1>HomeAgent</h1><p class="sub">智能家居助手控制台</p><form id="login-form"><label>用户名</label><input id="username" autocomplete="username" placeholder="请输入用户名" required><label>密码</label><input id="password" type="password" autocomplete="current-password" placeholder="请输入密码" required><button type="submit" id="submit-btn">登 录</button><div id="err" class="err"></div></form></div><div class="foot">HomeAgent · NapCat Theme</div></div><script>
|
||||
</style></head><body><div class="login-wrap"><div class="login-card"><div class="logo"><img src="/logo.svg" alt="HologramAgent"></div><h1>HologramAgent</h1><p class="sub">智能家居助手控制台</p><form id="login-form"><label>用户名</label><input id="username" autocomplete="username" placeholder="请输入用户名" required><label>密码</label><input id="password" type="password" autocomplete="current-password" placeholder="请输入密码" required><button type="submit" id="submit-btn">登 录</button><div id="err" class="err"></div></form></div><div class="foot">HologramAgent · NapCat Theme</div></div><script>
|
||||
(function(){var m=window.matchMedia('(prefers-color-scheme: light)');function apply(){document.documentElement.setAttribute('data-theme',m.matches?'light':'dark')}apply();m.addEventListener('change',apply)})();
|
||||
document.getElementById('login-form').addEventListener('submit',async(e)=>{e.preventDefault();const username=document.getElementById('username').value.trim();const password=document.getElementById('password').value;const err=document.getElementById('err');const btn=document.getElementById('submit-btn');err.textContent='';if(!username||!password){err.textContent='请输入用户名和密码';return}btn.disabled=true;btn.textContent='登录中...';try{const r=await fetch('/api/v1/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username,password})});if(r.ok){location.href='/';return}let data={};try{data=await r.json()}catch(_){}err.textContent=data.error||'登录失败'}catch(_){err.textContent='网络错误,请重试'}finally{btn.disabled=false;btn.textContent='登 录'}});
|
||||
document.getElementById('password').addEventListener('keydown',function(e){if(e.key==='Enter')document.getElementById('login-form').dispatchEvent(new Event('submit'))});
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/JianFeeeee/HologramAgent/internal/memory"
|
||||
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// fakeGraphMemory 是一个最小 MemoryAPI 桩,只为把 GraphData 喂给 handler。
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
"github.com/JianFeeeee/HologramAgent/internal/knowledge"
|
||||
"github.com/JianFeeeee/HologramAgent/internal/memory"
|
||||
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||||
pubsdk "github.com/JianFeeeee/homeagentsdk/sdk"
|
||||
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
|
||||
)
|
||||
|
||||
// 记忆面:图记忆 / 文档记忆 / 文本记忆 / 知识库 / LLM 源 / 变更追踪。
|
||||
|
||||
@ -363,7 +363,7 @@ type openAIMessage struct {
|
||||
// OpenAI 客户端把它当作能力探测(probe)。请求方要的不是模型清单本身,
|
||||
// 而是一个「这个端点讲 OpenAI 协议」的确认。
|
||||
//
|
||||
// 所以这里返回 HomeAgent 自身作为唯一条目:调用方无论填哪个 id 都会
|
||||
// 所以这里返回 HologramAgent 自身作为唯一条目:调用方无论填哪个 id 都会
|
||||
// 被路由到同一个 agent(本端点的 model 参数确实只是透传)。
|
||||
// 不谎报 GPT 之类的名字 —— 那会让用户以为能选模型,而实际不能。
|
||||
func (h *Handler) handleOpenAIModels(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@ -217,7 +217,7 @@ func TestClientsRecognizeServerEventTypes(t *testing.T) {
|
||||
mustKnow []string
|
||||
}{
|
||||
{"GUI", "../../../cmd/gui/renderer/app.js", false, sseMustKnow},
|
||||
{"鸿蒙", "../../../cmd/ohos/HomeAgent/entry/src/main/ets", true, sseMustKnow},
|
||||
{"鸿蒙", "../../../cmd/ohos/HologramAgent/entry/src/main/ets", true, sseMustKnow},
|
||||
{"CLI", "../../../cmd/waiter", true, cliMustKnow},
|
||||
}
|
||||
|
||||
@ -280,7 +280,7 @@ func TestNoPhantomFrameTypes(t *testing.T) {
|
||||
|
||||
// 客户端源码里搜幽灵字面量(排除注释行,避免判据被说明文字喂饱)。
|
||||
scan := []string{
|
||||
"../../../cmd/ohos/HomeAgent/entry/src/main/ets/common/ChatSse.ets",
|
||||
"../../../cmd/ohos/HologramAgent/entry/src/main/ets/common/ChatSse.ets",
|
||||
}
|
||||
|
||||
for _, p := range scan {
|
||||
@ -413,7 +413,7 @@ func TestDeviceBridgeOpCoverage(t *testing.T) {
|
||||
downOps := []string{"bind_ack", "cmd", "hello_ack", "push", "cmd_speech_start", "cmd_speech_end"}
|
||||
|
||||
// 客户端源码:鸿蒙端设备桥。
|
||||
const p = "../../../cmd/ohos/HomeAgent/entry/src/main/ets/common/DeviceBridge.ets"
|
||||
const p = "../../../cmd/ohos/HologramAgent/entry/src/main/ets/common/DeviceBridge.ets"
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
t.Fatalf("读 %s: %v", p, err)
|
||||
|
||||
@ -406,7 +406,7 @@ func TestHandleStaticServesHTML(t *testing.T) {
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
if !strings.Contains(w.Body.String(), "HomeAgent Dashboard") {
|
||||
if !strings.Contains(w.Body.String(), "HologramAgent Dashboard") {
|
||||
t.Error("expected dashboard HTML")
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ func TestOpenAIModelsRequiresAuth(t *testing.T) {
|
||||
// 假流式实现 → 先同步等 InjectTextSync 返回(被扣住,阻塞数秒),
|
||||
// 等不到就什么都发不出去 → 首帧迟到数秒。
|
||||
//
|
||||
// 所以断言「首帧远早于总计 700ms 的生成时间」<EFBFBD><EFBFBD>就是判据的全部。
|
||||
// 所以断言「首帧远早于总计 700ms 的生成时间」就是判据的全部。
|
||||
func TestOpenAIStreamIsActuallyStreaming(t *testing.T) {
|
||||
srv, iom, bus := newOpenAITestServer(t)
|
||||
|
||||
|
||||
@ -240,7 +240,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "username", Default: "admin", Type: "string", DisplayName: "登录用户名", Description: "Web 控制台登录用户名", Category: "webui"})
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "password", Default: "", Type: "password", DisplayName: "Web 控制台登录密码", Description: "Web 控制台登录密码", Category: "webui"})
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{Key: "session_ttl_hours", Default: "24", Type: "int", DisplayName: "会话时长(小时)", Description: "登录 cookie 有效时长", Category: "webui"})
|
||||
// ---- 通用反代(HomeAgent 自带能力)----
|
||||
// ---- 通用反代(HologramAgent 自带能力)----
|
||||
// 基域名:默认 localhost ⇒ <标签>.localhost:<端口> 开箱即用(RFC 6761 强制
|
||||
// 解析到 loopback,无需 DNS/证书/hosts)。远程访问时改成本机可达的域名,
|
||||
// 如 webui.example.com ⇒ <标签>.webui.example.com。
|
||||
|
||||
@ -20,13 +20,13 @@ import (
|
||||
)
|
||||
|
||||
// 通用反向代理:插件声明自带 HTTP 服务(plugin.json 的 proxies),
|
||||
// HomeAgent 按 **Host 子域标签** 把它们从 webui 的同一端口反代出去。
|
||||
// HologramAgent 按 **Host 子域标签** 把它们从 webui 的同一端口反代出去。
|
||||
//
|
||||
// ## 为什么是 Host 路由而不是路径前缀
|
||||
//
|
||||
// 插件自带 UI 普遍使用根绝对路径(实测 huawei_smarthome 的前端是
|
||||
// `api('/api/status')` → `fetch('/api/status')`)。若挂在 `/p/huawei/` 下,
|
||||
// 这些请求会打到 HomeAgent 自己的 `/api/status`,**静默错路由**。路径前缀方案
|
||||
// 这些请求会打到 HologramAgent 自己的 `/api/status`,**静默错路由**。路径前缀方案
|
||||
// 要么要求所有插件改前端,要么做 HTML/JS 内容重写——后者对拼进 JS 字符串的
|
||||
// 绝对路径只是"按概率能用",会产生「页面能开、某个按钮就坏」的静默故障。
|
||||
//
|
||||
@ -43,7 +43,7 @@ import (
|
||||
//
|
||||
// ## 认证
|
||||
//
|
||||
// 逐条由插件声明(ProxyAuthHomeAgent / ProxyAuthNone),默认 HomeAgent 统一保护。
|
||||
// 逐条由插件声明(ProxyAuthHomeAgent / ProxyAuthNone),默认 HologramAgent 统一保护。
|
||||
// 但 Host 路由下**子域与主门户不同源**,浏览器不会把门户的 homeagent_session
|
||||
// 发给子域——所以受保护模式下由反代层校验门户会话/API Key,校验通过后放行。
|
||||
// 详见 authorizeProxy。
|
||||
@ -589,7 +589,10 @@ func (h *Handler) serveProxyHost(w http.ResponseWriter, r *http.Request) bool {
|
||||
})
|
||||
return true
|
||||
}
|
||||
if rt.Auth == sdk.ProxyAuthHomeAgent && !h.authorizeProxy(w, r) {
|
||||
// 用 EffectiveProxyAuth 而不是直接比常量:存量 plugin.json 里可能写着
|
||||
// 旧值(ProxyAuthAgentLegacy)。直接比常量 ⇒ 旧值被判为"非门户鉴权"
|
||||
// ⇒ **鉴权被绕过**,这是本仓最不能出的错。
|
||||
if sdk.EffectiveProxyAuth(rt.Auth) == sdk.ProxyAuthAgent && !h.authorizeProxy(w, r) {
|
||||
return true
|
||||
}
|
||||
if rt.reverse == nil {
|
||||
@ -622,7 +625,7 @@ func (h *Handler) serveProxyHost(w http.ResponseWriter, r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if route.Auth == sdk.ProxyAuthHomeAgent && !h.authorizeProxy(w, r) {
|
||||
if sdk.EffectiveProxyAuth(route.Auth) == sdk.ProxyAuthAgent && !h.authorizeProxy(w, r) {
|
||||
return true // 已写 401
|
||||
}
|
||||
|
||||
@ -658,7 +661,7 @@ func (h *Handler) authorizeProxy(w http.ResponseWriter, r *http.Request) bool {
|
||||
}
|
||||
w.Header().Set("WWW-Authenticate", `Bearer realm="homeagent"`)
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{
|
||||
"error": "该服务由 HomeAgent 统一保护,需要先登录门户或用 X-API-Key 访问",
|
||||
"error": "该服务由 HologramAgent 统一保护,需要先登录门户或用 X-API-Key 访问",
|
||||
"hint": "浏览器先访问门户登录;脚本用 -H 'X-API-Key: <key>' 或 ?__token=<key>",
|
||||
})
|
||||
return false
|
||||
@ -842,7 +845,9 @@ func parseManualRoutes(text string) []proxyDecl {
|
||||
out = append(out, proxyDecl{
|
||||
Plugin: "manual", Name: "invalid", Host: "invalid",
|
||||
Target: "",
|
||||
Auth: "homeagent",
|
||||
// 非法行占位:用默认安全值(不是裸字面量 —— 契约值改名时
|
||||
// 裸字面量会被漏掉,届时这里悄悄变成"无鉴权")。
|
||||
Auth: sdk.ProxyAuthAgent,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ import (
|
||||
// 访问不到那两个公共 CDN(内网 / 出口受限 / 断网 / 被墙)。
|
||||
//
|
||||
// 为什么必须本地化而不是「换个 CDN」:
|
||||
// 换 CDN 只是把同一个赌注重下一遍。HomeAgent 明确支持离线/内网部署,
|
||||
// 换 CDN 只是把同一个赌注重下一遍。HologramAgent 明确支持离线/内网部署,
|
||||
// 前端却有 4 个硬依赖在公网上 ⇒ 断网时星图必坏、且用户无从修复。
|
||||
// marked / DOMPurify 同理(Markdown 渲染与 XSS 净化)。
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user