feat(browser): B3 截图→上下文闭环 + B4 面板声明页(Y4)
- B3:截图落盘后**自动**并入本轮视图(能收图 ⇒ 真 image 部件挂 user 消息;收不了 ⇒ vision/OCR 文字;
大图先经 image-crop 裁切;取走即消费、每会话 ≤4 张、TTL 10min、跨会话隔离)
- B4:browser 面板**声明**(配置表单 + 运行状态/指纹自检/最近截图三张只读表 + 拍一张/重新自检)
+ 六个动作走 PLG-2 的通用动作端点(**不开新路由**)+ 保存事务(不可用则回滚 409)+ 热重载
- 读面**不启动浏览器**(计数型假 dialer 证明 0 次拨号);行数据不含路径/URL/内容
- 3 个变异(短路主路/行与列 key 脱钩/保存不回滚)
⚠️ **两行接线未落**(不在我文件面):wire_runtime.go 的 TransformContext + wire_frontend.go 的
wireBrowserPanel —— 落地前 B3/B4 只是「组件就绪」,跑着的后端里不存在(Y4 已写盯梢用例)
This commit is contained in:
1 parent
4213b268fe
commit
e0a6bca501
18 files changed
+3698
-72
No files matched your search
@@ -94,6 +94,14 @@ type Manager struct {
|
||||
closed bool
|
||||
source string
|
||||
|
||||
// sessionOpenedAt 是当前会话的建立时间(面板的只读事实面用,见 state.go)。
|
||||
sessionOpenedAt time.Time
|
||||
// shots 是最近截图统计(**非内容**,最新在前,最多 maxShotRecords 条)。
|
||||
shots []ShotRecord
|
||||
// fp / fpAt 是最后一次指纹自检的读数与时间(面板显示「上次自检」用)。
|
||||
fp *Fingerprint
|
||||
fpAt time.Time
|
||||
|
||||
stopReaper chan struct{}
|
||||
reaperDone chan struct{}
|
||||
closeOnce sync.Once
|
||||
@@ -171,6 +179,9 @@ type PageResult struct {
|
||||
|
||||
// Screenshot 抓一张视口截图并落盘(0600)。超过配置上限时先降级为 JPEG 重抓,
|
||||
// 仍超限则**如实报错**,绝不交出半张坏图。
|
||||
//
|
||||
// 截图统计(字节/尺寸/格式)同时记进内存(`State().Shots`),供面板的只读事实面显示
|
||||
// 「最近拍到了什么」—— 面板不读文件、不进上下文,只看统计(state.go 口径 2)。
|
||||
func (m *Manager) Screenshot(ctx context.Context) (Shot, error) {
|
||||
var shot Shot
|
||||
err := m.withSession(ctx, func(s *session) error {
|
||||
@@ -183,6 +194,7 @@ func (m *Manager) Screenshot(ctx context.Context) (Shot, error) {
|
||||
return err
|
||||
}
|
||||
shot = saved
|
||||
m.recordShotLocked(saved, time.Now())
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -214,6 +226,9 @@ func (m *Manager) capture(ctx context.Context, s *session) ([]byte, string, erro
|
||||
|
||||
// Fingerprint 在**隔离世界**里读一次页面指纹(reference/10 §4.1 的 L2~L5 自检项),
|
||||
// 用来证明「这次跑的是有头、webdriver 关、屏幕/时区/语言与设定一致」。
|
||||
//
|
||||
// 读数会被记进内存(`State().Fingerprint`),面板因此能显示「上次自检」而不必每次都
|
||||
// 起浏览器(state.go 口径 1:面板取数无副作用,自检是一条显式动作)。
|
||||
func (m *Manager) Fingerprint(ctx context.Context) (Fingerprint, error) {
|
||||
var fp Fingerprint
|
||||
err := m.withSession(ctx, func(s *session) error {
|
||||
@@ -227,6 +242,7 @@ func (m *Manager) Fingerprint(ctx context.Context) (Fingerprint, error) {
|
||||
if err != nil {
|
||||
return Fingerprint{}, err
|
||||
}
|
||||
m.recordFingerprint(fp)
|
||||
return fp, nil
|
||||
}
|
||||
|
||||
@@ -273,6 +289,7 @@ func (m *Manager) ensureLocked(ctx context.Context) (*session, error) {
|
||||
}
|
||||
m.sess = s
|
||||
m.lastUsed = time.Now()
|
||||
m.sessionOpenedAt = m.lastUsed
|
||||
m.log.Info("browser session opened", "source", m.source, "headless", m.cfg.headless)
|
||||
return s, nil
|
||||
}
|
||||
@@ -292,6 +309,7 @@ func (m *Manager) recycleLocked(reason Reason) {
|
||||
}
|
||||
m.sess.Close()
|
||||
m.sess = nil
|
||||
m.sessionOpenedAt = time.Time{}
|
||||
m.log.Info("browser session recycled", "reason", reason, "detail", reason.Message())
|
||||
}
|
||||
|
||||
@@ -304,6 +322,7 @@ func (m *Manager) dropLocked(cause error) {
|
||||
m.log.Warn("browser session dropped after connection error", "error", cause)
|
||||
m.sess.Close()
|
||||
m.sess = nil
|
||||
m.sessionOpenedAt = time.Time{}
|
||||
}
|
||||
|
||||
// reapLoop 是空闲清扫协程:滴答间隔跟随空闲上限的一半(夹在具名上下界之间)。
|
||||
@@ -351,6 +370,7 @@ func (m *Manager) Close() error {
|
||||
if m.sess != nil {
|
||||
m.sess.Close()
|
||||
m.sess = nil
|
||||
m.sessionOpenedAt = time.Time{}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user