Merge branch 'hh2'

# Conflicts:
#	apps/server/src/modules/browser/service.ts
#	apps/web/app/components/browser/desktop-view.tsx
#	apps/web/app/components/browser/site-origin-view.tsx
#	packages/contracts/src/browser.ts
This commit is contained in:
root committed 2026-09-29 17:43:00 +08:00
commit f41cd96fee
33 files changed
+1919 -302

No files matched your search

+16 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { holderName, toViewport, waitingText } from "./coords.ts";
import { agentActive, controllerLabel, holderName, toViewport, waitingText } from "./coords.ts";
describe("原站视图坐标换算", () => {
const natural = { width: 1280, height: 800 };
@@ -29,3 +29,18 @@ describe("原站视图坐标换算", () => {
expect(holderName("admin_session:alice")).toBe("alice");
});
});
describe("当前控制文案", () => {
const now = Date.parse("2026-09-29T10:00:00Z");
it("自动化且没有最近动作 → 「自动化」;两分钟内有自动化动作 → 「Agent」", () => {
expect(controllerLabel({ controller: "automation", lastAutomationAt: null }, now)).toBe("自动化");
expect(controllerLabel({ controller: "automation" }, now)).toBe("自动化");
expect(controllerLabel({ controller: "automation", lastAutomationAt: "2026-09-29T09:50:00Z" }, now)).toBe("自动化");
expect(controllerLabel({ controller: "automation", lastAutomationAt: "2026-09-29T09:59:10Z" }, now)).toBe("Agent");
expect(agentActive({ controller: "automation", lastAutomationAt: "2026-09-29T09:59:10Z" }, now)).toBe(true);
});
it("人工接管 / 等待处理不看自动化时间", () => {
expect(controllerLabel({ controller: "human", lastAutomationAt: "2026-09-29T09:59:50Z" }, now)).toBe("人工接管");
expect(controllerLabel({ controller: "waiting" }, now)).toBe("等待处理");
});
});
+22 -1
View File
@@ -17,12 +17,33 @@ export function toViewport(
}
export const CONTROLLER_LABEL: Record<BrowserController, string> = {
automation: "Agent",
automation: "自动化",
human: "人工接管",
waiting: "等待处理",
closed: "已关闭",
};
/** 自动化动作在这段时间内发生过,算「Agent 正在操作」 */
export const AGENT_ACTIVE_MS = 2 * 60_000;
/** Agent 是否正在用这个会话:控制者是自动化,且最近 AGENT_ACTIVE_MS 内有过自动化动作 */
export function agentActive(
s: { controller: BrowserController; lastAutomationAt?: string | null },
now = Date.now(),
): boolean {
if (s.controller !== "automation" || !s.lastAutomationAt) return false;
const t = new Date(s.lastAutomationAt).getTime();
return Number.isFinite(t) && now - t < AGENT_ACTIVE_MS;
}
/** 「当前控制」文案:无人操作时是「自动化」,Agent 正在操作才显示「Agent」 */
export function controllerLabel(
s: { controller: BrowserController; lastAutomationAt?: string | null },
now = Date.now(),
): string {
return agentActive(s, now) ? "Agent" : CONTROLLER_LABEL[s.controller];
}
const WAITING_TEXT: Record<string, string> = {
captcha: "登录页需要验证码",
challenge: "页面需要人工验证",
@@ -9,7 +9,7 @@ import { StatusBadge, type StatusTone } from "~/components/console/status-badge.
import { Button } from "~/components/ui/button.tsx";
import { apiData, errorMessage } from "~/lib/api.ts";
import { qk } from "~/lib/query-keys.ts";
import { CONTROLLER_LABEL, credentialBadge, holderName, waitingText } from "./coords.ts";
import { agentActive, controllerLabel, credentialBadge, holderName, waitingText } from "./coords.ts";
import { ReconnectPanel, ReloadNotice, useAutoReconnect } from "./reconnect.tsx";
import { SiteOriginView } from "./site-origin-view.tsx";
@@ -153,7 +153,7 @@ function DesktopView({
<div className="flex flex-wrap items-center gap-2">
<StatusBadge tone={conn.tone}>{conn.text}</StatusBadge>
<StatusBadge tone={CONTROLLER_TONE[s.controller] ?? "neutral"}>
当前控制:{CONTROLLER_LABEL[s.controller]}
当前控制:{controllerLabel(s)}
{human && leaseHolder ? `(${leaseHolder})` : ""}
</StatusBadge>
{waiting ? <StatusBadge tone="warning">{waiting}</StatusBadge> : null}
@@ -217,7 +217,9 @@ function DesktopView({
<p className="text-caption text-muted-foreground">
{mine
? "直接在画面里点击和输入。中文、日文用本机输入法输入;复制粘贴用 Ctrl+C / Ctrl+V。"
: "只读观看,Agent 可能正在操作。需要自己点或输入时先接管。"}
: agentActive(s)
? "只读观看,Agent 正在操作。需要自己点或输入时先接管,Agent 会暂停。"
: "只读观看,目前没有 Agent 在操作。需要自己点或输入时先接管。"}
</p>
<div className="pg-slab relative min-w-0 overflow-hidden rounded-xl">
@@ -34,7 +34,7 @@ import { StatusBadge, type StatusTone } from "~/components/console/status-badge.
import { Button } from "~/components/ui/button.tsx";
import { Input } from "~/components/ui/input.tsx";
import { errorMessage } from "~/lib/api.ts";
import { CONTROLLER_LABEL, credentialBadge, holderName, toViewport, waitingText } from "./coords.ts";
import { controllerLabel, credentialBadge, holderName, toViewport, waitingText } from "./coords.ts";
import { isBrowserGone, ReconnectPanel, ReloadNotice, useAutoReconnect } from "./reconnect.tsx";
// 原站视图(REQ §4.7.1、§4.7.3):同一个 camofox 会话的定时截图 + 人工输入映射。
@@ -156,7 +156,7 @@ export function SiteOriginView({ site }: { site: SiteListItem }) {
<div className="flex flex-wrap items-center gap-2">
<StatusBadge tone={conn.tone}>{conn.text}</StatusBadge>
<StatusBadge tone={CONTROLLER_TONE[s.controller] ?? "neutral"}>
当前控制:{CONTROLLER_LABEL[s.controller]}
当前控制:{controllerLabel(s)}
{human && leaseHolder ? `(${leaseHolder})` : ""}
</StatusBadge>
{waiting ? <StatusBadge tone="warning">{waiting}</StatusBadge> : null}