mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-04 00:03:59 +00:00
fix(browser): render 改走共享后端(带登录态),v2.2.1
browser_render 原实现每次独立 chromium --dump-dom 冷启动:不带共享 profile 登录态、每次 ~2s 启动开销。现改为主路径经 CDP 9222 开临时 标签页渲染(Title+OuterHTML)后即关——登录态与 interactive 会话一致; 后端不可用时保持 dump-dom failback(补 30s 超时防挂死)。 返回新增 mode 字段(backend-tab / local-dump-dom)便于 agent 判断。 修复过程中清理 python 重写残留的重复函数定义。
This commit is contained in:
@ -2,7 +2,7 @@
|
|||||||
"name": "browser",
|
"name": "browser",
|
||||||
"name_zh": "浏览器",
|
"name_zh": "浏览器",
|
||||||
"name_en": "Browser",
|
"name_en": "Browser",
|
||||||
"version": "2.2.0",
|
"version": "2.2.1",
|
||||||
"description": "统一浏览器插件:搜索、HTTP抓取(quick)、无头渲染(normal)、交互式浏览器(interactive/CDP)",
|
"description": "统一浏览器插件:搜索、HTTP抓取(quick)、无头渲染(normal)、交互式浏览器(interactive/CDP)",
|
||||||
"author": "HomeAgent",
|
"author": "HomeAgent",
|
||||||
"entry": "plugin.so",
|
"entry": "plugin.so",
|
||||||
|
|||||||
@ -707,6 +707,9 @@ func (p *Plugin) fetchWithChromium(rawURL string, maxChars int) (interface{}, er
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleRender 无头渲染 JS 页面并提取文本(normal 模式)。
|
||||||
|
// 主路径走共享浏览器后端:开临时标签页(带全机登录态)→ 渲染 → 取 text → 关标签页;
|
||||||
|
// 后端不可用时 failback 到独立 chromium --dump-dom(无登录态,仅保功能)。
|
||||||
func (p *Plugin) handleRender(args map[string]interface{}) (interface{}, error) {
|
func (p *Plugin) handleRender(args map[string]interface{}) (interface{}, error) {
|
||||||
rawURL := readArg(args, "url", "")
|
rawURL := readArg(args, "url", "")
|
||||||
if rawURL == "" {
|
if rawURL == "" {
|
||||||
@ -716,32 +719,69 @@ func (p *Plugin) handleRender(args map[string]interface{}) (interface{}, error)
|
|||||||
return errResult(err.Error()), nil
|
return errResult(err.Error()), nil
|
||||||
}
|
}
|
||||||
waitSec := int64(readArg(args, "wait", float64(0)))
|
waitSec := int64(readArg(args, "wait", float64(0)))
|
||||||
if waitSec > 0 {
|
|
||||||
time.Sleep(time.Duration(waitSec) * time.Second)
|
var title, html string
|
||||||
|
rendered := false
|
||||||
|
|
||||||
|
ok, needInstall, _ := p.ensureBackend()
|
||||||
|
if ok {
|
||||||
|
remoteCtx, remoteCancel := chromedp.NewRemoteAllocator(context.Background(), cdpEndpoint)
|
||||||
|
defer remoteCancel()
|
||||||
|
tabCtx, tabCancel := chromedp.NewContext(remoteCtx)
|
||||||
|
defer tabCancel()
|
||||||
|
actions := []chromedp.Action{
|
||||||
|
chromedp.Navigate(rawURL),
|
||||||
|
chromedp.WaitReady("body"),
|
||||||
|
}
|
||||||
|
if waitSec > 0 {
|
||||||
|
actions = append(actions, chromedp.Sleep(time.Duration(waitSec)*time.Second))
|
||||||
|
}
|
||||||
|
actions = append(actions,
|
||||||
|
chromedp.Title(&title),
|
||||||
|
chromedp.OuterHTML("html", &html),
|
||||||
|
)
|
||||||
|
// 整体限时 30s,防慢页拖死工具
|
||||||
|
rctx, rcancel := context.WithTimeout(tabCtx, 30*time.Second)
|
||||||
|
defer rcancel()
|
||||||
|
if err := chromedp.Run(rctx, actions...); err == nil {
|
||||||
|
rendered = true
|
||||||
|
} else {
|
||||||
|
log.Printf("[%s] render via backend failed (%v), fallback to dump-dom", p.name, err)
|
||||||
|
}
|
||||||
|
} else if needInstall {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"error": "browser backend not installed",
|
||||||
|
"need_install": true,
|
||||||
|
"guide": "调用 browser_install 安装共享后端;或重试本工具自动降级为独立 chromium 渲染(不带登录态)",
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
var html string
|
|
||||||
chromiumPath := "/usr/local/bin/chromium"
|
if !rendered {
|
||||||
if _, err := os.Stat(chromiumPath); err == nil {
|
chromiumPath := "/usr/local/bin/chromium"
|
||||||
|
if _, err := os.Stat(chromiumPath); err != nil {
|
||||||
|
if _, e2 := exec.LookPath("chromium"); e2 == nil {
|
||||||
|
chromiumPath = "chromium"
|
||||||
|
} else {
|
||||||
|
return errResult("no chromium available"), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd := exec.Command(chromiumPath, "--headless", "--disable-gpu", "--no-sandbox", "--dump-dom", rawURL)
|
cmd := exec.Command(chromiumPath, "--headless", "--disable-gpu", "--no-sandbox", "--dump-dom", rawURL)
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
if err := cmd.Run(); err != nil {
|
done := make(chan error, 1)
|
||||||
return errResult("chromium: " + err.Error()), nil
|
go func() { done <- cmd.Run() }()
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
if err != nil {
|
||||||
|
return errResult("chromium: " + err.Error()), nil
|
||||||
|
}
|
||||||
|
case <-time.After(30 * time.Second):
|
||||||
|
cmd.Process.Kill()
|
||||||
|
return errResult("chromium dump-dom timeout (30s)"), nil
|
||||||
}
|
}
|
||||||
html = out.String()
|
html = out.String()
|
||||||
} else {
|
|
||||||
resp, err := http.Get(rawURL)
|
|
||||||
if err != nil {
|
|
||||||
return errResult("http get: " + err.Error()), nil
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
|
||||||
html = string(body)
|
|
||||||
}
|
|
||||||
title := ""
|
|
||||||
if m := regexp.MustCompile(`<title>([^<]+)</title>`).FindStringSubmatch(html); len(m) > 1 {
|
|
||||||
title = m[1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text := htmlToText(html)
|
text := htmlToText(html)
|
||||||
origLen := len(text)
|
origLen := len(text)
|
||||||
truncated := origLen > 5000
|
truncated := origLen > 5000
|
||||||
@ -756,17 +796,13 @@ func (p *Plugin) handleRender(args map[string]interface{}) (interface{}, error)
|
|||||||
if truncated {
|
if truncated {
|
||||||
result += fmt.Sprintf("\n\n...(仅显示前 5000 字符,共 %d 字符)", origLen)
|
result += fmt.Sprintf("\n\n...(仅显示前 5000 字符,共 %d 字符)", origLen)
|
||||||
}
|
}
|
||||||
return map[string]interface{}{"content": result, "title": title}, nil
|
mode := "backend-tab"
|
||||||
|
if !rendered {
|
||||||
|
mode = "local-dump-dom"
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"content": result, "title": title, "mode": mode}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Interactive Browser Session (CDP) ─────────────────────
|
|
||||||
|
|
||||||
// ── 共享浏览器后端(systemd 托管)+ 本地 spawn failback ──────────
|
|
||||||
|
|
||||||
// cdpEndpoint 是共享 Chromium 后端的 CDP 地址(homeagent-browser.service)。
|
|
||||||
const cdpEndpoint = "http://127.0.0.1:9222"
|
|
||||||
|
|
||||||
// cdpReachable 探测浏览器后端是否在线(GET /json/version)。
|
|
||||||
func cdpReachable(endpoint string) bool {
|
func cdpReachable(endpoint string) bool {
|
||||||
client := &http.Client{Timeout: 2 * time.Second}
|
client := &http.Client{Timeout: 2 * time.Second}
|
||||||
resp, err := client.Get(endpoint + "/json/version")
|
resp, err := client.Get(endpoint + "/json/version")
|
||||||
@ -788,6 +824,9 @@ func startSystemdUnit() error {
|
|||||||
return exec.Command("systemctl", "start", "homeagent-browser.service").Run()
|
return exec.Command("systemctl", "start", "homeagent-browser.service").Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cdpEndpoint 是共享 Chromium 后端的 CDP 地址(homeagent-browser.service)。
|
||||||
|
const cdpEndpoint = "http://127.0.0.1:9222"
|
||||||
|
|
||||||
// ensureBackend 确保共享浏览器后端可用:探测 → 拉起已装服务 → 报告未装。
|
// ensureBackend 确保共享浏览器后端可用:探测 → 拉起已装服务 → 报告未装。
|
||||||
// 返回 (ok, needInstall, err)。
|
// 返回 (ok, needInstall, err)。
|
||||||
func (p *Plugin) ensureBackend() (bool, bool, error) {
|
func (p *Plugin) ensureBackend() (bool, bool, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user