mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
The previous pass aligned the observation overlay with the dsh shell; the product call is the opposite direction — the floating card should read as a BrowserSkill surface, not a shell-native widget. - Reuse @browser-skill/ui directly (Button, cn); status dots spec'd after the extension popup's ConnectionStatusIndicator; Remix icons for interrupt/pin/warn/pop-out. - Compile the BSK tailwind utility sheet scoped under the .bsk-obs root class (scripts/build-client-css.mjs + postcss-prefix-selector) and ship the oklch design tokens on the same scope, so nothing leaks into the host shell and the shell theme cannot bleed back. tsdown injects .nomodule.css verbatim (minified, unhashed) alongside the hashed CSS modules. - BSK ships no shared tooltip, so the one-time interrupt semantics hint is a card-spec bubble (bg-card/border/12px radius), retired after first use. - Tests: pin react/react-dom to this package's 18.x copies in vitest — the ui package sources peer on react ^19 and would otherwise emit react-19 (transitional) elements that the react-18 renderer rejects. Production is unaffected (react stays external, provided by the shell). - stylelint: ignore the generated bsk-ui.nomodule.css.
85 lines
3.4 KiB
TypeScript
85 lines
3.4 KiB
TypeScript
/**
|
|
* dsh-plugin-browserskill browser half: the `browser_screenshot` keyed
|
|
* toolview plus the observation overlay (live thumbnails + interrupt) floating
|
|
* over the shell via the `shell.overlay` seat.
|
|
*/
|
|
|
|
import type { ImageAttachmentRef } from "@deepseek-ai/dsh-attachment";
|
|
import type { ClientContext, ISessions, SessionId } from "@deepseek-ai/dsh-client-runtime/client";
|
|
// Type-only: pulls the 'shell.overlay' SlotMap merge into scope.
|
|
import type {} from "@deepseek-ai/dsh-client-ui-layout/client";
|
|
import type { ToolCallViewProps } from "@deepseek-ai/dsh-client-ui-tool/client";
|
|
import { createElement } from "react";
|
|
// Scope-prefixed BSK design tokens and utility sheet (injected verbatim as
|
|
// <style> tags; selectors stay unhashed so `cn(..., "bsk-obs")` roots match).
|
|
import "./bsk-tokens.nomodule.css";
|
|
import "./bsk-ui.nomodule.css";
|
|
import { ObservationOverlay } from "./ObservationOverlay";
|
|
import { type EventSourceLike, ObservationClientStore } from "./observation-store";
|
|
import { ScreenshotToolView } from "./ScreenshotToolView";
|
|
|
|
/** Required services: slots, session-scoped attachment reads, and the overlay seat. */
|
|
export const inject = ["slots", "sessions"];
|
|
|
|
/** Resolve one durable image attachment into a browser blob URL. */
|
|
async function loadSessionImage(
|
|
sessions: ISessions,
|
|
sessionId: SessionId,
|
|
attachment: ImageAttachmentRef,
|
|
): Promise<string> {
|
|
const session = sessions.binding(sessionId)?.session;
|
|
if (session === undefined) {
|
|
throw new Error(`screenshot toolview: session "${String(sessionId)}" is not bound`);
|
|
}
|
|
const result = await session.readAttachment(attachment.attachmentId);
|
|
if (!result.ok) {
|
|
throw new Error(
|
|
`screenshot toolview: readAttachment failed: ${result.error.code}: ${result.error.message}`,
|
|
);
|
|
}
|
|
const bytes = Uint8Array.from(result.value.data);
|
|
return URL.createObjectURL(
|
|
new Blob([bytes.buffer as ArrayBuffer], { type: result.value.attachment.mediaType }),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Thumbnail loader for the overlay: frames are plugin-owned runtime data
|
|
* (never referenced by a session log, so the session-authorized RPC refuses
|
|
* them), served by the host over the plugin's own route.
|
|
*/
|
|
async function overlayImageLoader(attachmentId: string): Promise<string> {
|
|
const res = await fetch(`/bsk-observation/thumbnail/${encodeURIComponent(attachmentId)}`);
|
|
if (!res.ok) throw new Error(`thumbnail fetch failed: ${res.status}`);
|
|
return URL.createObjectURL(await res.blob());
|
|
}
|
|
|
|
/**
|
|
* Client plugin body: register the keyed toolview and the observation overlay.
|
|
* @param ctx - client root context.
|
|
*/
|
|
export function apply(ctx: ClientContext): void {
|
|
const sessions = ctx.get("sessions") as unknown as ISessions;
|
|
ctx.slots.inject("tool.call.toolview", () =>
|
|
ctx.slots.register(
|
|
{ name: "tool.call.toolview", key: "browser_screenshot" },
|
|
(props: ToolCallViewProps) =>
|
|
createElement(ScreenshotToolView, {
|
|
...props,
|
|
loadImage: (attachment: ImageAttachmentRef) =>
|
|
loadSessionImage(sessions, props.sessionId, attachment),
|
|
}),
|
|
),
|
|
);
|
|
const store = new ObservationClientStore({
|
|
fetchFn: (url, init) => fetch(url, init),
|
|
eventSourceFactory: (url) => new EventSource(url) as unknown as EventSourceLike,
|
|
loadImage: overlayImageLoader,
|
|
});
|
|
ctx.slots.inject("shell.overlay", () =>
|
|
ctx.slots.register({ name: "shell.overlay", id: "bsk-observation" }, () =>
|
|
createElement(ObservationOverlay, { store }),
|
|
),
|
|
);
|
|
}
|