dsh-seework 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["css","apply","css","ops: Parameters<SeeWorkScope['mutateOps']>[0]","parts: string[]","models: ModelConfig[]","css","seen: string[]","ASSUMED_STAGE: { readonly width: number; readonly height: number }","ANNOTATION_COLORS: readonly string[]","ANNOTATION_WIDTHS: readonly number[]","minX","minY","hits: number[]","css","CROP_ASPECTS: readonly CropAspectOption[]","CROP_HANDLES: readonly CropHandle[]","HANDLE_AXES: Record<CropHandle, { x: -1 | 0 | 1; y: -1 | 0 | 1 }>","HANDLE_CURSORS: Record<CropHandle, string>","css","conversationProbe: (() => ComposerDrafts | undefined) | undefined","face: ComposerFace | undefined","face","service","placement: FloatingBarPlacement","css","cryptoObj: Crypto | undefined","next: CanvasDocument","css","removeHint","css","activeSurface: SeeWorkSurface | undefined","launcherCss","css","root: Root","LAUNCHER_TARGETS: readonly LauncherTarget[]","controls: LauncherControls | undefined","css","css","launcherCss","root: Root","handle","newestId: string | undefined","api: SeeWorkApi","value: LibraryListResult","EMPTY_FILTER: LibraryFilter","face: CanvasAddFace | undefined","sleep","image","css","state: LibraryState","css","launcherCss","root: Root","fetchFn: typeof fetch","bits: Record<string, boolean>","css","lightbox: { host: HTMLElement; root: Root } | undefined","out: ToolCardImage[]","css","disposers: Array<() => void>","css","TAB_TARGETS: readonly SeeWorkTabTarget[]","disposers: Array<() => void>","scope: SeeWorkScope","disposeSlot: (() => void) | undefined","tabs: { dispose: () => void; tabsAvailable: boolean } | undefined","controls: LauncherControls","dispose: (() => void) | undefined","face: SettingsCardFace","face"],"sources":["../src/protocol.ts","../src/client/api.ts","../src/client/controls.tsx","../src/client/VersionRow.tsx","../src/client/SettingsCard.tsx","../src/canvas-limits.ts","../src/canvas-viewport.ts","../src/canvas-placement.ts","../src/client/annotations.ts","../src/client/burn-image.ts","../src/client/AnnotationEditor.tsx","../src/client/crop-rect.ts","../src/client/CropOverlay.tsx","../src/client/composer-draft.ts","../src/client/floating-bar.ts","../src/client/NodeFloatingBar.tsx","../src/client/snapshot-store.ts","../src/client/canvas-store.ts","../src/client/CanvasBoard.tsx","../src/client/surfaces.ts","../src/client/settings-panel.tsx","../src/client/header-launchers.tsx","../src/client/canvas-panel.tsx","../src/client/generation-watch.ts","../src/client/library-store.ts","../src/client/canvas-add.ts","../src/client/library-detail.tsx","../src/client/LibraryPanel.tsx","../src/client/library-panel.tsx","../src/client/settings-scope.ts","../src/client/chat-images.tsx","../src/client/tool-card.tsx","../src/client/LibraryTab.tsx","../src/client/CanvasTab.tsx","../src/client/sidebar-tabs.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Wire contract shared by the host and browser halves of dsh-seework: the\n * settings namespace, the route paths, the SeeAI Hub gateway request/response\n * shapes, and the local material-library record shape.\n *\n * Pure types + constants — safe for the client bundle to inline.\n */\n\n/** Settings namespace this plugin owns (host settings seam + bridge). */\nexport const SEEWORK_SETTINGS_NAMESPACE = 'dsh-seework'\n\n/**\n * Aspect ratios the gateway serves uniformly (#658/D-16).\n *\n * One list for every model, in the order a user should see them — the catalog's\n * own arrays are lexicographically sorted sets, so they are not a usable order\n * for a picker.\n */\nexport const UNIFIED_ASPECT_RATIOS = ['1:1', '4:3', '3:4', '16:9', '9:16', '3:2', '2:3', '21:9'] as const\n\n/** Factory default for the ratio setting (#658/D-13/D-16). */\nexport const DEFAULT_ASPECT_RATIO = '3:4'\n\n/** Factory default for the output-format setting (#658/D-13). */\nexport const DEFAULT_OUTPUT_FORMAT = 'png'\n\n/** Formats to offer when no saved model declares any. */\nexport const OUTPUT_FORMAT_FALLBACKS = ['png', 'jpeg', 'webp'] as const\n\n/**\n * Whether a list contains a value, ignoring case (#658).\n *\n * The one membership rule both halves need: the request layer asks whether the\n * saved model can take the configured output format, and the settings card asks\n * whether the stored value is already among the offered options.\n */\nexport function includesIgnoringCase(list: readonly string[], wanted: string): boolean {\n return list.some(value => value.toLowerCase() === wanted.toLowerCase())\n}\n\n/**\n * Default SeeAI Hub Gateway base URL — the OpenAI-compatible root, i.e. the\n * part that `/images/generations` is appended to. Local development gateway is\n * the default so a fresh install talks to the working stack; point it at a\n * deployment domain in the settings card.\n */\nexport const DEFAULT_API_URL = 'http://127.0.0.1:8080/v1'\n\n/**\n * Default SeeAI Hub **service** base URL, used only for the model catalog.\n *\n * `docs/api/README.md` is explicit that the two surfaces are separate and that\n * neither answers the other's paths: generation is the Gateway (`/v1/*`), while\n * `GET /api/v1/catalog/models` is the service. Deriving one from the other\n * works only behind a reverse proxy that mounts both under one host, which is\n * why the catalog address is its own setting.\n */\nexport const DEFAULT_SERVICE_URL = 'http://127.0.0.1:8081'\n\n/**\n * Most images this plugin asks for in one request.\n *\n * **This is the plugin's own ceiling, not a contract value.**\n * `docs/api/images.md` documents `n` as a positive integer whose ceiling is the\n * *model's* own maximum — published since catalog v2 as the `n` descriptor's\n * `max` — so the per-model limit comes from the catalog, while this constant is\n * the plugin's own guard against a runaway `n` turning into a runaway bill.\n *\n * It is stated once because several places have to agree and each used to spell\n * `10` itself. Every consumer now goes through the two compositions below rather\n * than clamping on its own: the normalizer (`settings.ts`), the outgoing body\n * (`engine.ts`), the tool argument (`agent-tools.ts`) and the promise the\n * announcement makes (`model-summary.ts`), which must never exceed what is\n * actually sent.\n *\n * The two compositions of it live here too ({@link imageCountCeiling} and\n * {@link effectiveImageCount}), because spelling `min(10, max(1, …))` at each\n * call site is how the task record and the outgoing body drifted apart (#668).\n */\nexport const MAX_IMAGES_PER_REQUEST = 10\n\n/**\n * The most images one request may ask for, given a model's declared ceiling.\n *\n * A **declared** ceiling below one is read as one image — {@link ModelConfig.maxImages}\n * is \"1 when unknown\" — while a declared ceiling above the wire ceiling is cut\n * back to it. Omitting `modelCap` means \"no model is known at this call site\"\n * (the send-time guard), so only the wire ceiling applies.\n *\n * @param modelCap - the model's declared ceiling, when the catalog declared one.\n * @returns the ceiling this model's requests are held to.\n */\nexport function imageCountCeiling(modelCap?: number): number {\n const declared = typeof modelCap === 'number' && Number.isFinite(modelCap)\n ? Math.trunc(modelCap)\n : MAX_IMAGES_PER_REQUEST\n return Math.min(MAX_IMAGES_PER_REQUEST, Math.max(1, declared))\n}\n\n/**\n * The image count a request actually carries: what was asked for, clamped into\n * the model's own ceiling.\n *\n * Every caller that records or sends a count uses this, so the task, the library\n * entry and the outgoing body cannot disagree.\n *\n * @param requested - the caller's `n`.\n * @param modelCap - the model's declared ceiling, when the catalog declared one.\n * @returns the count that goes on the wire.\n */\nexport function effectiveImageCount(requested: number, modelCap?: number): number {\n const asked = Number.isFinite(requested) ? Math.trunc(requested) : 1\n return Math.min(imageCountCeiling(modelCap), Math.max(1, asked))\n}\n\n/** Same-origin, loopback-only settings bridge for this plugin's namespace. */\nexport const SETTINGS_API = {\n describe: '/api/dsh-seework/settings/describe',\n mutate: '/api/dsh-seework/settings/mutate',\n /**\n * Folder choosing for the material directory, over the host's\n * `ctx.directoryPicker` seam: `directoryPicker` reports how the host can choose\n * (opening nothing), `pickDirectory` opens one chooser and waits.\n */\n directoryPicker: '/api/dsh-seework/settings/directory-picker',\n pickDirectory: '/api/dsh-seework/settings/pick-directory',\n} as const\n\n/**\n * How the host can choose a directory, mirroring the DSH seam's capabilities.\n *\n * `native` opens one OS chooser on the host's screen; `browse` only serves listing\n * primitives for the shell's own in-app browser (a remote client cannot reach the\n * host's display); `none` means no picker is composed at all.\n */\nexport type DirectoryPickerKind = 'native' | 'browse' | 'none'\n\n/** What the host reports about choosing a directory. */\nexport interface DirectoryPickerStatus {\n kind: DirectoryPickerKind\n /** Operator-facing note for anything other than `native`. */\n message?: string\n}\n\n/** What one pick attempt produced, as the route reports it. */\nexport interface PickDirectoryResult {\n /** The chosen absolute directory; absent when the operator cancelled. */\n path?: string\n cancelled?: boolean\n}\n\n/** SeeAI Hub model-catalog discovery (host-mediated, keeps no secrets). */\nexport const CATALOG_API = {\n models: '/api/dsh-seework/catalog/models',\n /**\n * Run one automatic detection round and report what changed (#652).\n *\n * The card calls this when it opens; the host throttles it against the\n * low-frequency background timer, so opening the card twice does not re-request.\n */\n refresh: '/api/dsh-seework/catalog/refresh',\n} as const\n\n/** The image-generation proxy route: the browser submits, the host calls upstream. */\nexport const GENERATE_API = '/api/dsh-seework/generate' as const\n\n/** Host-resident generation queue (browser side of the shared runtime). */\nexport const TASK_API = {\n list: '/api/dsh-seework/tasks/list',\n cancel: '/api/dsh-seework/tasks/cancel',\n} as const\n\n/** Update discovery for a published install, and the install it triggers. */\nexport const UPDATE_API = {\n /** Read the running version, the registry's newest, and how this copy got here. */\n status: '/api/dsh-seework/update/status',\n /**\n * Start installing the newest published version.\n *\n * Answers as soon as the install has been *started*, not when it finishes: the\n * install re-composes the profile and tears this plugin's routes down, so a\n * handler that waited for it could not deliver its own response.\n */\n apply: '/api/dsh-seework/update/apply',\n} as const\n\n/** How this copy of the plugin reached the profile. */\nexport type InstallKind = 'registry' | 'local' | 'unknown'\n\n/** What the settings card renders in its version row. */\nexport interface UpdateStatus {\n /** The version running right now. */\n current: string\n /** The newest version the registry offers, when the check could read one. */\n latest?: string\n /** How this copy was installed; only `registry` is offered an update button. */\n kind: InstallKind\n /** Whether `latest` is strictly newer than `current`. */\n updateAvailable: boolean\n /** Why no answer was available, when there was none. */\n error?: string\n}\n\n/** What the update route reports once it has started an install. */\nexport interface UpdateStart {\n /** Whether an install was actually started. */\n started: boolean\n /** The version being installed. */\n to?: string\n /** Why nothing was started. */\n error?: string\n}\n\n/**\n * The local material library — every image this plugin generates is written\n * under the data root and indexed, which is what the sidebar library and the\n * canvas read back.\n */\nexport const LIBRARY_API = {\n list: '/api/dsh-seework/library/list',\n /** Cheap identity poll: the newest entry id and the total. */\n head: '/api/dsh-seework/library/head',\n remove: '/api/dsh-seework/library/remove',\n clear: '/api/dsh-seework/library/clear',\n /** Prefix route: `/api/dsh-seework/library/image/<file>`. */\n image: '/api/dsh-seework/library/image',\n} as const\n\n/**\n * The library's identity without its contents: what a browser polls to notice a\n * generation that finished in the host (agent tools write entries the page is\n * never told about).\n */\nexport interface LibraryHead {\n total: number\n /** Newest entry id, absent while the library is empty. */\n newestId?: string\n /** Newest entry's creation time, absent while the library is empty. */\n newestAt?: number\n}\n\n/**\n * One durable image an Agent tool result points at.\n *\n * A tool result is not model-visible content, so the shell's own image loader\n * refuses to resolve it (`session.readAttachment` proves reachability from the\n * session log). The conversation card therefore asks this route with the\n * complete reference the host persisted in the tool result's presentation\n * metadata, and the host re-validates every field before reading the store.\n *\n * Prefix route: `/api/dsh-seework/attachment/image?attachment_id=…&media_type=…`.\n */\nexport const ATTACHMENT_API = {\n image: '/api/dsh-seework/attachment/image',\n} as const\n\n/** Image media types the attachment store accepts, as a runtime list. */\nexport const IMAGE_MEDIA_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/gif'] as const\n\n/** One of {@link IMAGE_MEDIA_TYPES}. */\nexport type ImageMedia = (typeof IMAGE_MEDIA_TYPES)[number]\n\n/**\n * Whether a wire value is an image media type this plugin may read and serve.\n * @param value - the candidate media type.\n * @returns true when it is one of the accepted image types.\n */\nexport function isImageMedia(value: string): value is ImageMedia {\n return (IMAGE_MEDIA_TYPES as readonly string[]).includes(value)\n}\n\n/** Data root the library lives under (overridable in settings). */\nexport const DEFAULT_DATA_DIR_NAME = 'dsh-seework'\n\n/**\n * Canvas documents live under `<data root>/canvas/`. The board is spatial, not\n * a node graph: every card is just a placed rectangle, and the only structure\n * is what the user sees (see `docs/architecture.md`).\n */\nexport const CANVAS_API = {\n list: '/api/dsh-seework/canvas/list',\n create: '/api/dsh-seework/canvas/create',\n read: '/api/dsh-seework/canvas/read',\n save: '/api/dsh-seework/canvas/save',\n remove: '/api/dsh-seework/canvas/remove',\n /**\n * Canvas-owned pictures (a burned annotation, a crop): the board writes one\n * with POST here, and reads it back from `<asset>/<file>`.\n *\n * They deliberately never enter the material library — the library records what\n * was generated, and a marked-up copy is the board's own material.\n */\n asset: '/api/dsh-seework/canvas/asset',\n /**\n * Housekeeping for those pictures: `assets` enumerates them (and says how much\n * of them no card shows any more), `pruneAssets` deletes exactly those, and\n * `removeAsset` deletes one — refused while a card still shows it.\n *\n * `assets` reads naturally as a *collection* path, but it is registered\n * separately from the `asset` prefix route, which matches `asset/<file>` only.\n */\n assets: '/api/dsh-seework/canvas/assets',\n pruneAssets: '/api/dsh-seework/canvas/assets/prune',\n removeAsset: '/api/dsh-seework/canvas/asset/remove',\n} as const\n\n/** One canvas-owned picture on disk. */\nexport interface CanvasAssetSummary {\n file: string\n bytes: number\n /** Whether a card on some board still shows it. */\n referenced: boolean\n}\n\n/** What the canvas housekeeping route reports. */\nexport interface CanvasAssetListing {\n files: CanvasAssetSummary[]\n /** Pictures no card references — the ones 「清理无用图片」 deletes. */\n orphans: number\n orphanBytes: number\n}\n\n/** One canvas-owned image, as the write route answers. */\nexport interface CanvasImageRef {\n file: string\n url: string\n mime: string\n width?: number\n height?: number\n}\n\n/** Where an image card's file lives: the material library, or the board's own assets. */\nexport type CanvasCardSource = 'library' | 'canvas'\n\n/**\n * Where an image card's picture came from.\n *\n * `chat` and `panel` mirror the material library's own `source` (the library\n * records whether a generation was asked for in a conversation or from the\n * panel); `annotation` and `crop` are composites this board made itself. Cards\n * written before this field existed have none, and the viewer infers one.\n */\nexport type CanvasCardOrigin = 'chat' | 'panel' | 'annotation' | 'crop'\n\n/**\n * Human label for one origin, for the card badge.\n * @param origin - the card's origin, when it has one.\n * @returns the badge text, or undefined when there is nothing honest to show.\n */\nexport function canvasOriginLabel(origin: CanvasCardOrigin | undefined): string | undefined {\n if (origin === 'chat') return '对话生成'\n if (origin === 'panel') return '面板生成'\n if (origin === 'annotation') return '标注合成'\n if (origin === 'crop') return '裁剪合成'\n return undefined\n}\n\n/**\n * The URL that serves one image card's picture.\n * @param file - the file name (library entry image, or canvas asset).\n * @param source - which store it belongs to; absent means the material library.\n * @returns the same-origin route URL.\n */\nexport function canvasImageUrl(file: string, source: CanvasCardSource = 'library'): string {\n return source === 'canvas'\n ? `${CANVAS_API.asset}/${file}`\n : `${LIBRARY_API.image}/${file}`\n}\n\n/** Canvas viewport: translation in board units plus the zoom factor. */\nexport interface CanvasViewport {\n x: number\n y: number\n /** Zoom factor, clamped to [CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX]. */\n k: number\n}\n\n/** The kinds of card a board can hold. */\nexport type CanvasCardKind = 'image' | 'text'\n\n/** One card placed on a board. */\nexport interface CanvasCard {\n id: string\n kind: CanvasCardKind\n /** Board coordinates of the card's top-left corner. */\n x: number\n y: number\n /** Board size in pixels. */\n width: number\n height: number\n /** Paint order; higher is on top. */\n z: number\n /** Image cards: the material-library file name (`library/image/<file>`). */\n file?: string\n /**\n * Image cards: which store `file` belongs to. Absent means the material library,\n * so every board written before canvas assets existed keeps working.\n */\n source?: CanvasCardSource\n /**\n * Image cards: where the picture came from (conversation generation, panel\n * generation, or this board's own annotation/crop). Shown as the card badge, and\n * what makes a board's own pictures distinguishable from library material.\n */\n origin?: CanvasCardOrigin\n /** Image cards: a snapshot of what it came from, for the card caption. */\n model?: string\n prompt?: string\n /** Text cards: the note body. */\n text?: string\n /** Text cards: font size in board pixels. */\n fontSize?: number\n}\n\n/** One board document. */\nexport interface CanvasDocument {\n id: string\n title: string\n /** Increments on every accepted save; the save route fences on it. */\n revision: number\n viewport: CanvasViewport\n cards: CanvasCard[]\n createdAt: number\n updatedAt: number\n}\n\n/** One board in the canvas list. */\nexport interface CanvasSummary {\n id: string\n title: string\n revision: number\n cardCount: number\n createdAt: number\n updatedAt: number\n}\n\n/** Canvas list payload. */\nexport interface CanvasListResult {\n canvases: CanvasSummary[]\n dataRoot: string\n}\n\n/** Generation modes: text-to-image, or image-to-image with reference images. */\nexport type GenerateMode = 'text' | 'edit'\n\n/** One model in the local catalog (what the user selected from the gateway). */\nexport interface ModelConfig {\n /** Model name sent as `model` to the gateway. */\n id: string\n /** Optional display name; empty means \"same as id\". */\n label?: string\n /**\n * Accepted `resolution` tiers, e.g. ['1K', '2K']. Empty means the model has\n * no tier vocabulary and requests omit `resolution` entirely.\n *\n * The array is **ordered, lowest first**: since catalog v2 an enum\n * descriptor's `values` order *is* the family's declared order, so this list\n * doubles as the ranking the announcement and the agent read.\n */\n resolutions: string[]\n /**\n * The tier a request uses when the caller names none: the model's own\n * declared default (`supported_parameters.resolution.default`).\n *\n * Deliberately **not** the same thing as `resolutions[0]`: a model may\n * declare a default that is not its lowest tier. Empty means the contract\n * declared none — a breach of the \"`resolution.default` is always published\"\n * invariant — and the plugin then omits `resolution` rather than guessing a\n * tier (it reports the missing default in the announcement instead).\n */\n resolutionDefault?: string\n /** Accepted `aspect_ratio` values, e.g. ['16:9', '1:1'] (declared order). */\n aspectRatios: string[]\n /** Accepted `output_format` values. */\n outputFormats: string[]\n /** Maximum images per request (1 when unknown). */\n maxImages: number\n /** Maximum reference images per request (0 = text-to-image only). */\n maxReferenceImages: number\n /**\n * Whether these capabilities came from the SeeAI Hub catalog at all.\n *\n * This decides what an *empty* list means. `true` = \"the catalog described\n * this model and listed nothing for that field, so the model does not take it\n * — omit it\". `false` = \"we know nothing — the entry carried no\n * `supported_parameters` at all (a bare OpenAI-compatible `/models` list, or\n * a catalog snapshot published before v2) — pass the user's value through\".\n * Getting this backwards either breaks bare gateways or turns defaults into\n * 400s.\n */\n capabilitiesKnown?: boolean\n /**\n * When this model's capabilities were last read from the catalog (epoch ms).\n *\n * Written on a **successful** discovery and kept in the saved settings, so a\n * surface that dates its data can say how fresh it is: a failed refresh leaves\n * the previous models (and this stamp) untouched, so an old value is the honest\n * signal that the snapshot may be stale. **Absent means unknown** — a settings\n * document written before this field existed — and there is deliberately no\n * sentinel value for that state.\n */\n discoveredAt?: number\n}\n\n/**\n * A generation request from the browser or an agent tool. The host resolves\n * the credentials, maps this onto the SeeAI Hub field vocabulary, and persists\n * the result into the library.\n */\nexport interface GenerateRequest {\n mode: GenerateMode\n /** Model id from the configured catalog. */\n model: string\n /** The prompt; SeeAI Hub validates length per model. */\n prompt: string\n /** Resolution tier (e.g. '2K'), 'auto', or '' to omit. */\n resolution: string\n /** Aspect ratio (e.g. '16:9'), 'auto', or '' to omit. */\n aspectRatio: string\n /** Output format: 'png' | 'jpeg' | 'webp', or '' to omit (gateway default). */\n outputFormat: string\n /**\n * Number of images; a positive integer. `docs/api/images.md` leaves the\n * ceiling to the model's catalog entry, and this plugin caps it at\n * {@link MAX_IMAGES_PER_REQUEST} on the way out.\n */\n n: number\n /** Reference images as data URLs (edit mode only). */\n imageUrls: string[]\n /** Original reference-image names, kept for the library record. */\n refNames?: string[]\n}\n\n/** Why one parameter never reached the gateway. */\nexport type DropReason =\n /** The catalog did not list the field for this model (or listed no fields at all). */\n | 'unsupported_field'\n /** The field is accepted by this model, but not with that value. */\n | 'invalid_value'\n\n/** One parameter left out of a request, with the reason it was left out. */\nexport interface DroppedParameter {\n field: string\n value: string\n /** The values the model does accept (empty = \"the model declares none\"). */\n allowed: string[]\n /** Machine-readable reason, so callers need not parse the message. */\n reason: DropReason\n}\n\n/** One generated image, normalized host-side to base64. */\nexport interface GeneratedImage {\n /** Raw base64 payload (no data: prefix). */\n b64: string\n /** MIME type of the payload, e.g. image/png. */\n mime: string\n /** Upstream revised prompt, when the provider returns one. */\n revisedPrompt?: string\n}\n\n/** Successful generation outcome. */\nexport interface GenerateResult {\n images: GeneratedImage[]\n /** Amount charged by the gateway for this request (CNY), when reported. */\n cost?: number\n}\n\n/** One image reference inside a library entry (a served URL, never base64). */\nexport interface LibraryImageRef {\n /** Same-origin URL: `${LIBRARY_API.image}/<file>`. */\n url: string\n /** Stored file name (what `remove` and the canvas address). */\n file: string\n mime: string\n /** Intrinsic size, when known. */\n width?: number\n height?: number\n revisedPrompt?: string\n}\n\n/** One library entry: a single generation and everything it produced. */\nexport interface LibraryEntry {\n id: string\n createdAt: number\n mode: GenerateMode\n model: string\n prompt: string\n resolution: string\n aspectRatio: string\n /**\n * Quality is **no longer a plugin parameter** (#661): every generation writes\n * an empty string here. The field stays because entries written before that\n * change carry a real value and the library must still read them back.\n */\n quality: string\n outputFormat: string\n n: number\n images: LibraryImageRef[]\n /** Amount charged by the gateway (CNY), when reported. */\n cost?: number\n /** Reference-image names (edit mode), for display only. */\n refNames?: string[]\n /** Where the generation came from. */\n source: 'agent' | 'panel' | 'canvas'\n /** Session the agent call belonged to, when applicable. */\n sessionId?: string\n /** Free-form user labels (phase 2 filtering). */\n tags?: string[]\n}\n\n/** Library list payload. */\nexport interface LibraryListResult {\n entries: LibraryEntry[]\n total: number\n /** On-disk image count across all entries (storage usage, sidebar footer). */\n imageCount: number\n /** Absolute data root the library is written to. */\n dataRoot: string\n}\n\n/** Generation task states. */\nexport type GenerationTaskStatus = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled'\n\n/** One host-resident generation task (shared by the panel and agent tools). */\nexport interface GenerationTask {\n id: string\n request: GenerateRequest\n status: GenerationTaskStatus\n createdAt: number\n startedAt?: number\n finishedAt?: number\n result?: GenerateResult\n /** The library entry written for this task, once it completed. */\n entryId?: string\n error?: string\n /** What asked for this task. */\n source: 'agent' | 'panel' | 'canvas'\n /** Owning agent session, when an agent asked for it. */\n sessionId?: string\n /**\n * Parameters left out because the selected model does not declare them.\n * Reported rather than hidden: a silently downgraded request should be\n * visible to whoever asked for it.\n */\n droppedParameters?: DroppedParameter[]\n}\n\n/**\n * One source the catalog discovery tried, with what it answered.\n */\nexport interface CatalogAttempt {\n url: string\n /** `ok` / `http <status>` / `unreachable` / `unusable`. */\n outcome: string\n /** Image models this source yielded (0 when it did not answer usefully). */\n models: number\n}\n\n/**\n * Catalog discovery result shown by the settings card.\n *\n * `models` is the **same shape the settings document stores** (#669): discovery\n * used to answer with a second, nearly identical type (`CatalogModel`) that every\n * consumer projected by hand, which is how one field addition became four\n * coordinated edits.\n */\nexport interface CatalogResult {\n models: ModelConfig[]\n /** Which kind of source answered: the SeeAI Hub catalog, or `/models`. */\n origin: 'catalog' | 'models'\n /** The URL that produced the returned list. */\n catalogUrl: string\n /** Total models the upstream reported before image filtering. */\n scanned: number\n /** Every source that was tried, in order — the \"why is it empty\" evidence. */\n attempts: CatalogAttempt[]\n}\n\n/**\n * What one automatic detection round changed (#652).\n *\n * The three id lists are the whole of the product decision this feature makes:\n * capabilities of a saved model are adopted (`adopted`), while a model the user\n * has not saved (`added`) and one the catalog stopped describing (`missing`) are\n * only reported and left for the user to decide.\n */\nexport interface CatalogRefreshSummary {\n /** When the catalog was successfully read (epoch ms). */\n refreshedAt: number\n /** Saved models whose catalog description changed and was adopted. */\n adopted: string[]\n /** Catalog models the user has not saved — reported, never adopted. */\n added: string[]\n /** Saved models the catalog no longer describes — reported, never removed. */\n missing: string[]\n}\n\n/** The result of asking for an automatic round (`catalog/refresh`). */\nexport interface CatalogRefreshOutcome {\n /** False when a throttle, a missing configuration, or a failure stopped it. */\n ran: boolean\n /** Why it did not run: `throttled` | `not-configured` | `failed`. */\n skipped?: 'throttled' | 'not-configured' | 'failed'\n /** Present once a round succeeded (also on the last success after a failure). */\n result?: CatalogRefreshSummary\n /** The catalog answer, so the card can stage models the user has not saved. */\n catalog?: CatalogResult\n}\n","/**\n * Browser-side API client for the plugin's own routes. Every call goes to the\n * same origin and the host half talks to SeeAI Hub, so the API key never\n * reaches the page.\n */\n\nimport {\n CANVAS_API,\n CATALOG_API,\n GENERATE_API,\n LIBRARY_API,\n SETTINGS_API,\n TASK_API,\n UPDATE_API,\n type CanvasAssetListing,\n type CanvasDocument,\n type CanvasImageRef,\n type CanvasListResult,\n type CanvasSummary,\n type CatalogResult,\n type CatalogRefreshOutcome,\n type DirectoryPickerStatus,\n type GenerateRequest,\n type GenerationTask,\n type LibraryEntry,\n type LibraryHead,\n type LibraryListResult,\n type PickDirectoryResult,\n type UpdateStart,\n type UpdateStatus,\n} from '../protocol.ts'\n\n/** One decoded bridge/route response. */\ntype Envelope<T> = { ok: true; value: T } | { ok: false; code: string; message: string }\n\n/** POST one JSON request and decode the envelope. */\nasync function postJson<T>(path: string, body: unknown): Promise<Envelope<T>> {\n try {\n const response = await fetch(path, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n const payload = await response.json() as Envelope<T>\n if (typeof payload === 'object' && payload !== null && 'ok' in payload) return payload\n return { ok: false, code: 'internal', message: `接口返回了未知形状(HTTP ${response.status})` }\n } catch {\n return { ok: false, code: 'internal', message: '接口不可达' }\n }\n}\n\n/** The generation and library routes the panel/canvas consume. */\nexport class SeeWorkApi {\n /** Discover the deployment's image models (never persists anything). */\n catalog(input: { apiUrl?: string; serviceUrl?: string; apiKey?: string } = {}): Promise<Envelope<CatalogResult>> {\n return postJson<CatalogResult>(CATALOG_API.models, input)\n }\n\n /**\n * Run one automatic detection round and adopt refreshed capabilities (#652).\n *\n * Called when the card opens. The host throttles it against its own background\n * timer, so reopening the card does not re-request; an unconfigured plugin\n * answers `skipped: 'not-configured'` without touching the network.\n *\n * @returns what the round changed — the catalog answer is included so the card\n * can stage models the user has not saved yet.\n */\n catalogRefresh(): Promise<Envelope<CatalogRefreshOutcome>> {\n return postJson<CatalogRefreshOutcome>(CATALOG_API.refresh, {})\n }\n\n /** Submit one generation and wait for its task to settle. */\n generate(request: GenerateRequest & { waitForCompletion?: boolean }): Promise<Envelope<{ task: GenerationTask }>> {\n return postJson<{ task: GenerationTask }>(GENERATE_API, request)\n }\n\n /** Every retained task, newest first. */\n tasks(): Promise<Envelope<{ tasks: GenerationTask[] }>> {\n return postJson<{ tasks: GenerationTask[] }>(TASK_API.list, {})\n }\n\n /** Cancel a queued or running task. */\n cancel(taskId: string): Promise<Envelope<{ task: GenerationTask }>> {\n return postJson<{ task: GenerationTask }>(TASK_API.cancel, { taskId })\n }\n\n /** The material library, newest first. */\n library(): Promise<Envelope<LibraryListResult>> {\n return postJson<LibraryListResult>(LIBRARY_API.list, {})\n }\n\n /**\n * The library's identity only — what the page polls to notice a generation\n * that finished in the host.\n */\n libraryHead(): Promise<Envelope<LibraryHead>> {\n return postJson<LibraryHead>(LIBRARY_API.head, {})\n }\n\n /** Remove one library entry and its files. */\n removeEntry(id: string): Promise<Envelope<{ entries: LibraryEntry[] }>> {\n return postJson<{ entries: LibraryEntry[] }>(LIBRARY_API.remove, { id })\n }\n\n /** Remove every library entry. */\n clearLibrary(): Promise<Envelope<{ entries: LibraryEntry[] }>> {\n return postJson<{ entries: LibraryEntry[] }>(LIBRARY_API.clear, {})\n }\n\n /** Every stored board, newest-updated first. */\n canvases(): Promise<Envelope<CanvasListResult>> {\n return postJson<CanvasListResult>(CANVAS_API.list, {})\n }\n\n /** Create a fresh board. */\n createCanvas(title?: string): Promise<Envelope<{ canvas: CanvasDocument }>> {\n return postJson<{ canvas: CanvasDocument }>(CANVAS_API.create, title === undefined ? {} : { title })\n }\n\n /** Read one board. */\n readCanvas(id: string): Promise<Envelope<{ canvas: CanvasDocument }>> {\n return postJson<{ canvas: CanvasDocument }>(CANVAS_API.read, { id })\n }\n\n /** Save a board, fenced by the revision it was read at. */\n saveCanvas(canvas: CanvasDocument, expectedRevision: number): Promise<Envelope<{ canvas: CanvasDocument }>> {\n return postJson<{ canvas: CanvasDocument }>(CANVAS_API.save, { canvas, expectedRevision })\n }\n\n /** Delete a board. */\n removeCanvas(id: string): Promise<Envelope<{ canvases: CanvasSummary[] }>> {\n return postJson<{ canvases: CanvasSummary[] }>(CANVAS_API.remove, { id })\n }\n\n /**\n * Ask how the host can choose a folder (opens nothing).\n *\n * `native` means one OS chooser on the host's screen; `browse` means the shell's\n * own in-app browser serves listings instead, and `none` means there is no picker\n * at all — in both latter cases the card offers no button.\n */\n directoryPicker(): Promise<Envelope<DirectoryPickerStatus>> {\n return postJson<DirectoryPickerStatus>(SETTINGS_API.directoryPicker, {})\n }\n\n /**\n * Open the host's folder chooser and wait for the operator.\n * @returns the chosen absolute directory, or `cancelled: true`.\n */\n pickDirectory(): Promise<Envelope<PickDirectoryResult>> {\n return postJson<PickDirectoryResult>(SETTINGS_API.pickDirectory, {})\n }\n\n /**\n * Store one canvas-owned picture (a burned annotation or a crop).\n *\n * It never enters the material library: the library records what was generated,\n * and a marked-up copy belongs to the board that produced it.\n *\n * @param dataUrl - the image as a PNG data URL.\n * @returns the stored image's file name and URL, or an error envelope.\n */\n writeCanvasAsset(dataUrl: string): Promise<Envelope<{ image: CanvasImageRef }>> {\n return postJson<{ image: CanvasImageRef }>(CANVAS_API.asset, { dataUrl })\n }\n\n /**\n * Enumerate the board's own pictures and how much of them is unreferenced.\n * @returns each file with its size and whether a card still shows it.\n */\n canvasAssets(): Promise<Envelope<CanvasAssetListing>> {\n return postJson<CanvasAssetListing>(CANVAS_API.assets, {})\n }\n\n /** Delete every board picture no card shows any more. */\n pruneCanvasAssets(): Promise<Envelope<{ removed: number; bytes: number }>> {\n return postJson<{ removed: number; bytes: number }>(CANVAS_API.pruneAssets, {})\n }\n\n /**\n * Delete one board picture.\n *\n * Refused while a card still shows it, so removing a card and dropping its file\n * has to happen in that order (and a second board's copy is never pulled away).\n *\n * @param file - the picture's file name.\n */\n removeCanvasAsset(file: string): Promise<Envelope<{ removed: number; bytes: number }>> {\n return postJson<{ removed: number; bytes: number }>(CANVAS_API.removeAsset, { file })\n }\n\n /**\n * Read the running version, the registry's newest, and how this copy got here.\n *\n * Never fails on a host without a plugin manager or with an unreachable\n * registry: the answer carries an `error` and the card renders it.\n */\n updateStatus(): Promise<Envelope<UpdateStatus>> {\n return postJson<UpdateStatus>(UPDATE_API.status, {})\n }\n\n /**\n * Start installing the newest published version.\n *\n * Answers as soon as the install has been **started**, not when it finishes:\n * applying it re-composes the profile and unloads this plugin, so there is\n * nothing left to answer a later outcome. The caller polls `updateStatus`\n * until the version changes.\n */\n applyUpdate(): Promise<Envelope<UpdateStart>> {\n return postJson<UpdateStart>(UPDATE_API.apply, {})\n }\n}\n","/**\n * Local form controls for the SeeWork settings card.\n *\n * The shell's `dsh-client-ui-primitives` package cannot be a runtime dependency\n * of this plugin: it is not in the shared module baseline, and bundling it drags\n * its whole syntax-highlighting dependency tree into this bundle. These four\n * controls carry the same interaction contract and are styled with the shell's\n * `--dsw-*` design tokens, so they follow the active theme.\n */\n\nimport type { ButtonHTMLAttributes, InputHTMLAttributes, ReactNode } from 'react'\nimport css from './controls.module.css'\n\n/** Render a button.\n * @param props.variant - visual family; `primary` is the confirming action.\n */\nexport function Button({ variant = 'ghost', className, children, ...rest }: {\n variant?: 'primary' | 'ghost' | 'outline'\n className?: string | undefined\n children?: ReactNode\n} & ButtonHTMLAttributes<HTMLButtonElement>): JSX.Element {\n const variantClass = variant === 'primary' ? css.buttonPrimary : variant === 'outline' ? css.buttonOutline : ''\n return (\n <button\n type=\"button\"\n className={[css.button, variantClass, className].filter(Boolean).join(' ')}\n {...rest}\n >\n {children}\n </button>\n )\n}\n\n/** Render a text input. */\nexport function TextInput({ className, ...rest }: { className?: string | undefined } & InputHTMLAttributes<HTMLInputElement>): JSX.Element {\n return <input className={[css.input, className].filter(Boolean).join(' ')} {...rest} />\n}\n\n/** Render a toggle switch.\n * @param props.label - accessible name, rendered beside the control.\n */\nexport function Toggle({ checked, onChange, label, title, disabled }: {\n checked: boolean\n onChange: (next: boolean) => void\n label: string\n title?: string | undefined\n disabled?: boolean\n}): JSX.Element {\n return (\n <label className={css.toggle} title={title}>\n <input\n type=\"checkbox\"\n role=\"switch\"\n checked={checked}\n disabled={disabled}\n onChange={event => { onChange(event.target.checked) }}\n />\n <span>{label}</span>\n </label>\n )\n}\n\n/** Render a small status pill. */\nexport function Pill({ children }: { children: ReactNode }): JSX.Element {\n return <span className={css.pill}>{children}</span>\n}\n\n/**\n * Copy text to the clipboard, preferring the async Clipboard API and falling\n * back to a hidden textarea (older browsers and non-secure origins).\n * @param text - the text to place on the clipboard.\n * @returns settlement after a copy attempt succeeded.\n */\nexport async function copyText(text: string): Promise<void> {\n if (navigator.clipboard?.writeText !== undefined) {\n await navigator.clipboard.writeText(text)\n return\n }\n const area = document.createElement('textarea')\n area.value = text\n area.setAttribute('readonly', '')\n area.style.position = 'fixed'\n area.style.opacity = '0'\n document.body.appendChild(area)\n try {\n area.select()\n if (!document.execCommand('copy')) throw new Error('copy refused')\n } finally {\n area.remove()\n }\n}\n","/**\n * The version row: what is running, what the registry offers, and the button\n * that installs it.\n *\n * Starting an update unloads this plugin — the profile is re-composed and these\n * routes go away with it — so the request that asked for the install is the last\n * one the old code serves. The row therefore never waits for an outcome: it\n * polls the status route until the running version changes, and reads a failed\n * poll as \"still working\" rather than as an error, because that failure is\n * exactly what a host in the middle of a reload looks like.\n *\n * The page itself is still running the bundle it booted with, so the last word\n * is always \"refresh\": the host is on the new version the moment the install\n * lands, the tab is not.\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { UpdateStatus } from '../protocol.ts'\nimport type { SeeWorkApi } from './api.ts'\nimport { Button } from './controls.tsx'\nimport css from './settings-card.module.css'\n\n/** How often the row asks again while an install is running. */\nconst POLL_MS = 2000\n\n/** How many polls before the row stops waiting and asks for a page refresh. */\nconst MAX_POLLS = 45\n\n/** Wait one interval. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => { setTimeout(resolve, ms) })\n}\n\n/** What the row is doing right now. */\ntype Phase = 'checking' | 'idle' | 'updating' | 'done'\n\n/**\n * Render the settings card's version section.\n * @param props.api - the plugin route client.\n */\nexport function VersionRow({ api }: { api: SeeWorkApi }): JSX.Element {\n const [status, setStatus] = useState<UpdateStatus | undefined>(undefined)\n const [phase, setPhase] = useState<Phase>('checking')\n const [note, setNote] = useState<string | undefined>(undefined)\n\n const check = useCallback(async (): Promise<void> => {\n setPhase('checking')\n setNote(undefined)\n const answer = await api.updateStatus()\n if (answer.ok) {\n setStatus(answer.value)\n setNote(answer.value.error)\n } else {\n setNote(answer.message)\n }\n setPhase('idle')\n }, [api])\n\n useEffect(() => { void check() }, [check])\n\n const apply = useCallback(async (): Promise<void> => {\n setPhase('updating')\n setNote(undefined)\n const started = await api.applyUpdate()\n if (!started.ok) {\n setNote(started.message)\n setPhase('idle')\n return\n }\n const from = status?.current\n for (let attempt = 0; attempt < MAX_POLLS; attempt++) {\n await sleep(POLL_MS)\n const answer = await api.updateStatus()\n // A failed poll is the reload in progress, not a failure.\n if (!answer.ok) continue\n if (answer.value.current !== from) {\n setStatus(answer.value)\n setPhase('done')\n return\n }\n }\n // Either the host came back on the same version or it never came back; both\n // leave the tab running a bundle older than the host, so refreshing is the\n // next step either way.\n setPhase('done')\n }, [api, status?.current])\n\n const local = status?.kind === 'local'\n\n return (\n <>\n <div className={css.field}>\n <span className={css.label}>当前版本</span>\n <div className={css.dirRow}>\n <span className={css.fixedValue} data-dsh-seework-version=\"\" title={status?.current}>\n {status?.current ?? '…'}\n </span>\n <Button\n variant=\"outline\"\n disabled={phase === 'checking' || phase === 'updating'}\n onClick={() => { void check() }}\n >\n {phase === 'checking' ? '检查中…' : '检查更新'}\n </Button>\n </div>\n </div>\n\n {local\n ? (\n <p className={css.hint} data-dsh-seework-version-kind=\"local\">\n 这是本地目录安装,更新走 <code>pnpm build &amp;&amp; pnpm sync</code>,这里不提供更新。\n </p>\n )\n : phase === 'updating'\n ? <p className={css.hint}>正在更新:宿主重载插件期间页面可能短暂失联,属正常。</p>\n : phase === 'done'\n ? (\n <p className={css.ok} data-dsh-seework-version-state=\"done\">\n 更新已开始。请<strong>刷新页面</strong>(Ctrl+R)让新版本生效。\n </p>\n )\n : status?.updateAvailable === true && status.latest !== undefined\n ? (\n <div className={css.row}>\n <Button variant=\"primary\" onClick={() => { void apply() }}>\n 更新到 {status.latest}\n </Button>\n </div>\n )\n : status?.latest !== undefined\n ? <p className={css.muted}>已是最新版本(注册表上是 {status.latest})。</p>\n : null}\n\n {note !== undefined ? <p className={css.warn}>{note}</p> : null}\n </>\n )\n}\n","/**\n * The SeeWork settings card (Settings → Plugins → SeeWork).\n *\n * Edits the SeeAI Hub connection (API address + user API key), the image-model\n * catalog, and generation defaults. The local data root is shown but **not\n * editable**: it is the plugin's own storage location, and nobody should have to\n * type a filesystem path into a form (user report: 「素材目录不应该手动填写地址」).\n * Changing it stays possible through the plugin's settings document.\n *\n * The address and key are staged rather than written on every keystroke:\n * \"检测可用模型\" probes exactly what is on screen, and \"保存连接\" persists it.\n * That matters because the API key is a `role('secret')` field — the wire never\n * returns the stored value, so an accidental overwrite could not be undone.\n */\n\nimport { DEFAULT_ASPECT_RATIO, DEFAULT_OUTPUT_FORMAT, OUTPUT_FORMAT_FALLBACKS, UNIFIED_ASPECT_RATIOS, includesIgnoringCase, type DirectoryPickerStatus, type ModelConfig } from '../protocol.ts'\nimport { Button, Pill, TextInput, Toggle } from './controls.tsx'\nimport { VersionRow } from './VersionRow.tsx'\nimport { useCallback, useEffect, useState } from 'react'\nimport { SeeWorkApi } from './api.ts'\nimport type { SeeWorkConfig, SeeWorkScope } from './settings-scope.ts'\nimport css from './settings-card.module.css'\n\n/** What the card controller hands its component. */\nexport interface SettingsCardFace {\n scope: SeeWorkScope\n api: SeeWorkApi\n}\n\n/** Persisted catalog entries, normalized for display. */\nfunction selectedModels(config: SeeWorkConfig | undefined): ModelConfig[] {\n return Array.isArray(config?.models) ? config.models : []\n}\n\n/** Re-render on every scope or secret change. */\nfunction useScopeSnapshot(scope: SeeWorkScope): { config: SeeWorkConfig | undefined; status: string; keySet: boolean } {\n const [tick, setTick] = useState(0)\n useEffect(() => {\n const bump = (): void => { setTick(value => value + 1) }\n const stops = [scope.subscribe(bump), scope.subscribeSecrets(bump)]\n return () => { for (const stop of stops) stop() }\n }, [scope])\n void tick\n const snapshot = scope.getSnapshot()\n return { config: snapshot.value, status: snapshot.status, keySet: scope.getSecretSet('apiKey') }\n}\n\n/** The settings card. */\nexport function SeeWorkSettingsCard({ scope, api }: SettingsCardFace): JSX.Element {\n const { config, status, keySet } = useScopeSnapshot(scope)\n const [draftApiUrl, setDraftApiUrl] = useState<string | undefined>(undefined)\n const [draftServiceUrl, setDraftServiceUrl] = useState<string | undefined>(undefined)\n const [draftApiKey, setDraftApiKey] = useState('')\n const [candidates, setCandidates] = useState<ModelConfig[] | undefined>(undefined)\n /**\n * Whether the staged rows came from the automatic round (#652).\n *\n * The model section seeds its ticks differently for the two sources: a manual\n * detection stages exactly the saved choices, while the automatic round also\n * pre-ticks the models the catalog gained, so adding one is a single\n * 「保存模型」. It is a prop rather than section state because only the parent\n * knows which round produced the rows.\n */\n const [stagedFromAuto, setStagedFromAuto] = useState(false)\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<{ tone: 'ok' | 'warn' | 'error'; text: string } | undefined>(undefined)\n /** The data root the host actually resolved, for the read-only display. */\n const [resolvedRoot, setResolvedRoot] = useState('')\n /** How the host can choose a folder (asking opens nothing). */\n const [picker, setPicker] = useState<DirectoryPickerStatus | undefined>(undefined)\n\n const apiUrl = draftApiUrl ?? config?.apiUrl ?? ''\n const serviceUrl = draftServiceUrl ?? config?.serviceUrl ?? ''\n const persisted = selectedModels(config)\n /** Whether the user moved the data root away from the default. */\n const customRoot = (config?.dataDir ?? '').trim() !== ''\n\n /**\n * Ask the host where the library really lives.\n *\n * The configured value may be empty (the default) or relative to DSH_HOME, and\n * only the host knows — so the card shows the host's answer instead of guessing.\n */\n const refreshRoot = useCallback(async (): Promise<void> => {\n const result = await api.library()\n if (result.ok) setResolvedRoot(result.value.dataRoot)\n }, [api])\n\n useEffect(() => {\n let live = true\n // Where the data lives, and whether this host can open a folder chooser.\n void api.library().then(result => {\n if (live && result.ok) setResolvedRoot(result.value.dataRoot)\n }).catch(() => {})\n void api.directoryPicker().then(result => {\n if (live && result.ok) setPicker(result.value)\n }).catch(() => {})\n return () => { live = false }\n }, [api])\n\n /** Open the host's folder chooser and store what it returns. */\n const chooseDirectory = useCallback(async (): Promise<void> => {\n setBusy(true)\n setNotice(undefined)\n const result = await api.pickDirectory()\n if (!result.ok) {\n setBusy(false)\n setNotice({ tone: 'warn', text: result.message })\n return\n }\n // Cancelling the dialog is not an error and needs no message.\n if (result.value.path === undefined) {\n setBusy(false)\n return\n }\n const path = result.value.path\n await scope.set('dataDir', path)\n await scope.load()\n await refreshRoot()\n setBusy(false)\n setNotice({ tone: 'ok', text: `素材目录已改为 ${path}(旧目录里的文件没有被删)。` })\n }, [api, refreshRoot, scope])\n\n /** Go back to the plugin's own default location. */\n const restoreDefaultDirectory = useCallback(async (): Promise<void> => {\n setBusy(true)\n setNotice(undefined)\n await scope.unset('dataDir')\n await scope.load()\n await refreshRoot()\n setBusy(false)\n setNotice({ tone: 'ok', text: '素材目录已恢复默认(旧目录里的文件没有被删)。' })\n }, [refreshRoot, scope])\n\n /** Write one scalar field and re-read the section. */\n const write = useCallback((field: string, value: unknown): void => {\n void scope.set(field, value).then(() => scope.load())\n }, [scope])\n\n /** Persist the staged connection fields. */\n const saveConnection = useCallback(async (): Promise<void> => {\n setBusy(true)\n const ops: Parameters<SeeWorkScope['mutateOps']>[0] = []\n if (apiUrl.trim() !== (config?.apiUrl ?? '')) ops.push({ op: 'set', path: ['apiUrl'], value: apiUrl.trim() })\n if (serviceUrl.trim() !== (config?.serviceUrl ?? '')) ops.push({ op: 'set', path: ['serviceUrl'], value: serviceUrl.trim() })\n if (draftApiKey.trim() !== '') ops.push({ op: 'set', path: ['apiKey'], value: draftApiKey.trim() })\n if (ops.length === 0) {\n setBusy(false)\n setNotice({ tone: 'warn', text: '没有需要保存的改动。' })\n return\n }\n await scope.mutateOps(ops)\n await scope.load()\n setDraftApiUrl(undefined)\n setDraftServiceUrl(undefined)\n setDraftApiKey('')\n setBusy(false)\n setNotice({ tone: 'ok', text: '连接设置已保存。' })\n }, [apiUrl, config?.apiUrl, config?.serviceUrl, draftApiKey, scope, serviceUrl])\n\n /**\n * The automatic detection round that runs when the card opens (#652).\n *\n * It keeps the saved snapshot fresh without the user doing anything: the host\n * adopts refreshed capabilities itself (that never changes the selection), and\n * this only surfaces what it *found* — models the user has not saved are staged\n * as ticked rows, so adding them is one 「保存模型」 rather than a re-detect.\n *\n * Deliberately silent on everything else: no configuration yet, a throttled\n * round, an unreachable service, or a plugin with nothing saved all resolve to\n * \"do nothing here\". The card's own 「检测可用模型」 remains the explicit path\n * that reports failures.\n */\n useEffect(() => {\n if (status !== 'ready') return\n let live = true\n void api.catalogRefresh().then(result => {\n if (!live || !result.ok || !result.value.ran) return\n const summary = result.value.result\n const catalog = result.value.catalog\n if (summary === undefined || catalog === undefined) return\n void scope.load()\n const parts: string[] = []\n if (summary.added.length > 0) {\n setStagedFromAuto(true)\n setCandidates(catalog.models)\n parts.push(`目录里有 ${summary.added.length} 个还没加入的模型,已勾选,点「保存模型」即可加入`)\n }\n if (summary.missing.length > 0) {\n parts.push(`有 ${summary.missing.length} 个已保存模型在目录里找不到了(快照保留着,是否移除由你决定)`)\n }\n if (summary.adopted.length > 0) parts.push(`已刷新 ${summary.adopted.length} 个已保存模型的能力`)\n if (parts.length > 0) setNotice({ tone: 'warn', text: `${parts.join(';')}。` })\n }).catch(() => {})\n return () => { live = false }\n // `status` gates the first run; `scope` and `api` are stable for the card's life.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status, api, scope])\n\n /** Probe the catalog with the on-screen addresses and key. */\n const detect = useCallback(async (): Promise<void> => {\n setBusy(true)\n setNotice(undefined)\n setStagedFromAuto(false)\n const result = await api.catalog({ apiUrl: apiUrl.trim(), serviceUrl: serviceUrl.trim(), apiKey: draftApiKey.trim() })\n setBusy(false)\n if (!result.ok) {\n setCandidates(undefined)\n // The host reports every address it tried; show them so a wrong address\n // is a five-second fix rather than a mystery.\n setNotice({ tone: 'error', text: `检测失败:${result.message}` })\n return\n }\n setCandidates(result.value.models)\n const tried = result.value.attempts\n .map(attempt => `${attempt.url}(${attempt.outcome})`)\n .join(';')\n if (result.value.models.length === 0) {\n setNotice({\n tone: 'warn',\n text: `扫到 ${result.value.scanned} 个模型,其中没有图片模型。来源:${tried}`,\n })\n return\n }\n setNotice({\n tone: 'ok',\n text: `发现 ${result.value.models.length} 个图片模型(共扫描 ${result.value.scanned} 个),来自 ${result.value.catalogUrl}。勾选后点「保存模型」。`,\n })\n }, [api, apiUrl, draftApiKey, serviceUrl])\n\n /** Persist one catalog selection (a whole-array write, secrets untouched). */\n const saveModels = useCallback(async (selection: ModelConfig[]): Promise<void> => {\n setBusy(true)\n // Discovery already answers with the stored shape (#669), so the selection is\n // written as it stands: the field list and the rules that make a degenerate\n // value safe live in `capability.ts` (the schema, the read-back and the\n // ceiling), not here. The discovery time travels with it too — a dated surface\n // reports it, and a failed re-detection leaves the old value (and its honest\n // \"may be stale\" reading) in place.\n const models: ModelConfig[] = selection\n // One atomic write: `models` and `defaultModel` must not be able to land\n // apart (a revision conflict between two writes would leave them disagreeing).\n const ops: Parameters<SeeWorkScope['mutateOps']>[0] = [{ op: 'set', path: ['models'], value: models }]\n if (models.length > 0 && (config?.defaultModel ?? '') === '') {\n ops.push({ op: 'set', path: ['defaultModel'], value: models[0]!.id })\n }\n await scope.mutateOps(ops)\n await scope.load()\n setBusy(false)\n setNotice({ tone: 'ok', text: `已保存 ${models.length} 个模型。` })\n }, [config?.defaultModel, scope])\n\n if (status === 'loading') {\n return <div className={css.card}><p className={css.muted}>正在读取设置…</p></div>\n }\n if (status === 'unavailable') {\n return (\n <div className={css.card}>\n <p className={css.error}>\n 设置接口不可达。请确认浏览器打开的就是本机 DSH 宿主(该接口只监听本机回环地址)。\n </p>\n </div>\n )\n }\n\n return (\n <div className={css.card}>\n <section className={css.section}>\n <h3 className={css.sectionTitle}>SeeAI Hub 连接</h3>\n <label className={css.field}>\n <span className={css.label}>API 地址</span>\n <TextInput\n value={apiUrl}\n placeholder=\"http://127.0.0.1:8080/v1\"\n spellCheck={false}\n onChange={event => { setDraftApiUrl(event.target.value) }}\n />\n </label>\n <p className={css.hint}>\n 生成接口的地址,填到 <code>/v1</code> 为止。开发环境默认 <code>http://127.0.0.1:8080/v1</code>。\n </p>\n <label className={css.field}>\n <span className={css.label}>模型目录地址</span>\n <TextInput\n value={serviceUrl}\n placeholder=\"http://127.0.0.1:8081\"\n spellCheck={false}\n onChange={event => { setDraftServiceUrl(event.target.value) }}\n />\n </label>\n <p className={css.hint}>\n SeeAI Hub 的「模型目录」在 service 上(默认 <code>127.0.0.1:8081</code>),不在生成网关上;\n 两个地址各管各的。如果你的部署把两者挂在同一个域名下,填同一个域名即可——插件两个地址都会试,并把结果告诉你。\n </p>\n <label className={css.field}>\n <span className={css.label}>\n 用户 API Key\n {keySet ? <Pill>已保存</Pill> : null}\n </span>\n <TextInput\n value={draftApiKey}\n type=\"password\"\n autoComplete=\"off\"\n spellCheck={false}\n placeholder={keySet ? '留空表示不修改已保存的 Key' : 'sk-…'}\n onChange={event => { setDraftApiKey(event.target.value) }}\n />\n </label>\n <p className={css.hint}>\n 在 SeeAI Hub 后台「API Keys」里创建的设备 Key。密钥只写在本机设置文档里,不会存到浏览器。\n </p>\n <div className={css.row}>\n <Button variant=\"outline\" disabled={busy} onClick={() => { void detect() }}>\n {busy ? '处理中…' : '检测可用模型'}\n </Button>\n <Button variant=\"primary\" disabled={busy} onClick={() => { void saveConnection() }}>\n 保存连接\n </Button>\n </div>\n </section>\n\n <ModelSection\n persisted={persisted}\n candidates={candidates}\n stagedFromAuto={stagedFromAuto}\n busy={busy}\n onSave={selection => { void saveModels(selection) }}\n />\n\n <section className={css.section}>\n <h3 className={css.sectionTitle}>生成默认值</h3>\n <DefaultsFields config={config} models={persisted} onWrite={write} />\n </section>\n\n <section className={css.section}>\n <h3 className={css.sectionTitle}>本地与行为</h3>\n <div className={css.field}>\n <span className={css.label}>素材目录</span>\n <div className={css.dirRow}>\n <span className={css.fixedValue} data-dsh-seework-datadir=\"\" title={resolvedRoot}>\n {resolvedRoot !== '' ? resolvedRoot : '~/.dsh/dsh-seework(默认)'}\n </span>\n {picker?.kind === 'native'\n ? (\n <Button\n disabled={busy}\n data-dsh-seework-pick-dir=\"\"\n title=\"打开系统的文件夹选择窗口\"\n onClick={() => { void chooseDirectory() }}\n >\n 选择目录…\n </Button>\n )\n : null}\n {customRoot\n ? (\n <Button\n disabled={busy}\n data-dsh-seework-reset-dir=\"\"\n title=\"回到插件自己的默认位置\"\n onClick={() => { void restoreDefaultDirectory() }}\n >\n 恢复默认\n </Button>\n )\n : null}\n </div>\n </div>\n <p className={css.hint}>\n {picker === undefined || picker.kind === 'native'\n ? '对话里生成的图片、画布上的合成图都存在这里;换目录只是换个地方读写,旧目录里的文件不会被删。'\n : picker.message}\n </p>\n <BehaviorSwitches config={config} onWrite={write} />\n <div className={css.row}>\n <Button variant=\"primary\" disabled={busy} onClick={() => { void saveConnection() }}>\n 保存\n </Button>\n </div>\n </section>\n\n <section className={css.section}>\n <h3 className={css.sectionTitle}>版本</h3>\n <VersionRow api={api} />\n </section>\n\n {notice !== undefined\n ? <p className={notice.tone === 'ok' ? css.ok : notice.tone === 'warn' ? css.warn : css.error}>{notice.text}</p>\n : null}\n </div>\n )\n}\n\n/** The model catalog section: detection results plus the persisted selection. */\nfunction ModelSection({\n persisted,\n candidates,\n stagedFromAuto,\n busy,\n onSave,\n}: {\n persisted: ModelConfig[]\n candidates: ModelConfig[] | undefined\n /**\n * True when `candidates` came from the automatic round, whose rows pre-tick the\n * models the catalog gained; a manual detection stages the saved choices as-is.\n */\n stagedFromAuto: boolean\n busy: boolean\n onSave: (selection: ModelConfig[]) => void\n}): JSX.Element {\n // Until a detection runs, the table shows what is already saved; afterwards a\n // detection result the user has not accepted yet is what they see. Discovery\n // answers with the stored shape since #669, so a row is a model — only the\n // display label needs filling in.\n const rows = (candidates ?? persisted).map(model => ({ ...model, label: model.label ?? '' }))\n\n const [selected, setSelected] = useState<string[] | undefined>(undefined)\n // The staged selection is seeded when a detection result arrives, and only\n // then: re-seeding on every settings write would throw away boxes the user just\n // ticked (they type a default, the section re-reads, the ticks vanish).\n const persistedIds = persisted.map(model => model.id).join('\\n')\n useEffect(() => {\n if (candidates === undefined) {\n setSelected(undefined)\n return\n }\n const stored = persistedIds === '' ? [] : persistedIds.split('\\n')\n // A manual detection stages the saved choices as they are; the automatic\n // round additionally pre-ticks what the catalog gained (#652), so adding a\n // model is one 「保存模型」 per new row rather than a hunt for it.\n setSelected(stagedFromAuto\n ? [...new Set([...stored, ...candidates.map(model => model.id).filter(id => !stored.includes(id))])]\n : stored.filter(id => candidates.some(model => model.id === id)))\n // eslint-disable-next-line react-hooks/exhaustive-deps -- persistedIds and the source flag are read, not triggers\n }, [candidates])\n\n const checked = new Set(selected ?? persisted.map(model => model.id))\n const pending = rows.filter(model => checked.has(model.id))\n\n return (\n <section className={css.section}>\n <h3 className={css.sectionTitle}>图片模型</h3>\n <p className={css.hint}>\n 只勾选你的网关确实支持生图的模型。没有目录信息的网关按「仅文生图、参数由上游决定」处理。\n </p>\n {rows.length === 0\n ? <p className={css.muted}>还没有模型:先填好地址与 Key,点「检测可用模型」。</p>\n : (\n <div className={css.modelList}>\n {rows.map(model => (\n <label key={model.id} className={css.modelRow}>\n <input\n type=\"checkbox\"\n checked={checked.has(model.id)}\n onChange={event => {\n const next = new Set(checked)\n if (event.target.checked) next.add(model.id)\n else next.delete(model.id)\n setSelected(rows.filter(candidate => next.has(candidate.id)).map(candidate => candidate.id))\n }}\n />\n <span className={css.modelMain}>\n <span className={css.modelName}>{model.label !== '' ? model.label : model.id}</span>\n {model.label !== '' && model.label !== model.id ? <code className={css.modelId}>{model.id}</code> : null}\n <span className={css.modelCapability}>\n {[\n model.resolutions.length > 0 ? model.resolutions.join(' / ') : '档位由上游决定',\n model.aspectRatios.length > 0 ? `${model.aspectRatios.length} 种比例` : '比例由上游决定',\n model.maxReferenceImages > 0 ? `可带参考图 ≤${model.maxReferenceImages}` : '仅文生图',\n ].filter(Boolean).join(' · ')}\n </span>\n </span>\n </label>\n ))}\n </div>\n )}\n <div className={css.row}>\n <Button\n variant=\"primary\"\n \n disabled={busy || rows.length === 0}\n onClick={() => { onSave(pending) }}\n >\n 保存模型\n </Button>\n {selected !== undefined\n ? <Button variant=\"ghost\" onClick={() => { setSelected([]) }}>全不选</Button>\n : null}\n </div>\n </section>\n )\n}\n\n/**\n * Output formats to offer: what the saved models declare, or the three we know\n * (#658/D-16); the stored value is kept selectable so the control never shows\n * something other than what is stored.\n */\nfunction outputFormatOptions(models: ModelConfig[], current: string): string[] {\n const seen: string[] = []\n for (const model of models) {\n for (const format of model.outputFormats ?? []) {\n if (!includesIgnoringCase(seen, format)) seen.push(format)\n }\n }\n return withCurrent(seen.length > 0 ? seen : [...OUTPUT_FORMAT_FALLBACKS], current)\n}\n\n/** The gateway-wide ratios (#658/D-16), keeping a stored value that is not one. */\nfunction aspectRatioOptions(current: string): string[] {\n return withCurrent([...UNIFIED_ASPECT_RATIOS], current)\n}\n\n/** Keep a stored value selectable even when the list does not offer it (old documents). */\nfunction withCurrent(options: string[], current: string): string[] {\n const wanted = current.trim()\n if (wanted !== '' && !includesIgnoringCase(options, wanted)) options.push(wanted)\n return options\n}\n\n/** Generation defaults; each control writes its own scalar field immediately. */\nfunction DefaultsFields({\n config,\n models,\n onWrite,\n}: {\n config: SeeWorkConfig | undefined\n models: ModelConfig[]\n onWrite: (field: string, value: unknown) => void\n}): JSX.Element {\n // The two defaulted values a user may override here. Resolution is deliberately\n // absent: it is derived per model (#658/D-17), and \"images per request\" is gone\n // too (#658/D-15) — `n` defaults to 1 in code.\n const ratio = config?.defaultAspectRatio?.trim() || DEFAULT_ASPECT_RATIO\n const format = config?.outputFormat?.trim() || DEFAULT_OUTPUT_FORMAT\n return (\n <div className={css.grid}>\n <label className={css.field}>\n <span className={css.label}>默认模型</span>\n <select\n className={css.select}\n value={config?.defaultModel ?? ''}\n onChange={event => { onWrite('defaultModel', event.target.value) }}\n >\n <option value=\"\">(目录里的第一个)</option>\n {models.map(model => (\n <option key={model.id} value={model.id}>{model.label !== undefined && model.label !== '' ? model.label : model.id}</option>\n ))}\n </select>\n </label>\n <label className={css.field}>\n <span className={css.label}>默认比例</span>\n <select\n className={css.select}\n value={ratio}\n onChange={event => { onWrite('defaultAspectRatio', event.target.value) }}\n >\n {aspectRatioOptions(ratio).map(option => <option key={option} value={option}>{option}</option>)}\n </select>\n </label>\n <label className={css.field}>\n <span className={css.label}>输出格式</span>\n <select\n className={css.select}\n value={format}\n onChange={event => { onWrite('outputFormat', event.target.value) }}\n >\n {outputFormatOptions(models, format).map(option => <option key={option} value={option}>{option}</option>)}\n </select>\n </label>\n </div>\n )\n}\n\n/** Plugin switches. */\nfunction BehaviorSwitches({\n config,\n onWrite,\n}: {\n config: SeeWorkConfig | undefined\n onWrite: (field: string, value: unknown) => void\n}): JSX.Element {\n return (\n <div className={css.switches}>\n <Toggle\n checked={config?.enabled ?? true}\n label=\"启用插件\"\n onChange={next => { onWrite('enabled', next) }}\n />\n <Toggle\n checked={config?.allowAgentGeneration ?? true}\n label=\"允许 Agent 生图\"\n onChange={next => { onWrite('allowAgentGeneration', next) }}\n />\n <Toggle\n checked={config?.announceToAgent ?? true}\n label=\"把插件与模型告知 Agent\"\n onChange={next => { onWrite('announceToAgent', next) }}\n />\n </div>\n )\n}\n","/**\n * Canvas limits shared by both halves.\n *\n * These live in their own module — with no imports at all — because the client\n * bundle and the host store must agree on them, while the host store is full of\n * Node built-ins. Importing a constant from `canvas-store.ts` into browser code\n * drags `node:crypto` and friends into the bundle, where the module loader\n * cannot resolve them and the whole client half fails to load. Keeping the\n * constants dependency-free is what makes the shared use safe.\n */\n\n/** Zoom range for a board. */\nexport const CANVAS_ZOOM_MIN = 0.1\nexport const CANVAS_ZOOM_MAX = 5\n\n/** Cards allowed on one board. */\nexport const CANVAS_MAX_CARDS = 500\n\n/** Boards kept before the oldest is dropped (with its file). */\nexport const CANVAS_MAX_BOARDS = 200\n\n/** Default size of a card placed on a board, in board pixels. */\nexport const CANVAS_NEW_IMAGE_SIZE = 320\n","/**\n * Canvas viewport math (pure functions, shared by the board and its tests).\n *\n * The board is spatial, not a node graph: the viewport is an ordinary\n * translate+scale pair, so every question about it (\"what is under the cursor\",\n * \"where does a card land if I click here\") is one linear map. Keeping that map\n * in one module means the pointer handling never re-derives it.\n *\n * Coordinate convention: a board card at `(x, y)` renders at\n * `(x * k + vx, y * k + vy)` in screen pixels.\n */\n\nimport type { CanvasCard, CanvasViewport } from './protocol.ts'\nimport { CANVAS_ZOOM_MAX, CANVAS_ZOOM_MIN } from './canvas-limits.ts'\n\n/** Zoom applied per wheel notch and per toolbar button press. */\nexport const ZOOM_STEP = 1.25\n\n/** Padding kept around the content by {@link fitViewport}. */\nexport const FIT_PADDING = 48\n\n/** Clamp a zoom factor into the supported range. */\nexport function clampZoom(k: number): number {\n if (!Number.isFinite(k)) return 1\n return Math.round(Math.min(CANVAS_ZOOM_MAX, Math.max(CANVAS_ZOOM_MIN, k)) * 1000) / 1000\n}\n\n/** Convert a screen point to board coordinates. */\nexport function screenToBoard(viewport: CanvasViewport, screenX: number, screenY: number): { x: number; y: number } {\n return {\n x: (screenX - viewport.x) / viewport.k,\n y: (screenY - viewport.y) / viewport.k,\n }\n}\n\n/** Convert a board point to screen coordinates. */\nexport function boardToScreen(viewport: CanvasViewport, boardX: number, boardY: number): { x: number; y: number } {\n return {\n x: boardX * viewport.k + viewport.x,\n y: boardY * viewport.k + viewport.y,\n }\n}\n\n/**\n * Zoom one step around a fixed screen anchor (the cursor, or the viewport\n * centre for a button press), so the board point under the anchor stays put.\n * @param direction - 1 zooms in, -1 zooms out.\n */\nexport function zoomAt(\n viewport: CanvasViewport,\n direction: 1 | -1,\n anchorX: number,\n anchorY: number,\n): CanvasViewport {\n const next = clampZoom(direction === 1 ? viewport.k * ZOOM_STEP : viewport.k / ZOOM_STEP)\n return zoomTo(viewport, next, anchorX, anchorY)\n}\n\n/** Zoom to an exact factor around a fixed screen anchor. */\nexport function zoomTo(\n viewport: CanvasViewport,\n targetK: number,\n anchorX: number,\n anchorY: number,\n): CanvasViewport {\n const k = clampZoom(targetK)\n if (k === viewport.k) return viewport\n const board = screenToBoard(viewport, anchorX, anchorY)\n return {\n x: anchorX - board.x * k,\n y: anchorY - board.y * k,\n k,\n }\n}\n\n/** Translate the viewport by a screen-space delta (dragging the board). */\nexport function panBy(viewport: CanvasViewport, deltaX: number, deltaY: number): CanvasViewport {\n return { x: viewport.x + deltaX, y: viewport.y + deltaY, k: viewport.k }\n}\n\n/** Bounding box of a set of cards, or undefined for an empty board. */\nexport function cardBounds(cards: CanvasCard[]): { x: number; y: number; width: number; height: number } | undefined {\n if (cards.length === 0) return undefined\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const card of cards) {\n minX = Math.min(minX, card.x)\n minY = Math.min(minY, card.y)\n maxX = Math.max(maxX, card.x + card.width)\n maxY = Math.max(maxY, card.y + card.height)\n }\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }\n}\n\n/**\n * Compute the viewport that shows every card with a little padding.\n * @param stageWidth - visible area width in screen pixels.\n * @param stageHeight - visible area height in screen pixels.\n * @returns the fitted viewport; the identity viewport for an empty board or a\n * degenerate stage.\n */\nexport function fitViewport(\n cards: CanvasCard[],\n stageWidth: number,\n stageHeight: number,\n padding = FIT_PADDING,\n): CanvasViewport {\n if (cards.length === 0 || stageWidth <= 0 || stageHeight <= 0) return { x: 0, y: 0, k: 1 }\n const bounds = cardBounds(cards)\n if (bounds === undefined || bounds.width <= 0 || bounds.height <= 0) return { x: 0, y: 0, k: 1 }\n const availableWidth = Math.max(1, stageWidth - 2 * padding)\n const availableHeight = Math.max(1, stageHeight - 2 * padding)\n const k = clampZoom(Math.min(availableWidth / bounds.width, availableHeight / bounds.height))\n return {\n x: Math.round((-bounds.x * k + (stageWidth - bounds.width * k) / 2) * 100) / 100,\n y: Math.round((-bounds.y * k + (stageHeight - bounds.height * k) / 2) * 100) / 100,\n k,\n }\n}\n\n/**\n * Grid cell size in screen pixels at the current zoom, chosen so the dot grid\n * always reads as roughly the same density however far the user zoomed.\n */\nexport function gridStep(k: number, base = 32): number {\n if (!Number.isFinite(k) || k <= 0) return base\n let step = base * k\n while (step < 12) step *= 4\n while (step > 160) step /= 4\n return step\n}\n","/**\n * Where a picked picture lands on a board.\n *\n * Kept outside the board component so the maths has one home: the picture picker\n * inside the board and the conversation's 「加到画布」 both ask where the visible\n * centre is, and both build the same card there. (Automatic placement of a fresh\n * generation is gone — a background generation no longer touches the board.)\n */\n\nimport { CANVAS_NEW_IMAGE_SIZE } from './canvas-limits.ts'\nimport { screenToBoard } from './canvas-viewport.ts'\nimport type { CanvasCard, CanvasCardOrigin, CanvasViewport } from './protocol.ts'\n\n/** One picture to place, as the library stores it. */\nexport interface PlacedImage {\n /** Library file name the card points at. */\n file: string\n width?: number | undefined\n height?: number | undefined\n /** Model that produced it, shown on the card. */\n model?: string | undefined\n /** Prompt snapshot, shown on the card. */\n prompt?: string | undefined\n /** Where it came from, shown as the card badge. */\n origin?: CanvasCardOrigin | undefined\n}\n\n/**\n * Build the card for one picture, centred on the given board point.\n * @param image - the picture to place.\n * @param centre - board coordinates of the stage centre.\n * @param id - the card id to use.\n * @returns the card, sized to the picture's own aspect ratio.\n */\nexport function centeredImageCard(image: PlacedImage, centre: { x: number; y: number }, id: string): CanvasCard {\n const aspect = image.width !== undefined && image.height !== undefined && image.height > 0\n ? image.width / image.height\n : 1\n const height = Math.round(CANVAS_NEW_IMAGE_SIZE / aspect)\n return {\n id,\n kind: 'image',\n x: Math.round(centre.x - CANVAS_NEW_IMAGE_SIZE / 2),\n y: Math.round(centre.y - height / 2),\n width: CANVAS_NEW_IMAGE_SIZE,\n height,\n z: 0,\n file: image.file,\n model: image.model ?? '',\n prompt: image.prompt ?? '',\n ...image.origin === undefined ? {} : { origin: image.origin },\n }\n}\n\n/**\n * Board coordinates of the visible centre.\n * @param viewport - the board's viewport.\n * @param stage - visible stage size in screen pixels.\n * @returns the board point at the middle of the stage.\n */\nexport function stageCentre(viewport: CanvasViewport, stage: { width: number; height: number }): { x: number; y: number } {\n return screenToBoard(viewport, stage.width / 2, stage.height / 2)\n}\n\n/**\n * Stage size assumed when nothing measurable is on screen yet.\n *\n * Halving a real stage puts a picture in the middle of what the user sees;\n * halving a ZERO stage puts it exactly on the viewport's top-left corner, which\n * is the \"why did it land in the corner\" bug. Assuming a plausible panel size\n * keeps it near the middle even before the canvas has been laid out.\n */\nexport const ASSUMED_STAGE: { readonly width: number; readonly height: number } = { width: 960, height: 640 }\n\n/**\n * The stage size to place against, falling back when nothing is measured.\n * @param measured - the last measurement (may be zero before layout).\n * @returns a usable size.\n */\nexport function usableStage(measured: { width: number; height: number }): { width: number; height: number } {\n return measured.width > 0 && measured.height > 0 ? measured : { ...ASSUMED_STAGE }\n}\n","/**\n * Annotation objects (the board's own marking layer).\n *\n * Three kinds share one session — brush strokes, rectangles, text — and the user\n * may mix them freely, which is why they are one list of objects rather than one\n * active tool's output (ADR-0016 in the reference client made the same call).\n * Everything is stored in the **picture's own pixel coordinates**, so burning is\n * a straight 1:1 replay onto a canvas of the natural size and a different display\n * scale can never move a mark.\n *\n * Geometry here is pure data + arithmetic (no DOM), so it is unit-tested; the\n * actual painting lives in `burn-image.ts`.\n */\n\n/** Which tool is drawing right now. */\nexport type AnnotationTool = 'brush' | 'rect' | 'arrow' | 'text' | 'eraser'\n\n/** One point, in the picture's own pixels. */\nexport interface AnnotationPoint {\n x: number\n y: number\n}\n\n/** A freehand stroke. */\nexport interface BrushAnnotation {\n kind: 'brush'\n color: string\n /** Stroke width in picture pixels. */\n sizePx: number\n points: AnnotationPoint[]\n}\n\n/** A rectangle outline: the \"circle this for the model\" mark. */\nexport interface RectAnnotation {\n kind: 'rect'\n color: string\n /** Outline width in picture pixels. */\n sizePx: number\n x: number\n y: number\n width: number\n height: number\n}\n\n/** A straight arrow: \"this one, in this direction\". */\nexport interface ArrowAnnotation {\n kind: 'arrow'\n color: string\n /** Shaft width in picture pixels (the head is sized from it). */\n sizePx: number\n from: AnnotationPoint\n to: AnnotationPoint\n}\n\n/** A text label anchored at its top-left corner. */\nexport interface TextAnnotation {\n kind: 'text'\n color: string\n /** Font size in picture pixels. */\n sizePx: number\n x: number\n y: number\n text: string\n}\n\n/** One annotation object. */\nexport type Annotation = BrushAnnotation | RectAnnotation | ArrowAnnotation | TextAnnotation\n\n/** The palette (three colours keeps the toolbar a row, not a picker). */\nexport const ANNOTATION_COLORS: readonly string[] = ['#ff3b30', '#ffcc00', '#0a84ff']\n\n/** The three widths, in editor pixels. */\nexport const ANNOTATION_WIDTHS: readonly number[] = [4, 8, 16]\n\n/** Smallest rectangle the user can draw (picture pixels). */\nexport const MIN_RECT_SIZE = 4\n\n/** How far from an object's edge still counts as a hit (picture pixels). */\nexport const HIT_TOLERANCE = 6\n\n/** Font family for text marks; must match what the burn uses. */\nexport const TEXT_FONT_FAMILY = 'system-ui, sans-serif'\n\n/** The bounding box of one object. */\nexport function annotationBounds(annotation: Annotation): { x: number; y: number; width: number; height: number } {\n if (annotation.kind === 'rect') return { x: annotation.x, y: annotation.y, width: annotation.width, height: annotation.height }\n if (annotation.kind === 'text') {\n // Height follows the font; width is left generous because the burn measures\n // the real text and the editor only needs the box for hit-testing.\n return { x: annotation.x, y: annotation.y, width: annotation.text.length * annotation.sizePx, height: annotation.sizePx * 1.25 }\n }\n if (annotation.kind === 'arrow') {\n const minX = Math.min(annotation.from.x, annotation.to.x)\n const minY = Math.min(annotation.from.y, annotation.to.y)\n const pad = annotation.sizePx * 2\n return {\n x: minX - pad,\n y: minY - pad,\n width: Math.abs(annotation.to.x - annotation.from.x) + pad * 2,\n height: Math.abs(annotation.to.y - annotation.from.y) + pad * 2,\n }\n }\n const xs = annotation.points.map(point => point.x)\n const ys = annotation.points.map(point => point.y)\n const minX = Math.min(...xs)\n const minY = Math.min(...ys)\n return {\n x: minX - annotation.sizePx / 2,\n y: minY - annotation.sizePx / 2,\n width: Math.max(...xs) - minX + annotation.sizePx,\n height: Math.max(...ys) - minY + annotation.sizePx,\n }\n}\n\n/** Move one object by a picture-pixel delta. */\nexport function moveAnnotation(annotation: Annotation, dx: number, dy: number): Annotation {\n if (annotation.kind === 'brush') {\n return { ...annotation, points: annotation.points.map(point => ({ x: point.x + dx, y: point.y + dy })) }\n }\n if (annotation.kind === 'arrow') {\n return {\n ...annotation,\n from: { x: annotation.from.x + dx, y: annotation.from.y + dy },\n to: { x: annotation.to.x + dx, y: annotation.to.y + dy },\n }\n }\n return { ...annotation, x: annotation.x + dx, y: annotation.y + dy }\n}\n\n/** Distance from a point to a segment, in picture pixels. */\nexport function distanceToSegment(point: AnnotationPoint, from: AnnotationPoint, to: AnnotationPoint): number {\n const dx = to.x - from.x\n const dy = to.y - from.y\n const lengthSquared = dx * dx + dy * dy\n if (lengthSquared === 0) return Math.hypot(point.x - from.x, point.y - from.y)\n // Projection of the point onto the segment, clamped to its ends.\n const t = Math.max(0, Math.min(1, ((point.x - from.x) * dx + (point.y - from.y) * dy) / lengthSquared))\n return Math.hypot(point.x - (from.x + t * dx), point.y - (from.y + t * dy))\n}\n\n/** Whether a point (picture pixels) lands on one object. */\nexport function hitAnnotation(annotation: Annotation, point: AnnotationPoint): boolean {\n if (annotation.kind === 'arrow') {\n // An arrow is mostly empty space: hit the shaft, not its bounding box.\n return distanceToSegment(point, annotation.from, annotation.to) <= annotation.sizePx * 1.5 + HIT_TOLERANCE\n }\n const bounds = annotationBounds(annotation)\n return point.x >= bounds.x - HIT_TOLERANCE\n && point.x <= bounds.x + bounds.width + HIT_TOLERANCE\n && point.y >= bounds.y - HIT_TOLERANCE\n && point.y <= bounds.y + bounds.height + HIT_TOLERANCE\n}\n\n/** The topmost object under a point, if any (later objects are on top). */\nexport function annotationAt(annotations: readonly Annotation[], point: AnnotationPoint): number | undefined {\n for (let index = annotations.length - 1; index >= 0; index -= 1) {\n if (hitAnnotation(annotations[index]!, point)) return index\n }\n return undefined\n}\n\n/** Every object under a point, topmost first (the eraser takes them all). */\nexport function annotationsAt(annotations: readonly Annotation[], point: AnnotationPoint): number[] {\n const hits: number[] = []\n for (let index = annotations.length - 1; index >= 0; index -= 1) {\n if (hitAnnotation(annotations[index]!, point)) hits.push(index)\n }\n return hits\n}\n\n/**\n * The head of an arrow, as a filled triangle in picture pixels.\n *\n * Shared by the editor's SVG and the burn so the mark the user sees is the mark\n * that gets painted.\n *\n * @param from - the tail.\n * @param to - the tip.\n * @param sizePx - the shaft width (the head scales from it).\n * @returns the three corners of the head, tip last.\n */\nexport function arrowHeadPoints(from: AnnotationPoint, to: AnnotationPoint, sizePx: number): [AnnotationPoint, AnnotationPoint, AnnotationPoint] {\n const angle = Math.atan2(to.y - from.y, to.x - from.x)\n const length = Math.max(sizePx * 3, 10)\n const half = Math.max(sizePx * 1.6, 6)\n const baseX = to.x - Math.cos(angle) * length\n const baseY = to.y - Math.sin(angle) * length\n const normalX = -Math.sin(angle) * half\n const normalY = Math.cos(angle) * half\n return [\n { x: baseX + normalX, y: baseY + normalY },\n { x: baseX - normalX, y: baseY - normalY },\n { x: to.x, y: to.y },\n ]\n}\n\n/**\n * Convert the editor's stroke widths into picture pixels.\n *\n * The user picks a width that LOOKS right on screen, so it has to be scaled by\n * how much the editor shrinks the picture; otherwise the same choice produces a\n * hairline on a 4K image and a slab on a thumbnail.\n *\n * @param sizePx - the width chosen in the editor.\n * @param scale - picture pixels per editor pixel (natural / displayed).\n * @returns the width to store on the object.\n */\nexport function scaleWidth(sizePx: number, scale: number): number {\n return Math.max(1, sizePx * (Number.isFinite(scale) && scale > 0 ? scale : 1))\n}\n\n/** Whether anything was drawn (an empty session must not produce a copy). */\nexport function hasAnnotations(annotations: readonly Annotation[]): boolean {\n return annotations.some(annotation => annotation.kind !== 'brush' || annotation.points.length > 0)\n && annotations.some(annotation => annotation.kind !== 'text' || annotation.text.trim() !== '')\n}","/**\n * Burning annotations and crops into a new picture (browser half).\n *\n * Both operations are the same shape: load the picture, replay something onto an\n * offscreen canvas of its natural size, hand back a PNG data URL. That URL is\n * what the host stores as a canvas asset — the original file is never touched and\n * nothing here goes near the material library.\n *\n * This is the one part of the feature that cannot be unit-tested in jsdom (no 2D\n * context), so it stays deliberately thin and arithmetic-free: all coordinates\n * arrive already in picture pixels from `annotations.ts` / `crop-rect.ts`.\n */\n\nimport { TEXT_FONT_FAMILY, arrowHeadPoints, type Annotation } from './annotations.ts'\nimport type { CropRect } from './crop-rect.ts'\n\n/** One burned picture: the PNG and the size it was burned at. */\nexport interface BurnedImage {\n dataUrl: string\n width: number\n height: number\n}\n\n/** Load one picture for the canvas to draw. */\nasync function loadImage(url: string): Promise<HTMLImageElement | undefined> {\n return new Promise(resolve => {\n const image = new Image()\n image.onload = () => { resolve(image) }\n image.onerror = () => { resolve(undefined) }\n image.src = url\n })\n}\n\n/**\n * Draw a picture onto an offscreen canvas and export it as a PNG data URL.\n *\n * @param url - where the picture is served from.\n * @param paint - draws on top of the picture (receives the 2D context and size).\n * @param source - sub-rectangle to take instead of the whole picture (a crop).\n * @returns the burned picture, or undefined when it could not be loaded.\n */\nasync function burn(\n url: string,\n paint: (context: CanvasRenderingContext2D, width: number, height: number) => void,\n source?: CropRect,\n): Promise<BurnedImage | undefined> {\n const image = await loadImage(url)\n if (image === undefined) return undefined\n const naturalWidth = image.naturalWidth === 0 ? image.width : image.naturalWidth\n const naturalHeight = image.naturalHeight === 0 ? image.height : image.naturalHeight\n const rect = source ?? { x: 0, y: 0, width: naturalWidth, height: naturalHeight }\n const canvas = document.createElement('canvas')\n canvas.width = Math.max(1, Math.round(rect.width))\n canvas.height = Math.max(1, Math.round(rect.height))\n const context = canvas.getContext('2d')\n if (context === null) return undefined\n context.drawImage(\n image,\n rect.x, rect.y, rect.width, rect.height,\n 0, 0, canvas.width, canvas.height,\n )\n paint(context, canvas.width, canvas.height)\n return { dataUrl: canvas.toDataURL('image/png'), width: canvas.width, height: canvas.height }\n}\n\n/**\n * Burn every annotation onto a copy of the picture, in list order.\n *\n * @param url - where the picture is served from.\n * @param annotations - the marks, in picture pixels.\n * @returns the composite, or undefined when the picture could not be loaded.\n */\nexport async function burnAnnotations(url: string, annotations: readonly Annotation[]): Promise<BurnedImage | undefined> {\n return burn(url, (context, width, height) => {\n for (const annotation of annotations) {\n if (annotation.kind === 'rect') {\n // Inset by half the line so the outline lands fully inside the picture.\n const inset = annotation.sizePx / 2\n context.strokeStyle = annotation.color\n context.lineWidth = annotation.sizePx\n context.strokeRect(\n annotation.x + inset,\n annotation.y + inset,\n Math.max(1, annotation.width - annotation.sizePx),\n Math.max(1, annotation.height - annotation.sizePx),\n )\n continue\n }\n if (annotation.kind === 'brush') {\n if (annotation.points.length === 0) continue\n context.strokeStyle = annotation.color\n context.fillStyle = annotation.color\n context.lineWidth = annotation.sizePx\n context.lineCap = 'round'\n context.lineJoin = 'round'\n const [first, ...rest] = annotation.points\n if (rest.length === 0) {\n // A tap with no drag is a dot, not an invisible stroke.\n context.beginPath()\n context.arc(first!.x, first!.y, annotation.sizePx / 2, 0, Math.PI * 2)\n context.fill()\n continue\n }\n context.beginPath()\n context.moveTo(first!.x, first!.y)\n for (const point of rest) context.lineTo(point.x, point.y)\n context.stroke()\n continue\n }\n if (annotation.kind === 'arrow') {\n // Shaft plus a filled head, from the same helper the editor draws with.\n const [left, right, tip] = arrowHeadPoints(annotation.from, annotation.to, annotation.sizePx)\n context.strokeStyle = annotation.color\n context.fillStyle = annotation.color\n context.lineWidth = annotation.sizePx\n context.lineCap = 'round'\n context.beginPath()\n context.moveTo(annotation.from.x, annotation.from.y)\n context.lineTo(annotation.to.x, annotation.to.y)\n context.stroke()\n context.beginPath()\n context.moveTo(left.x, left.y)\n context.lineTo(right.x, right.y)\n context.lineTo(tip.x, tip.y)\n context.closePath()\n context.fill()\n continue\n }\n if (annotation.text.trim() === '') continue\n context.fillStyle = annotation.color\n // `top` baseline matches the editor's top-left anchored label, so what the\n // user placed is where it lands.\n context.textBaseline = 'top'\n context.font = `${annotation.sizePx}px ${TEXT_FONT_FAMILY}`\n context.fillText(annotation.text, annotation.x, annotation.y)\n }\n void width\n void height\n })\n}\n\n/**\n * Cut a sub-rectangle out of the picture.\n *\n * @param url - where the picture is served from.\n * @param rect - the crop box in picture pixels.\n * @returns the cropped picture, or undefined when it could not be loaded.\n */\nexport async function burnCrop(url: string, rect: CropRect): Promise<BurnedImage | undefined> {\n return burn(url, () => {}, rect)\n}\n","/**\r\n * The annotation editor: mark the picture, then save the marks as one composite.\r\n *\r\n * Opened from the board's floating command bar. The picture is shown fitted to the\r\n * panel and the marks are drawn as an SVG overlay with the picture's own coordinate\r\n * system as its viewBox — so the display scale is the browser's problem, and every\r\n * object is stored in natural pixels. Saving burns them onto a copy (see\r\n * `burn-image.ts`) and hands the board a NEW node: the original picture, and the\r\n * material library, are untouched.\r\n *\r\n * Three tools mix in one session (`annotations.ts`): brush strokes, rectangles\r\n * (drag to draw), and text (click, type, Enter). Undo/redo is object-level —\r\n * see the reference client's ADR-0016, which this follows.\r\n */\r\n\r\nimport { useEffect, useMemo, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react'\r\nimport {\r\n ANNOTATION_COLORS,\r\n ANNOTATION_WIDTHS,\r\n annotationAt,\r\n annotationsAt,\r\n arrowHeadPoints,\r\n hasAnnotations,\r\n moveAnnotation,\r\n scaleWidth,\r\n type Annotation,\r\n type AnnotationPoint,\r\n type AnnotationTool,\r\n} from './annotations.ts'\r\nimport { burnAnnotations } from './burn-image.ts'\r\nimport { Button, TextInput } from './controls.tsx'\r\nimport css from './annotation-editor.module.css'\r\n\r\n/** What the editor needs to know about the picture it is marking. */\r\nexport interface AnnotationEditorProps {\r\n /** Where the picture is served from. */\r\n url: string\r\n /** Its natural size, when known (the editor reads it from the image otherwise). */\r\n natural?: { width: number; height: number } | undefined\r\n /** Called with the burned composite when the user saves. */\r\n onSave: (dataUrl: string) => void | Promise<void>\r\n /** Leave without saving. */\r\n onCancel: () => void\r\n}\r\n\r\n/** One drag in progress (brush stroke, rectangle, arrow, eraser, or moving an object). */\r\ntype Drag =\r\n | { kind: 'brush'; points: AnnotationPoint[] }\r\n | { kind: 'rect'; start: AnnotationPoint; current: AnnotationPoint }\r\n | { kind: 'arrow'; start: AnnotationPoint; current: AnnotationPoint }\r\n | { kind: 'erase' }\r\n | { kind: 'move'; index: number; last: AnnotationPoint; moved: boolean }\r\n\r\n/**\r\n * The editor overlay.\r\n * @param props - the picture and what to do when the user saves or cancels.\r\n * @returns the editor.\r\n */\r\nexport function AnnotationEditor(props: AnnotationEditorProps): JSX.Element {\r\n const [tool, setTool] = useState<AnnotationTool>('brush')\r\n const [color, setColor] = useState(ANNOTATION_COLORS[0]!)\r\n const [width, setWidth] = useState(ANNOTATION_WIDTHS[1]!)\r\n const [objects, setObjects] = useState<Annotation[]>([])\r\n const [selected, setSelected] = useState<number | undefined>(undefined)\r\n const [draft, setDraft] = useState<Annotation | undefined>(undefined)\r\n const [text, setText] = useState<{ point: AnnotationPoint; value: string } | undefined>(undefined)\r\n const [natural, setNatural] = useState(props.natural ?? { width: 1024, height: 1024 })\r\n const [saving, setSaving] = useState(false)\r\n // Object-level history: every committed change pushes the previous list.\r\n const [past, setPast] = useState<Annotation[][]>([])\r\n const [future, setFuture] = useState<Annotation[][]>([])\r\n const surfaceRef = useRef<SVGSVGElement | null>(null)\r\n const dragRef = useRef<Drag | undefined>(undefined)\r\n /**\r\n * The in-progress object, kept in a ref as well as in state.\r\n *\r\n * The pointer-up handler has to see what the moves produced, and a gesture can\r\n * deliver down/move/up inside one task (a fast drag, or a scripted one) — a\r\n * state closure would still hold the pre-gesture value and drop the object.\r\n */\r\n const draftRef = useRef<Annotation | undefined>(undefined)\r\n /**\r\n * Where the text tool was pressed, waiting for the release.\r\n *\r\n * The text box is deliberately created on pointer UP, not on pointer down: the\r\n * browser moves focus on `mousedown`, which lands right after `pointerdown`. A\r\n * box created on the press is therefore mounted and then blurred inside the very\r\n * same click — the input's `autoFocus` never survives and nobody can type. By\r\n * release time that focus shift is over and the box keeps the caret.\r\n */\r\n const textPointRef = useRef<AnnotationPoint | undefined>(undefined)\r\n /**\r\n * Whether the press in flight is the one that confirmed an open text box.\r\n *\r\n * Confirming and arming happen in two different React listeners — capture, then\r\n * bubble — and React re-renders in between, so the bubble handler sees a fresh\r\n * world where no box is open. Without this flag the same press would confirm one\r\n * label and immediately open the next box.\r\n */\r\n const textPressUsedRef = useRef(false)\r\n const commitDraft = (next: Annotation | undefined): void => {\r\n draftRef.current = next\r\n setDraft(next)\r\n }\r\n\r\n // The picture's real size decides the coordinate system, so read it from the\r\n // element rather than trusting whatever the caller happened to know.\r\n useEffect(() => {\r\n const image = new Image()\r\n image.onload = () => {\r\n if (image.naturalWidth > 0 && image.naturalHeight > 0) {\r\n setNatural({ width: image.naturalWidth, height: image.naturalHeight })\r\n }\r\n }\r\n image.src = props.url\r\n }, [props.url])\r\n\r\n const commit = (next: Annotation[]): void => {\r\n setPast(history => [...history, objects])\r\n setFuture([])\r\n setObjects(next)\r\n setSelected(undefined)\r\n }\r\n\r\n const undo = (): void => {\r\n if (past.length === 0) return\r\n const previous = past[past.length - 1]!\r\n setPast(history => history.slice(0, -1))\r\n setFuture(stack => [objects, ...stack])\r\n setObjects(previous)\r\n setSelected(undefined)\r\n }\r\n\r\n const redo = (): void => {\r\n if (future.length === 0) return\r\n const next = future[0]!\r\n setFuture(stack => stack.slice(1))\r\n setPast(history => [...history, objects])\r\n setObjects(next)\r\n setSelected(undefined)\r\n }\r\n\r\n useEffect(() => {\r\n const onKey = (event: KeyboardEvent): void => {\r\n // While the text box is open it owns the keyboard: Esc there cancels the\r\n // typing (handled by the input itself), NOT the whole editor. Closing the\r\n // editor on Esc is exactly the trap this guard exists to avoid. The branch\r\n // below also covers the box being open but unfocused (the user tabbed away,\r\n // or the window lost focus), where the input never sees the key at all.\r\n if (text !== undefined) {\r\n if (event.key === 'Escape') setText(undefined)\r\n return\r\n }\r\n if (event.key === 'Escape') props.onCancel()\r\n if (event.key === 'z' && (event.ctrlKey || event.metaKey)) {\r\n event.preventDefault()\r\n if (event.shiftKey) redo()\r\n else undo()\r\n }\r\n if ((event.key === 'Delete' || event.key === 'Backspace') && selected !== undefined) {\r\n event.preventDefault()\r\n commit(objects.filter((_object, index) => index !== selected))\r\n }\r\n }\r\n document.addEventListener('keydown', onKey)\r\n return () => { document.removeEventListener('keydown', onKey) }\r\n })\r\n\r\n /** Pointer position in picture pixels. */\r\n const toPicture = (event: ReactPointerEvent): AnnotationPoint => {\r\n const surface = surfaceRef.current\r\n if (surface === null) return { x: 0, y: 0 }\r\n const rect = surface.getBoundingClientRect()\r\n const scaleX = rect.width === 0 ? 1 : natural.width / rect.width\r\n const scaleY = rect.height === 0 ? 1 : natural.height / rect.height\r\n return {\r\n x: Math.min(natural.width, Math.max(0, (event.clientX - rect.left) * scaleX)),\r\n y: Math.min(natural.height, Math.max(0, (event.clientY - rect.top) * scaleY)),\r\n }\r\n }\r\n\r\n /** How much the editor shrinks the picture (for converting stroke widths). */\r\n const editorScale = (): number => {\r\n const surface = surfaceRef.current\r\n if (surface === null) return 1\r\n const rect = surface.getBoundingClientRect()\r\n return rect.width === 0 ? 1 : natural.width / rect.width\r\n }\r\n\r\n const onPointerDown = (event: ReactPointerEvent): void => {\r\n const point = toPicture(event)\r\n if (textPressUsedRef.current) {\r\n // This press only confirmed the previous label; it must not arm another one.\r\n textPressUsedRef.current = false\r\n return\r\n }\r\n if (text !== undefined) return\r\n if (tool === 'text') {\r\n // Only remembered here; the box is opened on release (see textPointRef).\r\n textPointRef.current = point\r\n return\r\n }\r\n if (tool === 'eraser') {\r\n // Object eraser: whatever the pointer touches disappears, and undo brings\r\n // it back. (An object list has no pixels to rub out.)\r\n dragRef.current = { kind: 'erase' }\r\n eraseAt(point)\r\n return\r\n }\r\n if (tool === 'brush') {\r\n dragRef.current = { kind: 'brush', points: [point] }\r\n commitDraft({ kind: 'brush', color, sizePx: scaleWidth(width, editorScale()), points: [point] })\r\n return\r\n }\r\n if (tool === 'arrow') {\r\n dragRef.current = { kind: 'arrow', start: point, current: point }\r\n setSelected(undefined)\r\n commitDraft({ kind: 'arrow', color, sizePx: scaleWidth(width, editorScale()), from: point, to: point })\r\n return\r\n }\r\n // Rect tool: a drag on an existing object moves it instead of drawing a new\r\n // one, which is what \"click the object and drag it\" means to a user.\r\n const hit = annotationAt(objects, point)\r\n if (hit !== undefined) {\r\n dragRef.current = { kind: 'move', index: hit, last: point, moved: false }\r\n setSelected(hit)\r\n return\r\n }\r\n dragRef.current = { kind: 'rect', start: point, current: point }\r\n setSelected(undefined)\r\n commitDraft({ kind: 'rect', color, sizePx: scaleWidth(width, editorScale()), x: point.x, y: point.y, width: 0, height: 0 })\r\n }\r\n\r\n /** Remove every object under one point, as one undoable step. */\r\n const eraseAt = (point: AnnotationPoint): void => {\r\n const hits = annotationsAt(objects, point)\r\n if (hits.length === 0) return\r\n commit(objects.filter((_object, index) => !hits.includes(index)))\r\n }\r\n\r\n const onPointerMove = (event: ReactPointerEvent): void => {\r\n const drag = dragRef.current\r\n if (drag === undefined) return\r\n const point = toPicture(event)\r\n if (drag.kind === 'erase') {\r\n eraseAt(point)\r\n return\r\n }\r\n if (drag.kind === 'brush') {\r\n drag.points.push(point)\r\n const current = draftRef.current\r\n if (current?.kind === 'brush') commitDraft({ ...current, points: [...drag.points] })\r\n return\r\n }\r\n if (drag.kind === 'rect') {\r\n drag.current = point\r\n commitDraft({\r\n kind: 'rect',\r\n color,\r\n sizePx: scaleWidth(width, editorScale()),\r\n x: Math.min(drag.start.x, point.x),\r\n y: Math.min(drag.start.y, point.y),\r\n width: Math.abs(point.x - drag.start.x),\r\n height: Math.abs(point.y - drag.start.y),\r\n })\r\n return\r\n }\r\n if (drag.kind === 'arrow') {\r\n drag.current = point\r\n commitDraft({ kind: 'arrow', color, sizePx: scaleWidth(width, editorScale()), from: drag.start, to: point })\r\n return\r\n }\r\n const dx = point.x - drag.last.x\r\n const dy = point.y - drag.last.y\r\n if (dx === 0 && dy === 0) return\r\n drag.last = point\r\n drag.moved = true\r\n setObjects(current => current.map((object, index) => index === drag.index ? moveAnnotation(object, dx, dy) : object))\r\n }\r\n\r\n const onPointerUp = (): void => {\r\n if (tool === 'text') {\r\n const point = textPointRef.current\r\n textPointRef.current = undefined\r\n if (point !== undefined) setText({ point, value: '' })\r\n return\r\n }\r\n const drag = dragRef.current\r\n dragRef.current = undefined\r\n if (drag === undefined) return\r\n if (drag.kind === 'move') {\r\n // A move is already applied to the list; only a real move is undoable.\r\n if (drag.moved) {\r\n setFuture([])\r\n setPast(history => [...history, objects])\r\n }\r\n return\r\n }\r\n const pending = draftRef.current\r\n commitDraft(undefined)\r\n if (pending === undefined) return\r\n // A rectangle or arrow that was never dragged is not a mark.\r\n if (pending.kind === 'rect' && (pending.width < 4 || pending.height < 4)) return\r\n if (pending.kind === 'arrow' && Math.hypot(pending.to.x - pending.from.x, pending.to.y - pending.from.y) < 6) return\r\n commit([...objects, pending])\r\n }\r\n\r\n const commitText = (): void => {\r\n const pending = text\r\n setText(undefined)\r\n if (pending === undefined || pending.value.trim() === '') return\r\n commit([...objects, {\r\n kind: 'text',\r\n color,\r\n sizePx: scaleWidth(width, editorScale()),\r\n x: pending.point.x,\r\n y: pending.point.y,\r\n text: pending.value.trim(),\r\n }])\r\n }\r\n\r\n const visible = useMemo(() => (draft === undefined ? objects : [...objects, draft]), [draft, objects])\r\n\r\n /**\r\n * Pressing anywhere outside the text box finishes the label.\r\n *\r\n * This replaces an `onBlur` commit, which could not survive a real click: the\r\n * browser's own `mousedown` focus handling blurred a freshly mounted box and the\r\n * handler threw the (still empty) typing away. Capture phase, so the press that\r\n * closes the box cannot also start a new one.\r\n */\r\n const onOverlayPointerDown = (event: ReactPointerEvent): void => {\r\n textPressUsedRef.current = false\r\n if (text === undefined) return\r\n const target = event.target\r\n if (target instanceof Element && target.closest('[data-dsh-seework-annotation-text]') !== null) return\r\n textPressUsedRef.current = true\r\n commitText()\r\n }\r\n\r\n return (\r\n <div\r\n className={css.overlay}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-label=\"标注编辑器\"\r\n onPointerDownCapture={onOverlayPointerDown}\r\n >\r\n <div className={css.panel}>\r\n <header className={css.toolbar} data-dsh-seework-annotation-toolbar=\"\">\r\n {([\r\n ['brush', '画笔'],\r\n ['rect', '矩形'],\r\n ['arrow', '箭头'],\r\n ['text', '文字'],\r\n ['eraser', '橡皮擦'],\r\n ] as Array<[AnnotationTool, string]>).map(([value, label]) => (\r\n <button\r\n key={value}\r\n type=\"button\"\r\n className={css.tool}\r\n data-active={tool === value ? 'true' : undefined}\r\n data-tool={value}\r\n onClick={() => { setTool(value) }}\r\n >\r\n {label}\r\n </button>\r\n ))}\r\n <span className={css.gap} />\r\n {ANNOTATION_COLORS.map(swatch => (\r\n <button\r\n key={swatch}\r\n type=\"button\"\r\n className={css.swatch}\r\n style={{ background: swatch }}\r\n title={`颜色 ${swatch}`}\r\n aria-label={`颜色 ${swatch}`}\r\n data-active={color === swatch ? 'true' : undefined}\r\n onClick={() => { setColor(swatch) }}\r\n />\r\n ))}\r\n <span className={css.gap} />\r\n {ANNOTATION_WIDTHS.map(size => (\r\n <button\r\n key={size}\r\n type=\"button\"\r\n className={css.tool}\r\n data-active={width === size ? 'true' : undefined}\r\n title={`粗细 ${size}`}\r\n onClick={() => { setWidth(size) }}\r\n >\r\n {size}\r\n </button>\r\n ))}\r\n <span className={css.gap} />\r\n <Button onClick={undo} disabled={past.length === 0} title=\"撤销(Ctrl+Z)\">撤销</Button>\r\n <Button onClick={redo} disabled={future.length === 0} title=\"重做(Ctrl+Shift+Z)\">重做</Button>\r\n <Button onClick={props.onCancel}>取消</Button>\r\n <Button\r\n variant=\"primary\"\r\n disabled={saving || !hasAnnotations(objects)}\r\n data-dsh-seework-annotation-save=\"\"\r\n onClick={() => {\r\n setSaving(true)\r\n void burnAnnotations(props.url, objects)\r\n .then(burned => burned === undefined ? undefined : props.onSave(burned.dataUrl))\r\n .finally(() => { setSaving(false) })\r\n }}\r\n >\r\n {saving ? '保存中…' : '保存为合成图'}\r\n </Button>\r\n </header>\r\n\r\n <div className={css.stage}>\r\n <div className={css.frame}>\r\n <img className={css.picture} src={props.url} alt=\"待标注的图片\" draggable={false} />\r\n <svg\r\n ref={surfaceRef}\r\n className={css.surface}\r\n viewBox={`0 0 ${natural.width} ${natural.height}`}\r\n preserveAspectRatio=\"none\"\r\n onPointerDown={onPointerDown}\r\n onPointerMove={onPointerMove}\r\n onPointerUp={onPointerUp}\r\n onPointerLeave={onPointerUp}\r\n >\r\n {visible.map((object, index) => <AnnotationShape key={index} annotation={object} />)}\r\n </svg>\r\n {text === undefined\r\n ? null\r\n : (\r\n <div\r\n className={css.textEntry}\r\n data-dsh-seework-annotation-text=\"\"\r\n style={{\r\n left: `${(text.point.x / natural.width) * 100}%`,\r\n top: `${(text.point.y / natural.height) * 100}%`,\r\n }}\r\n >\r\n <TextInput\r\n autoFocus\r\n value={text.value}\r\n placeholder=\"输入文字,回车确认,Esc 取消\"\r\n onChange={event => { setText(current => current === undefined ? current : { ...current, value: event.target.value }) }}\r\n onKeyDown={event => {\r\n if (event.key === 'Enter') commitText()\r\n if (event.key === 'Escape') {\r\n // Cancel the typing only: the editor stays open, and the\r\n // document-level Escape handler is told to stand down.\r\n event.stopPropagation()\r\n event.preventDefault()\r\n setText(undefined)\r\n }\r\n }}\r\n />\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n <p className={css.hint}>\r\n 五种标注可以混用:画笔涂抹、矩形框选、箭头指向、文字说明、橡皮擦按对象擦除(都可撤销)。保存会生成一张合成图作为**新**卡片,原图与素材库都不动。\r\n 文字框里回车确认、Esc 取消输入(不会关掉编辑器)、点框外即确认。\r\n {selected === undefined ? '' : ' 已选中一个标注:拖动可移动,Delete 删除。'}\r\n </p>\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\n/** One annotation drawn into the SVG overlay. */\r\nfunction AnnotationShape({ annotation }: { annotation: Annotation }): JSX.Element {\r\n if (annotation.kind === 'rect') {\r\n return (\r\n <rect\r\n x={annotation.x}\r\n y={annotation.y}\r\n width={annotation.width}\r\n height={annotation.height}\r\n fill=\"none\"\r\n stroke={annotation.color}\r\n strokeWidth={annotation.sizePx}\r\n />\r\n )\r\n }\r\n if (annotation.kind === 'text') {\r\n return (\r\n <text x={annotation.x} y={annotation.y} fill={annotation.color} fontSize={annotation.sizePx} dominantBaseline=\"hanging\">\r\n {annotation.text}\r\n </text>\r\n )\r\n }\r\n if (annotation.kind === 'arrow') {\r\n const [left, right, tip] = arrowHeadPoints(annotation.from, annotation.to, annotation.sizePx)\r\n return (\r\n <g>\r\n <line\r\n x1={annotation.from.x}\r\n y1={annotation.from.y}\r\n x2={annotation.to.x}\r\n y2={annotation.to.y}\r\n stroke={annotation.color}\r\n strokeWidth={annotation.sizePx}\r\n strokeLinecap=\"round\"\r\n />\r\n <polygon\r\n points={`${left.x},${left.y} ${right.x},${right.y} ${tip.x},${tip.y}`}\r\n fill={annotation.color}\r\n />\r\n </g>\r\n )\r\n }\r\n return (\r\n <polyline\r\n points={annotation.points.map(point => `${point.x},${point.y}`).join(' ')}\r\n fill=\"none\"\r\n stroke={annotation.color}\r\n strokeWidth={annotation.sizePx}\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n />\r\n )\r\n}\r\n","/**\n * Crop geometry, in the picture's own pixels.\n *\n * The crop box lives in natural coordinates and is rendered by scaling it to the\n * node's displayed size, so zooming the board never changes what will be cut —\n * the same three-layer mapping (natural ↔ node ↔ screen) the reference client\n * uses. Pure arithmetic, unit-tested; the actual cutting lives in `burn-image.ts`.\n */\n\n/** One crop box, in picture pixels. */\nexport interface CropRect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** One length/ratio choice in the crop toolbar. */\nexport interface CropAspectOption {\n label: string\n /** width / height, or null for a free box. */\n value: number | null\n}\n\n/** The ratio menu, mirroring the reference client's list. */\nexport const CROP_ASPECTS: readonly CropAspectOption[] = [\n { label: '自定义', value: null },\n { label: '1:1', value: 1 },\n { label: '4:3', value: 4 / 3 },\n { label: '3:4', value: 3 / 4 },\n { label: '16:9', value: 16 / 9 },\n { label: '9:16', value: 9 / 16 },\n]\n\n/** Smallest crop box (picture pixels), so a stray drag cannot produce nothing. */\nexport const MIN_CROP_SIZE = 16\n\n/** The eight resize handles. */\nexport type CropHandle = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w'\n\n/** All handles, in outline order. */\nexport const CROP_HANDLES: readonly CropHandle[] = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']\n\n/** Which directions a handle moves. */\nconst HANDLE_AXES: Record<CropHandle, { x: -1 | 0 | 1; y: -1 | 0 | 1 }> = {\n nw: { x: -1, y: -1 },\n n: { x: 0, y: -1 },\n ne: { x: 1, y: -1 },\n e: { x: 1, y: 0 },\n se: { x: 1, y: 1 },\n s: { x: 0, y: 1 },\n sw: { x: -1, y: 1 },\n w: { x: -1, y: 0 },\n}\n\n/** Keep a box inside the picture. */\nfunction clampRect(rect: CropRect, natural: { width: number; height: number }): CropRect {\n const width = Math.min(Math.max(MIN_CROP_SIZE, rect.width), natural.width)\n const height = Math.min(Math.max(MIN_CROP_SIZE, rect.height), natural.height)\n return {\n width,\n height,\n x: Math.min(Math.max(0, rect.x), Math.max(0, natural.width - width)),\n y: Math.min(Math.max(0, rect.y), Math.max(0, natural.height - height)),\n }\n}\n\n/**\n * The box a crop mode starts with: the whole picture, or the largest box of the\n * requested ratio that fits in it.\n *\n * @param natural - the picture's natural size.\n * @param aspect - width / height, or null for the whole picture.\n * @returns the starting box.\n */\nexport function defaultCropRect(natural: { width: number; height: number }, aspect: number | null): CropRect {\n if (aspect === null) return { x: 0, y: 0, width: natural.width, height: natural.height }\n const byWidth = { width: natural.width, height: natural.width / aspect }\n const size = byWidth.height <= natural.height ? byWidth : { width: natural.height * aspect, height: natural.height }\n return clampRect({\n x: (natural.width - size.width) / 2,\n y: (natural.height - size.height) / 2,\n width: size.width,\n height: size.height,\n }, natural)\n}\n\n/**\n * Move the box, keeping it inside the picture.\n * @param rect - the current box.\n * @param dx - pointer delta in picture pixels.\n * @param dy - pointer delta in picture pixels.\n * @param natural - the picture's natural size.\n * @returns the moved box.\n */\nexport function moveCropRect(rect: CropRect, dx: number, dy: number, natural: { width: number; height: number }): CropRect {\n return clampRect({ ...rect, x: rect.x + dx, y: rect.y + dy }, natural)\n}\n\n/**\n * Resize the box by one handle.\n *\n * The anchor (the opposite edge/corner) stays put, which is what makes dragging a\n * handle feel like resizing rather than moving; with a fixed ratio the box grows\n * along the dominant axis and stays inside the picture.\n *\n * @param rect - the current box.\n * @param handle - which handle is being dragged.\n * @param dx - pointer delta in picture pixels.\n * @param dy - pointer delta in picture pixels.\n * @param natural - the picture's natural size.\n * @param aspect - width / height, or null for a free box.\n * @returns the resized box.\n */\nexport function resizeCropRect(\n rect: CropRect,\n handle: CropHandle,\n dx: number,\n dy: number,\n natural: { width: number; height: number },\n aspect: number | null,\n): CropRect {\n const axes = HANDLE_AXES[handle]\n let width = axes.x === 0 ? rect.width : rect.width + dx * axes.x\n let height = axes.y === 0 ? rect.height : rect.height + dy * axes.y\n if (aspect !== null) {\n // Grow along whichever axis moved more, then derive the other from the ratio.\n const byWidth = { width, height: width / aspect }\n const byHeight = { width: height * aspect, height }\n const chosen = Math.abs(dx) >= Math.abs(dy) ? byWidth : byHeight\n width = chosen.width\n height = chosen.height\n }\n width = Math.max(MIN_CROP_SIZE, width)\n height = Math.max(MIN_CROP_SIZE, height)\n // The anchor is the edge the dragged handle is not touching.\n const x = axes.x === 0 ? rect.x : axes.x < 0 ? rect.x + rect.width - width : rect.x\n const y = axes.y === 0 ? rect.y : axes.y < 0 ? rect.y + rect.height - height : rect.y\n return clampRect({ x, y, width, height }, natural)\n}\n\n/**\n * The displayed position of the box inside a node.\n *\n * @param rect - the box in picture pixels.\n * @param node - the node's displayed size on screen.\n * @param natural - the picture's natural size.\n * @returns screen-space offsets relative to the node's top-left corner.\n */\nexport function cropRectToNode(\n rect: CropRect,\n node: { width: number; height: number },\n natural: { width: number; height: number },\n): CropRect {\n const scaleX = natural.width === 0 ? 1 : node.width / natural.width\n const scaleY = natural.height === 0 ? 1 : node.height / natural.height\n return {\n x: rect.x * scaleX,\n y: rect.y * scaleY,\n width: rect.width * scaleX,\n height: rect.height * scaleY,\n }\n}\n\n/**\n * Where a crop's new node goes: to the right of the original, same top edge.\n *\n * Non-destructive by construction — the crop is a NEW node next to the picture it\n * came from, so the user can compare them.\n *\n * @param source - the original card's board rectangle.\n * @param size - the new node's size.\n * @returns the new node's top-left corner.\n */\nexport function cropNodePosition(\n source: { x: number; y: number; width: number },\n size: { width: number },\n): { x: number; y: number } {\n return { x: source.x + source.width + 24, y: source.y }\n}\n\n/**\n * The displayed size of a crop's node: the crop's own aspect ratio, at a size\n * comparable to the picture it came from.\n *\n * @param rect - the crop box in picture pixels.\n * @param maxWidth - the widest the new node may be on the board.\n * @returns the node's board size.\n */\nexport function croppedNodeSize(rect: CropRect, maxWidth = 320): { width: number; height: number } {\n const aspect = rect.height === 0 ? 1 : rect.width / rect.height\n const width = Math.max(48, Math.min(maxWidth, rect.width))\n return { width: Math.round(width), height: Math.round(width / aspect) }\n}\n","/**\n * In-place crop mode for one board node.\n *\n * The crop box is drawn over the node itself (not in a dialog), because cropping\n * is a local framing decision and a modal would break the board context — the\n * reference client's ADR-0016 moved it here for exactly that reason. The box is\n * stored in the picture's own pixels and rendered through `crop-rect`'s mapping,\n * so nothing here depends on the board's zoom.\n *\n * Confirming produces a NEW node (`onConfirm` receives the box); the original card\n * and the material library are untouched.\n */\n\nimport { useRef, useState } from 'react'\nimport {\n CROP_ASPECTS,\n CROP_HANDLES,\n cropRectToNode,\n defaultCropRect,\n moveCropRect,\n resizeCropRect,\n type CropHandle,\n type CropRect,\n} from './crop-rect.ts'\nimport { Button } from './controls.tsx'\nimport css from './crop-overlay.module.css'\n\n/** What the crop overlay needs to know about the node it sits on. */\nexport interface CropOverlayProps {\n /** The node's rectangle on the board (board coordinates). */\n node: { x: number; y: number; width: number; height: number }\n /** The picture's natural size, for the three-layer mapping. */\n natural: { width: number; height: number }\n /** Crop the picture with this box (picture pixels). */\n onConfirm: (rect: CropRect) => void\n /** Leave crop mode without producing anything. */\n onCancel: () => void\n}\n\n/** Which direction each handle drags, for the cursor only. */\nconst HANDLE_CURSORS: Record<CropHandle, string> = {\n nw: 'nwse-resize',\n n: 'ns-resize',\n ne: 'nesw-resize',\n e: 'ew-resize',\n se: 'nwse-resize',\n s: 'ns-resize',\n sw: 'nesw-resize',\n w: 'ew-resize',\n}\n\n/**\n * The crop box and its toolbar for one node.\n * @param props - the node, the picture size, and what to do on confirm/cancel.\n * @returns the overlay.\n */\nexport function CropOverlay(props: CropOverlayProps): JSX.Element {\n const [aspect, setAspect] = useState<number | null>(null)\n const [rect, setRect] = useState<CropRect>(() => defaultCropRect(props.natural, null))\n const dragRef = useRef<{ mode: 'move' | CropHandle; startX: number; startY: number; startRect: CropRect } | undefined>(undefined)\n\n /** Picture pixels per screen pixel at the current board scale. */\n const scale = props.node.width === 0 ? 1 : props.natural.width / props.node.width\n const displayed = cropRectToNode(rect, props.node, props.natural)\n\n const beginDrag = (event: React.PointerEvent, mode: 'move' | CropHandle): void => {\n event.preventDefault()\n event.stopPropagation()\n dragRef.current = { mode, startX: event.clientX, startY: event.clientY, startRect: rect }\n const onMove = (moveEvent: PointerEvent): void => {\n const drag = dragRef.current\n if (drag === undefined) return\n const dx = (moveEvent.clientX - drag.startX) * scale\n const dy = (moveEvent.clientY - drag.startY) * scale\n setRect(drag.mode === 'move'\n ? moveCropRect(drag.startRect, dx, dy, props.natural)\n : resizeCropRect(drag.startRect, drag.mode, dx, dy, props.natural, aspect))\n }\n const onUp = (): void => {\n dragRef.current = undefined\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onUp)\n }\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n window.addEventListener('pointercancel', onUp)\n }\n\n return (\n <>\n {/* The box lives over the node: three-thirds guides, a dimmed outside is\n deliberately omitted so the user still sees what they are cutting. */}\n <div\n className={css.box}\n data-dsh-seework-crop=\"\"\n style={{\n left: `${props.node.x + displayed.x}px`,\n top: `${props.node.y + displayed.y}px`,\n width: `${displayed.width}px`,\n height: `${displayed.height}px`,\n }}\n onPointerDown={event => { beginDrag(event, 'move') }}\n >\n <span className={css.thirdV} />\n <span className={css.thirdV} data-second=\"\" />\n <span className={css.thirdH} />\n <span className={css.thirdH} data-second=\"\" />\n {CROP_HANDLES.map(handle => (\n <span\n key={handle}\n className={css.handle}\n data-handle={handle}\n style={{ cursor: HANDLE_CURSORS[handle] }}\n onPointerDown={event => { beginDrag(event, handle) }}\n />\n ))}\n </div>\n\n <div\n className={css.toolbar}\n data-dsh-seework-crop-toolbar=\"\"\n style={{ left: `${props.node.x}px`, top: `${props.node.y + props.node.height + 12}px` }}\n onPointerDown={event => { event.stopPropagation() }}\n >\n <select\n className={css.select}\n value={String(aspect)}\n title=\"裁剪比例\"\n onChange={event => {\n const value = event.target.value === 'null' ? null : Number(event.target.value)\n setAspect(value)\n setRect(defaultCropRect(props.natural, value))\n }}\n >\n {CROP_ASPECTS.map(option => (\n <option key={option.label} value={String(option.value)}>{option.label}</option>\n ))}\n </select>\n <span className={css.size}>{Math.round(rect.width)}×{Math.round(rect.height)}</span>\n <Button onClick={props.onCancel}>取消</Button>\n <Button variant=\"primary\" data-dsh-seework-crop-confirm=\"\" onClick={() => { props.onConfirm(rect) }}>裁剪</Button>\n </div>\n </>\n )\n}\n","/**\n * 「加入到对话框」: put a picture into the conversation's composer as a draft\n * attachment.\n *\n * The composer's attachments are browser-owned drafts: the shell registers the\n * file, hands back a draft id, and the input machine shows it in the rail with\n * its bytes sent along with the prompt (`createDrafts` + `addAttachments` on the\n * conversation service / the session's input actions). So this needs no host\n * route and no library write: the bytes come from the picture's own URL.\n *\n * Where the face comes from: the shell hands `sessionId` and `inputActions` to\n * every session-scope slot component, and our library/canvas tab bodies are\n * exactly that — so they publish the face while they are mounted, and the\n * conversation service is probed once from the plugin root (`ctx.get`, never a\n * declared `inject`: a missing provider must not park the whole plugin).\n *\n * Without a mounted tab there is no session context, so the buttons hide rather\n * than pretend: the home-screen floating panels have no conversation to add to.\n */\n\n/** The slice of the conversation service this module uses. */\nexport interface ComposerDrafts {\n /**\n * Register browser files as runtime-only draft attachments.\n * @param sessionId - target session.\n * @param files - browser files to register.\n * @returns ordered draft descriptors.\n */\n createDrafts: (sessionId: string, files: readonly File[]) => readonly { id: string }[]\n /** Release drafts that never made it into the composer. */\n releaseDraftAttachments: (attachments: readonly unknown[]) => void\n}\n\n/** The slice of the session input actions this module uses. */\nexport interface ComposerInput {\n /** Append ordered browser-owned attachment ids; a busy composer refuses. */\n addAttachments: (ids: readonly string[]) => boolean\n}\n\n/** A session-scope tab body publishes this while it is mounted. */\nexport interface ComposerFace {\n sessionId: string\n input: ComposerInput\n}\n\nlet conversationProbe: (() => ComposerDrafts | undefined) | undefined\nlet face: ComposerFace | undefined\n\n/**\n * Install the probe that resolves the conversation service (plugin root, once).\n *\n * A probe rather than the service itself: plugins attach in boot order, and this\n * one applies before the conversation plugin may have registered — resolving at\n * call time costs nothing and cannot go stale.\n *\n * @param next - the probe, or undefined when the page has no conversation.\n * @returns disposer restoring the previous probe.\n */\nexport function setComposerProbe(next: (() => ComposerDrafts | undefined) | undefined): () => void {\n const previous = conversationProbe\n conversationProbe = next\n return () => { conversationProbe = previous }\n}\n\n/**\n * Publish (or clear) the session-scope face a tab body owns.\n * @param next - the face, or undefined when no tab body is mounted.\n * @returns disposer clearing this exact face.\n */\nexport function setComposerFace(next: ComposerFace | undefined): () => void {\n const previous = face\n face = next\n return () => {\n if (face === next) face = previous\n }\n}\n\n/** Whether 「加入到对话框」 can do anything right now. */\nexport function composerAvailable(): boolean {\n return conversationProbe?.() !== undefined && face !== undefined\n}\n\n/**\n * Send one picture to the composer as a draft attachment.\n *\n * The picture is fetched from its own URL (a library image or a canvas asset),\n * turned into a browser `File`, and registered as a draft; the draft id then\n * goes into the session's input rail. A composer that refuses the id (a busy\n * submission) leaves nothing behind — the draft is released again.\n *\n * @param image - the picture's URL and the name its attachment should carry.\n * @returns true when the draft reached the composer.\n */\nexport async function sendImageToConversation(image: { url: string; name?: string | undefined }): Promise<boolean> {\n const service = conversationProbe?.()\n const current = face\n if (service === undefined || current === undefined) return false\n const response = await fetch(image.url)\n if (!response.ok) return false\n const blob = await response.blob()\n const name = image.name ?? 'seework-image.png'\n const file = new File([blob], name, { type: blob.type === '' ? 'image/png' : blob.type })\n const drafts = service.createDrafts(current.sessionId, [file])\n const ids = drafts.map(draft => draft.id)\n if (ids.length === 0) return false\n if (!current.input.addAttachments(ids)) {\n // A busy composer refuses the ids; hand the drafts back rather than leaking\n // object URLs the composer will never show.\n service.releaseDraftAttachments(drafts)\n console.warn('[dsh-seework] the composer refused the draft attachment.')\n return false\n }\n return true\n}\n\n/**\n * The attachment name for one library file: it keeps the extension the route\n * serves, so the composer's preview and the eventual prompt agree on the type.\n * @param file - the library or asset file name.\n * @returns a browser-safe file name.\n */\nexport function attachmentNameFor(file: string): string {\n const base = file.split(/[\\\\/]/).pop() ?? 'seework-image.png'\n return base === '' ? 'seework-image.png' : base\n}\n","/**\n * Where a node's floating command bar sits.\n *\n * Pure maths (no React, no DOM) so it can be tested without a browser: the bar\n * hugs the node's top edge, flips below it when there is not enough room above,\n * and clamps horizontally inside the visible stage so it never runs off an edge.\n * Mirrors the reference client's own helper (seeaitv `utils/floatingBarPosition`),\n * because the interaction it serves is the same one: click a node, get a bar\n * right there.\n */\n\nimport { boardToScreen } from '../canvas-viewport.ts'\nimport type { CanvasViewport } from '../protocol.ts'\n\n/** Vertical gap between the bar and the node it belongs to (screen px). */\nexport const FLOATING_BAR_GAP = 8\n\n/** Minimum horizontal distance from the stage's edges (screen px). */\nexport const FLOATING_BAR_EDGE_MARGIN = 8\n\n/** Which side of the node the bar ended up on (a caller may style the arrow). */\nexport type FloatingBarPlacement = 'above' | 'below'\n\n/** The node rectangle, in board coordinates. */\nexport interface FloatingBarNode {\n x: number\n y: number\n width: number\n height: number\n}\n\n/**\n * Place the bar for one node.\n *\n * @param node - the node's rectangle in board coordinates.\n * @param viewport - the board's viewport.\n * @param barSize - the bar's measured size in screen pixels.\n * @param stageSize - the visible stage size in screen pixels.\n * @returns absolute left/top inside the stage, and which side it landed on.\n */\nexport function floatingBarPosition(\n node: FloatingBarNode,\n viewport: CanvasViewport,\n barSize: { width: number; height: number },\n stageSize: { width: number; height: number },\n): { left: number; top: number; placement: FloatingBarPlacement } {\n const topLeft = boardToScreen(viewport, node.x, node.y)\n const bottomRight = boardToScreen(viewport, node.x + node.width, node.y + node.height)\n const centreX = (topLeft.x + bottomRight.x) / 2\n\n // Above by default; below when the bar would cross the stage's top edge.\n const placement: FloatingBarPlacement =\n topLeft.y - FLOATING_BAR_GAP - barSize.height < 0 ? 'below' : 'above'\n const top = placement === 'above'\n ? topLeft.y - FLOATING_BAR_GAP - barSize.height\n : bottomRight.y + FLOATING_BAR_GAP\n\n const minLeft = FLOATING_BAR_EDGE_MARGIN\n const maxLeft = Math.max(minLeft, stageSize.width - FLOATING_BAR_EDGE_MARGIN - barSize.width)\n const left = Math.min(Math.max(centreX - barSize.width / 2, minLeft), maxLeft)\n\n return { left, top, placement }\n}\n","/**\n * The floating command bar that appears when a board node is selected.\n *\n * Clicking an image on the canvas is the moment a user wants to act on THAT\n * picture, so the actions live right next to it instead of in a distant toolbar.\n * The bar is HTML over the board (not part of the scaled world layer), measured\n * after every render, and positioned by {@link floatingBarPosition} — the same\n * interaction the reference client uses.\n *\n * Batch 1 carries the one action that exists so far, 「加入到对话框」 (put the\n * picture back into the composer as a draft attachment); the 标注 / 裁剪 tools\n * join the same bar in the next batch.\n */\n\nimport { useLayoutEffect, useRef, useState } from 'react'\nimport type { CanvasViewport } from '../protocol.ts'\nimport { floatingBarPosition, type FloatingBarNode } from './floating-bar.ts'\nimport css from './node-floating-bar.module.css'\n\n/** What the bar offers for the node it belongs to. */\nexport interface NodeFloatingBarProps {\n /** The selected node, in board coordinates. */\n node: FloatingBarNode\n /** The board's viewport. */\n viewport: CanvasViewport\n /** The visible stage size, measured live by the board. */\n stageSize: { width: number; height: number }\n /** Put this picture into the conversation's composer. */\n onAddToConversation?: (() => boolean | Promise<boolean>) | undefined\n /** Mark up this picture (开门标注编辑器). */\n onAnnotate?: (() => void) | undefined\n /** Crop this picture in place (produces a new node). */\n onCrop?: (() => void) | undefined\n}\n\n/** One button in the bar. */\nfunction BarButton({ label, title, onClick, disabled = false }: {\n label: string\n title: string\n onClick?: (() => void) | undefined\n disabled?: boolean\n}): JSX.Element {\n return (\n <button\n type=\"button\"\n className={css.button}\n title={title}\n aria-label={title}\n disabled={disabled}\n onClick={event => {\n event.stopPropagation()\n onClick?.()\n }}\n >\n {label}\n </button>\n )\n}\n\n/**\n * The bar for one selected node.\n * @param props - the node, the viewport, the stage size, and the actions.\n * @returns the bar, positioned inside the stage.\n */\nexport function NodeFloatingBar(props: NodeFloatingBarProps): JSX.Element {\n const barRef = useRef<HTMLDivElement | null>(null)\n const [size, setSize] = useState({ width: 0, height: 0 })\n const [outcome, setOutcome] = useState<'idle' | 'failed'>('idle')\n\n // Measure after every render: the labels are localized text, so the width is\n // not knowable up front, and a wrong width would clamp against the wrong edge.\n useLayoutEffect(() => {\n const element = barRef.current\n if (element === null) return\n const rect = element.getBoundingClientRect()\n setSize(previous => previous.width === rect.width && previous.height === rect.height\n ? previous\n : { width: rect.width, height: rect.height })\n })\n\n const position = floatingBarPosition(props.node, props.viewport, size, props.stageSize)\n return (\n <div\n ref={barRef}\n className={css.bar}\n data-dsh-seework-node-bar=\"\"\n data-placement={position.placement}\n style={{\n left: `${position.left}px`,\n top: `${position.top}px`,\n // Hidden until measured: an unmeasured bar would flash at the corner.\n visibility: size.width > 0 ? 'visible' : 'hidden',\n }}\n onPointerDown={event => { event.stopPropagation() }}\n onMouseDown={event => { event.stopPropagation() }}\n >\n <BarButton\n label={outcome === 'failed' ? '放不进输入框' : '加入到对话框'}\n title={outcome === 'failed'\n ? '没能放进输入框:请确认当前会话的输入框可用'\n : '把这张图放进对话输入框当草稿附件'}\n disabled={props.onAddToConversation === undefined}\n onClick={() => {\n const run = props.onAddToConversation\n if (run === undefined) return\n void Promise.resolve(run()).then(ok => {\n // The bar is the only feedback here: the composer may be off screen.\n if (ok !== false) return\n setOutcome('failed')\n setTimeout(() => { setOutcome('idle') }, 2500)\n })\n }}\n />\n <BarButton\n label=\"标注\"\n title=\"在图上画矩形 / 涂抹 / 写字,保存成一张新图(原图不变)\"\n onClick={props.onAnnotate}\n disabled={props.onAnnotate === undefined}\n />\n <BarButton\n label=\"裁剪\"\n title=\"就地裁剪这张图(生成新卡片,原图保留)\"\n onClick={props.onCrop}\n disabled={props.onCrop === undefined}\n />\n </div>\n )\n}\n","/**\n * A minimal observable snapshot store.\n *\n * The shell's `dsh-client-store` package is not in the shared module baseline,\n * so depending on it would either duplicate the store machinery inside this\n * bundle or drag in its zustand/immer tree. The settings bridge needs only\n * \"read the current value, subscribe, replace or patch it\", which is this file.\n */\n\n/** One observable value with React-friendly snapshot semantics. */\nexport class SnapshotStore<T> {\n private value: T\n private readonly listeners = new Set<() => void>()\n\n constructor(initial: T) {\n this.value = initial\n }\n\n /** @returns the current value; the reference changes on every update. */\n getSnapshot(): T {\n return this.value\n }\n\n /** @returns the disposer removing this listener. */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n /** Replace the value and notify listeners. */\n set(next: T): void {\n this.value = next\n this.notify()\n }\n\n /** Patch the value with a mutator that receives a detached copy. */\n update(mutator: (draft: T) => void): void {\n const draft = { ...(this.value as unknown as Record<string, unknown>) } as unknown as T\n mutator(draft)\n this.value = draft\n this.notify()\n }\n\n private notify(): void {\n for (const listener of [...this.listeners]) listener()\n }\n}\n","/**\n * Browser-side canvas access: board listing, the open board, and autosave.\n *\n * The board is a spatial document (see `docs/architecture.md`), so the\n * interesting part is not the model but the save protocol:\n *\n * - edits touch the in-memory document and mark it dirty;\n * - a debounced save sends the document with the revision it was read at;\n * - a conflict (another window saved first) re-reads the board, adopts that\n * revision, and retries — the user's board is the working copy, so it is\n * never replaced by the remote document;\n * - a failed save keeps the document dirty so the next tick retries.\n *\n * Nothing here draws anything: the board component renders whatever this store\n * holds, so the same document can be saved, reloaded, or thrown away without\n * the view knowing.\n */\n\nimport type { CanvasCard, CanvasDocument, CanvasSummary, CanvasViewport } from '../protocol.ts'\nimport { SeeWorkApi } from './api.ts'\nimport { SnapshotStore } from './snapshot-store.ts'\n\n/** How long edits settle before a save is attempted. */\nexport const AUTOSAVE_DELAY_MS = 800\n\n/** Canvas state as the workspace renders it. */\nexport interface CanvasState {\n status: 'loading' | 'ready' | 'error'\n boards: CanvasSummary[]\n /** The open board, when one is open. */\n board?: CanvasDocument\n /** Whether the open board has edits that are not stored yet. */\n dirty: boolean\n /** `saving` is in flight; `conflict` needs the user to look; `error` is a failure. */\n save: 'idle' | 'saving' | 'saved' | 'conflict' | 'error'\n /** Human-readable reason for the current save state. */\n message?: string\n}\n/** Create a card id (board-local, only needs to be unique within one board). */\nexport function newCardId(): string {\n const cryptoObj: Crypto | undefined = globalThis.crypto\n if (cryptoObj?.randomUUID !== undefined) return cryptoObj.randomUUID()\n return `card-${Date.now().toString(36)}-${Math.round(Math.random() * 1e6).toString(36)}`\n}\n\n/** The board store. */\nexport class CanvasStore {\n private readonly store = new SnapshotStore<CanvasState>({ status: 'loading', boards: [], dirty: false, save: 'idle' })\n private readonly api: SeeWorkApi\n private saveTimer: ReturnType<typeof setTimeout> | undefined\n private saveInFlight = false\n /**\n * Monotonic local-edit counter, and the fence a save response is judged by.\n *\n * The host echoes the document it stored; that echo is a snapshot of what was\n * sent. Any edit made AFTER the request went out is newer than the echo, and\n * comparing this counter is how the response knows not to overwrite it.\n */\n private editSeq = 0\n private disposed = false\n\n constructor(api: SeeWorkApi) {\n this.api = api\n }\n\n /**\n * The route client this store writes through.\n *\n * Exposed for the board's own picture work (a burned annotation, a crop): those\n * write to the canvas asset store, and threading a second client through every\n * mount point just to upload one image would be worse.\n */\n get client(): SeeWorkApi {\n return this.api\n }\n\n getSnapshot(): CanvasState {\n return this.store.getSnapshot()\n }\n\n subscribe(listener: () => void): () => void {\n return this.store.subscribe(listener)\n }\n\n /** Stop the autosave timer (the surface is going away). */\n dispose(): void {\n this.disposed = true\n if (this.saveTimer !== undefined) clearTimeout(this.saveTimer)\n this.saveTimer = undefined\n }\n\n /** Re-read the board list; the open board is left alone. */\n async refreshList(): Promise<void> {\n const result = await this.api.canvases()\n if (this.disposed) return\n if (!result.ok) {\n this.store.update(draft => {\n draft.status = 'error'\n draft.message = result.message\n })\n return\n }\n this.store.update(draft => {\n draft.status = 'ready'\n draft.boards = result.value.canvases\n delete draft.message\n })\n }\n\n /**\n * Open a board.\n * @param id - board id, or '' to create a fresh one.\n * @returns the board that is now open, or undefined when it could not be opened.\n */\n async open(id = ''): Promise<CanvasDocument | undefined> {\n const result = id === '' ? await this.api.createCanvas() : await this.api.readCanvas(id)\n if (this.disposed) return undefined\n if (!result.ok) {\n this.store.update(draft => {\n draft.status = 'error'\n draft.message = result.message\n })\n return undefined\n }\n const board = result.value.canvas\n this.store.update(draft => {\n draft.board = board\n draft.dirty = false\n draft.save = 'idle'\n delete draft.message\n })\n void this.refreshList()\n return board\n }\n\n /** Close the open board, saving first when it has unsaved edits. */\n async close(): Promise<void> {\n if (this.store.getSnapshot().dirty) await this.saveNow()\n this.store.update(draft => {\n delete draft.board\n draft.dirty = false\n draft.save = 'idle'\n delete draft.message\n })\n }\n\n /** Delete a board (closing it first when it is the open one). */\n async deleteBoard(id: string): Promise<void> {\n if (this.store.getSnapshot().board?.id === id) await this.close()\n const result = await this.api.removeCanvas(id)\n if (this.disposed) return\n if (!result.ok) {\n this.store.update(draft => {\n draft.save = 'error'\n draft.message = `删除失败:${result.message}`\n })\n return\n }\n this.store.update(draft => { draft.boards = result.value.canvases })\n }\n\n /** Replace the board document as part of an edit. */\n private edit(mutate: (board: CanvasDocument) => void): void {\n const current = this.store.getSnapshot().board\n if (current === undefined) return\n // A shallow copy keeps React's identity checks meaningful while the nested\n // arrays are replaced by the callers that touch them.\n const next: CanvasDocument = { ...current, cards: [...current.cards] }\n mutate(next)\n this.editSeq += 1\n this.store.update(draft => {\n draft.board = next\n draft.dirty = true\n if (draft.save !== 'conflict') draft.save = 'idle'\n })\n this.scheduleSave()\n }\n\n /** Move, resize or restack one card. */\n updateCard(id: string, patch: Partial<CanvasCard>): void {\n this.edit(board => {\n board.cards = board.cards.map(card => card.id === id ? { ...card, ...patch } : card)\n })\n }\n\n /** Remove one card. */\n removeCard(id: string): void {\n this.edit(board => {\n board.cards = board.cards.filter(card => card.id !== id)\n })\n }\n\n /** Place new cards on the board, on top of everything already there. */\n addCards(cards: CanvasCard[]): void {\n if (cards.length === 0) return\n this.edit(board => {\n const top = board.cards.reduce((highest, card) => Math.max(highest, card.z), 0)\n board.cards = [...board.cards, ...cards.map((card, index) => ({ ...card, z: top + index + 1 }))]\n })\n }\n\n /** Move the viewport (and remember it). */\n setViewport(viewport: CanvasViewport): void {\n this.edit(board => { board.viewport = viewport })\n }\n\n /** Rename the board. */\n setTitle(title: string): void {\n this.edit(board => { board.title = title })\n }\n\n /** Save immediately, cancelling the debounce. */\n async saveNow(): Promise<void> {\n if (this.saveTimer !== undefined) {\n clearTimeout(this.saveTimer)\n this.saveTimer = undefined\n }\n await this.save()\n }\n\n /** Queue a save after the edits settle. */\n private scheduleSave(): void {\n if (this.saveTimer !== undefined) clearTimeout(this.saveTimer)\n this.saveTimer = setTimeout(() => {\n this.saveTimer = undefined\n void this.save()\n }, AUTOSAVE_DELAY_MS)\n }\n\n /**\n * Write the open board.\n *\n * A save that fails or conflicts must never lose the local edit: the document\n * stays in memory and dirty, and the user is told what happened.\n */\n private async save(): Promise<void> {\n const snapshot = this.store.getSnapshot()\n const board = snapshot.board\n if (board === undefined || !snapshot.dirty) return\n if (this.saveInFlight) {\n // Coalesce: the in-flight response re-schedules when it sees that edits\n // arrived after its request went out (see `changedSince` below).\n return\n }\n this.saveInFlight = true\n const sentAt = this.editSeq\n this.store.update(draft => { draft.save = 'saving' })\n const reading = board.revision\n const result = await this.api.saveCanvas(board, reading)\n this.saveInFlight = false\n if (this.disposed) return\n if (result.ok) {\n const changedSince = this.editSeq !== sentAt\n this.store.update(draft => {\n // The host's reply is the document it STORED, i.e. a snapshot of what\n // was sent. The local document is the working copy, so only its\n // revision comes back from that reply: adopting the echo wholesale\n // would silently drop every edit made after the request left — a\n // viewport change (\"适应内容\") or a card move included — and marking the\n // board clean would then stop it from ever being saved.\n if (draft.board !== undefined) {\n draft.board = { ...draft.board, revision: result.value.canvas.revision }\n }\n draft.dirty = changedSince\n draft.save = changedSince ? 'idle' : 'saved'\n })\n if (changedSince) this.scheduleSave()\n void this.refreshList()\n return\n }\n if (result.code === 'canvas_conflict') {\n // Another window saved first. The user's board stays exactly as they left\n // it — this is their working copy, and replacing it with the remote\n // document would throw their layout away. What changes is the revision we\n // fence on, which the re-read supplies; the retry then persists the\n // user's whole board, which is what \"last writer wins\" means for a\n // deliberately single-document board.\n const reread = await this.api.readCanvas(board.id)\n if (this.disposed) return\n const current = this.store.getSnapshot().board\n this.store.update(draft => {\n draft.save = 'conflict'\n draft.dirty = true\n draft.message = reread.ok\n ? '这块画布在别处也被改过:正在把你的版本保存为最新版本。'\n : '这块画布在别处也被改过,且重新读取失败;改动还在这里,稍后会自动重试。'\n if (reread.ok && current !== undefined) {\n draft.board = { ...current, revision: reread.value.canvas.revision }\n }\n })\n if (reread.ok) {\n // Retry immediately on the fresh revision so the user is not left\n // staring at a conflict they cannot act on.\n await this.save()\n }\n return\n }\n this.store.update(draft => {\n draft.save = 'error'\n draft.message = `保存失败:${result.message}`\n draft.dirty = true\n })\n }\n}\n\n/** A card id that does not collide with what is already on the board. */\nexport function unusedCardId(board: CanvasDocument | undefined): string {\n const existing = new Set((board?.cards ?? []).map(card => card.id))\n for (let attempt = 0; attempt < 10; attempt++) {\n const id = newCardId()\n if (!existing.has(id)) return id\n }\n return `card-${Date.now().toString(36)}`\n}\n","/**\n * The SeeWork canvas: a free spatial board.\n *\n * Deliberately not a node graph — there are no ports and no connections. A card\n * is a rectangle at a coordinate; the user arranges pictures and notes by hand,\n * which is what SeeAI TV's board does and what the user asked for. Cards come\n * from the material library (the images already generated) or are plain notes.\n *\n * All pointer handling goes through `canvas-viewport`'s linear map, so dragging\n * and zooming stay correct at any zoom level instead of drifting.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { CANVAS_NEW_IMAGE_SIZE } from '../canvas-limits.ts'\nimport { centeredImageCard, stageCentre } from '../canvas-placement.ts'\nimport { boardToScreen, fitViewport, panBy, zoomAt, zoomTo } from '../canvas-viewport.ts'\nimport { canvasImageUrl, canvasOriginLabel, type CanvasCard, type CanvasCardOrigin, type CanvasViewport, type LibraryEntry } from '../protocol.ts'\nimport { AnnotationEditor } from './AnnotationEditor.tsx'\nimport { CropOverlay } from './CropOverlay.tsx'\nimport { burnAnnotations, burnCrop } from './burn-image.ts'\nimport { attachmentNameFor, sendImageToConversation } from './composer-draft.ts'\nimport { cropNodePosition, croppedNodeSize, type CropRect } from './crop-rect.ts'\nimport { NodeFloatingBar } from './NodeFloatingBar.tsx'\nimport { Button, TextInput } from './controls.tsx'\nimport { unusedCardId, type CanvasState, type CanvasStore } from './canvas-store.ts'\nimport type { CanvasStore as CanvasStoreType } from './canvas-store.ts'\nimport css from './canvas.module.css'\n\n/** How far a click must travel before it counts as a drag. */\nconst DRAG_THRESHOLD_PX = 3\n\n/** How much of a card must be on screen before the layer list leaves the view alone. */\nconst LAYER_REVEAL_MARGIN = 24\n\n/**\n * How long a housekeeping notice stays up.\n *\n * It reports a finished action (\"已清理 4 张…\"), not a state the user has to keep\n * reading — a notice bar that never goes away just eats the board (user report:\n * 「一直存在提示条,不消失」).\n */\nexport const NOTICE_MS = 4000\n\n/** A byte count for a notice, e.g. `12.4 MB`. */\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\n/**\n * What the ✕ on one card promises.\n *\n * Only the board's own pictures are deleted with the card; a library picture is\n * the record of a generation, and saying otherwise would be a lie the user acts on.\n *\n * @param card - the card about to be removed.\n * @returns the tooltip text.\n */\nfunction removeHint(card: CanvasCard): string {\n if (card.source === 'canvas') return '从画布移除(这张合成图的文件也会一起删掉)'\n if (card.kind === 'image') return '从画布移除(不会删除素材库里的文件)'\n return '从画布移除'\n}\n\n/**\n * One line of the layer list.\n * @param card - the card.\n * @returns a short description (its prompt, its note, or its file name).\n */\nfunction layerName(card: CanvasCard): string {\n const text = (card.kind === 'text' ? card.text : card.prompt) ?? ''\n const trimmed = text.trim()\n if (trimmed !== '') return trimmed\n return card.kind === 'text' ? '(空备注)' : card.file ?? '图片'\n}\n\n/**\n * Visible stage size per board store, as the board last measured it.\n *\n * The conversation's 「加到画布」 runs outside any board render, so the size is\n * recorded here instead of being asked of the DOM from there.\n */\nconst stageSizes = new WeakMap<object, { width: number; height: number }>()\n\n/**\n * Record the stage size the board just measured.\n * @param store - the board's store.\n * @param size - the stage size in screen pixels.\n */\nexport function publishStageSize(store: CanvasStoreType, size: { width: number; height: number }): void {\n stageSizes.set(store, size)\n}\n\n/**\n * The last stage size a board measured.\n * @param store - the board's store.\n * @returns the size, or zeros before the board has rendered.\n */\nexport function stageSizeFor(store: CanvasStoreType): { width: number; height: number } {\n return stageSizes.get(store) ?? { width: 0, height: 0 }\n}\n\n/** Pointer gesture currently in progress. */\ntype Gesture =\n | { kind: 'pan'; lastX: number; lastY: number }\n | { kind: 'move'; cardId: string; startX: number; startY: number; cardX: number; cardY: number; moved: boolean }\n | { kind: 'resize'; cardId: string; startX: number; startY: number; width: number; height: number; moved: boolean }\n\n/** Props for the canvas workspace. */\nexport interface CanvasBoardProps {\n store: CanvasStore\n /** The material library, for picking pictures to place. */\n library: { entries: LibraryEntry[] }\n /** Load the library (the picker needs it even if the drawer was never opened). */\n onNeedLibrary: () => void\n}\n\n/** One node's rectangle in stage (screen) coordinates. */\nfunction nodeScreenRect(\n card: CanvasCard,\n viewport: CanvasViewport,\n): { x: number; y: number; width: number; height: number } {\n const topLeft = boardToScreen(viewport, card.x, card.y)\n return { x: topLeft.x, y: topLeft.y, width: card.width * viewport.k, height: card.height * viewport.k }\n}\n\n/** A new node's board size for one burned picture: its own aspect, comparable width. */\nfunction sizeKeepingAspect(\n referenceWidth: number,\n width: number,\n height: number,\n): { width: number; height: number } {\n const aspect = height === 0 ? 1 : width / height\n const nodeWidth = Math.max(64, Math.min(480, referenceWidth))\n return { width: Math.round(nodeWidth), height: Math.round(nodeWidth / aspect) }\n}\n\n/** Read a burned data URL's real pixel size (the new node is sized from it). */\nasync function loadBurnedSize(dataUrl: string): Promise<{ dataUrl: string; width: number; height: number }> {\n return new Promise(resolve => {\n const image = new Image()\n image.onload = () => { resolve({ dataUrl, width: image.naturalWidth, height: image.naturalHeight }) }\n image.onerror = () => { resolve({ dataUrl, width: 0, height: 0 }) }\n image.src = dataUrl\n })\n}\n\n/** The canvas workspace: toolbar + board + library picker. */export function CanvasBoard({ store, library, onNeedLibrary }: CanvasBoardProps): JSX.Element {\n const [tick, setTick] = useState(0)\n useEffect(() => store.subscribe(() => { setTick(value => value + 1) }), [store])\n void tick\n const state = store.getSnapshot()\n const board = state.board\n const api = store.client\n\n const surfaceRef = useRef<HTMLDivElement | null>(null)\n const gestureRef = useRef<Gesture | null>(null)\n const [gestureTick, setGestureTick] = useState(0)\n const [pickerOpen, setPickerOpen] = useState(false)\n const [selected, setSelected] = useState<string | undefined>(undefined)\n /** The card whose marks are being edited (modal), or undefined. */\n const [annotating, setAnnotating] = useState<string | undefined>(undefined)\n /** The card in crop mode (in place), or undefined. */\n const [cropping, setCropping] = useState<string | undefined>(undefined)\n /** The picture behind the active edit mode, with the size it really is. */\n const [modePicture, setModePicture] = useState<{ url: string; natural: { width: number; height: number } } | undefined>(undefined)\n const [busy, setBusy] = useState(false)\n /** What the last housekeeping action did (or why it failed). */\n const [assetNotice, setAssetNotice] = useState<string | undefined>(undefined)\n const noticeTimer = useRef<number | undefined>(undefined)\n /** Show a housekeeping notice, replacing any previous one (and any pending fade). */\n const showAssetNotice = useCallback((text: string): void => {\n if (noticeTimer.current !== undefined) window.clearTimeout(noticeTimer.current)\n setAssetNotice(text)\n noticeTimer.current = window.setTimeout(() => {\n noticeTimer.current = undefined\n setAssetNotice(undefined)\n }, NOTICE_MS)\n }, [])\n useEffect(() => () => {\n if (noticeTimer.current !== undefined) window.clearTimeout(noticeTimer.current)\n }, [])\n /** Whether the compact layer list is open. */\n const [layersOpen, setLayersOpen] = useState(false)\n\n /**\n * Open one edit mode for a card.\n *\n * The picture's natural size decides the coordinate system both modes work in\n * (marks and the crop box are stored in picture pixels), and only the loaded\n * image knows it — a card's board size is whatever the user dragged it to.\n */\n const openMode = (kind: 'annotate' | 'crop', card: CanvasCard): void => {\n const file = card.file ?? ''\n if (file === '') return\n const url = canvasImageUrl(file, card.source)\n const enter = (natural: { width: number; height: number }): void => {\n setModePicture({ url, natural })\n if (kind === 'annotate') setAnnotating(card.id)\n else setCropping(card.id)\n }\n const image = new Image()\n image.onload = () => { enter({ width: image.naturalWidth || card.width, height: image.naturalHeight || card.height }) }\n image.onerror = () => { enter({ width: card.width, height: card.height }) }\n image.src = url\n }\n\n /** Leave whichever edit mode is open. */\n const closeMode = (): void => {\n setAnnotating(undefined)\n setCropping(undefined)\n setModePicture(undefined)\n }\n\n /**\n * Turn one burned picture into a new card on the board.\n *\n * The composite goes to the canvas asset store — deliberately not the material\n * library — and the card says so (`source: 'canvas'` + its `origin`), so a\n * marked-up copy can never be mistaken for a generation.\n */\n const addBurnedCard = async (burned: { dataUrl: string; width: number; height: number }, placement: { x: number; y: number; width: number; height: number }, origin: 'annotation' | 'crop'): Promise<void> => {\n const current = store.getSnapshot().board\n if (current === undefined) return\n const label = canvasOriginLabel(origin) ?? '合成图'\n setBusy(true)\n try {\n const result = await api.writeCanvasAsset(burned.dataUrl)\n if (!result.ok) {\n console.warn('[dsh-seework] storing the marked-up picture failed:', result.message)\n return\n }\n store.addCards([{\n id: unusedCardId(current),\n kind: 'image',\n x: Math.round(placement.x),\n y: Math.round(placement.y),\n width: Math.round(placement.width),\n height: Math.round(placement.height),\n z: 0,\n file: result.value.image.file,\n source: 'canvas',\n origin,\n model: label,\n prompt: label,\n }])\n await store.saveNow()\n } finally {\n setBusy(false)\n }\n }\n\n const viewport = board?.viewport ?? { x: 0, y: 0, k: 1 }\n // A gesture mutates the viewport through the store, so the render below is\n // already up to date; gestureTick only exists to re-render the cursor state.\n void gestureTick\n\n // Stage size, used by \"fit\", by placement maths, and by the automatic\n // placement the plugin runs when a generation lands while the canvas is open\n // (that path has no DOM access of its own, so the size is recorded here).\n const stageRef = useRef<{ width: number; height: number }>({ width: 0, height: 0 })\n /**\n * The visible stage size, measured NOW.\n *\n * Measuring on demand matters: the recorded value can be a stale zero (this\n * board has not been laid out yet, or a hidden sibling — the floating overlay\n * and the sidebar tab share one store — measured 0×0 and overwrote it). A\n * zero here used to mean \"fit\" silently reset the view to 100% at the origin,\n * which reads to the user as \"my pictures disappeared\".\n */\n const stageSize = useCallback((): { width: number; height: number } => {\n const surface = surfaceRef.current\n if (surface === null) return stageRef.current\n const width = surface.offsetWidth\n const height = surface.offsetHeight\n return width > 0 && height > 0 ? { width, height } : stageRef.current\n }, [])\n\n // Measure the stage without ever measuring the transformed world layer: its\n // client rect already includes the zoom, and fitting against that feeds the\n // zoom back in on every press.\n useEffect(() => {\n const surface = surfaceRef.current\n if (surface === null) return\n const measure = (): void => {\n const size = { width: surface.offsetWidth, height: surface.offsetHeight }\n stageRef.current = size\n // Never publish a zero: a hidden sibling board would otherwise wipe the\n // size a visible one reported, and a placement that reads it would aim at\n // the viewport's corner instead of its middle.\n if (size.width > 0 && size.height > 0) publishStageSize(store, size)\n }\n measure()\n if (typeof ResizeObserver === 'undefined') return\n const observer = new ResizeObserver(measure)\n observer.observe(surface)\n return () => { observer.disconnect() }\n }, [store])\n\n /** Zoom one step around the stage centre (a toolbar press, not a cursor). */\n const zoomCentre = useCallback((direction: 1 | -1): void => {\n if (board === undefined) return\n const { width, height } = stageSize()\n if (width <= 0 || height <= 0) return\n store.setViewport(zoomAt(board.viewport, direction, width / 2, height / 2))\n }, [board, stageSize, store])\n\n /** Zoom to an exact factor around the stage centre. */\n const zoomExact = useCallback((k: number): void => {\n if (board === undefined) return\n const { width, height } = stageSize()\n if (width <= 0 || height <= 0) return\n store.setViewport(zoomTo(board.viewport, k, width / 2, height / 2))\n }, [board, stageSize, store])\n\n /**\n * Show everything on the board.\n *\n * A board with nothing to fit against is left alone rather than reset: the\n * degenerate fit returns the identity viewport, which throws away wherever\n * the user had scrolled to.\n */\n const fit = useCallback((): void => {\n if (board === undefined) return\n const { width, height } = stageSize()\n if (width <= 0 || height <= 0 || board.cards.length === 0) return\n store.setViewport(fitViewport(board.cards, width, height))\n }, [board, stageSize, store])\n\n // Wheel zoom: the board point under the cursor stays under the cursor.\n useEffect(() => {\n const surface = surfaceRef.current\n if (surface === null) return\n const onWheel = (event: WheelEvent): void => {\n if (board === undefined) return\n event.preventDefault()\n const bounds = surface.getBoundingClientRect()\n const anchorX = event.clientX - bounds.left\n const anchorY = event.clientY - bounds.top\n const factor = event.deltaY < 0 ? 1.08 : 1 / 1.08\n const current = store.getSnapshot().board?.viewport\n if (current === undefined) return\n store.setViewport(zoomTo(current, current.k * factor, anchorX, anchorY))\n }\n surface.addEventListener('wheel', onWheel, { passive: false })\n return () => { surface.removeEventListener('wheel', onWheel) }\n }, [board, store])\n\n // Global pointer handlers for the active gesture: the pointer can leave the\n // card (or the stage) mid-drag and must keep working.\n useEffect(() => {\n const onMove = (event: PointerEvent): void => {\n const gesture = gestureRef.current\n const current = store.getSnapshot().board\n if (gesture === null || current === undefined) return\n if (gesture.kind === 'pan') {\n // Pan by the step between this event and the previous one, never by the\n // total distance from the gesture's start. The current viewport is read\n // fresh each time, so a second handler (or a replayed event) can only\n // re-apply the same step to an already-advanced viewport: the board\n // races away under the pointer instead of following it.\n store.setViewport(panBy(current.viewport, event.clientX - gesture.lastX, event.clientY - gesture.lastY))\n gesture.lastX = event.clientX\n gesture.lastY = event.clientY\n return\n }\n const dx = (event.clientX - gesture.startX) / current.viewport.k\n const dy = (event.clientY - gesture.startY) / current.viewport.k\n if (gesture.kind === 'move') {\n if (!gesture.moved && Math.hypot(event.clientX - gesture.startX, event.clientY - gesture.startY) < DRAG_THRESHOLD_PX) return\n gesture.moved = true\n store.updateCard(gesture.cardId, { x: Math.round(gesture.cardX + dx), y: Math.round(gesture.cardY + dy) })\n return\n }\n gesture.moved = true\n store.updateCard(gesture.cardId, {\n width: Math.max(24, Math.round(gesture.width + dx)),\n height: Math.max(24, Math.round(gesture.height + dy)),\n })\n }\n const onUp = (): void => {\n const gesture = gestureRef.current\n gestureRef.current = null\n setGestureTick(value => value + 1)\n if (gesture !== null && gesture.kind !== 'pan' && gesture.moved) void store.saveNow()\n }\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n window.addEventListener('pointercancel', onUp)\n return () => {\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onUp)\n }\n }, [store])\n\n // Delete removes the selected card; Escape drops the selection.\n useEffect(() => {\n const onKey = (event: KeyboardEvent): void => {\n if (selected === undefined) return\n const target = event.target as HTMLElement | null\n const typing = target !== null && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)\n if (typing) return\n if (event.key === 'Delete' || event.key === 'Backspace') {\n event.preventDefault()\n store.removeCard(selected)\n setSelected(undefined)\n void store.saveNow()\n }\n if (event.key === 'Escape') setSelected(undefined)\n }\n document.addEventListener('keydown', onKey)\n return () => { document.removeEventListener('keydown', onKey) }\n }, [selected, store])\n\n const beginPan = (event: React.PointerEvent<HTMLDivElement>): void => {\n if (event.button !== 0) return\n // Pressing the board puts the work back on the board: the layer list is a\n // look-up aid, not a panel that stays in the way (user report: 「图层展开后,\n // 再画布点击后应该消失,现在一直占着」). Presses inside the list itself stop\n // propagation, so reading it does not close it.\n setLayersOpen(false)\n // Only the empty surface pans; a card's own handler stops propagation.\n const target = event.target as HTMLElement\n if (target.closest('[data-seework-card]') !== null) return\n setSelected(undefined)\n gestureRef.current = {\n kind: 'pan',\n lastX: event.clientX,\n lastY: event.clientY,\n }\n setGestureTick(value => value + 1)\n }\n\n const beginMove = (event: React.PointerEvent<HTMLElement>, card: CanvasCard): void => {\n if (event.button !== 0) return\n event.stopPropagation()\n setSelected(card.id)\n gestureRef.current = {\n kind: 'move',\n cardId: card.id,\n startX: event.clientX,\n startY: event.clientY,\n cardX: card.x,\n cardY: card.y,\n moved: false,\n }\n setGestureTick(value => value + 1)\n }\n\n const beginResize = (event: React.PointerEvent<HTMLElement>, card: CanvasCard): void => {\n if (event.button !== 0) return\n event.stopPropagation()\n event.preventDefault()\n gestureRef.current = {\n kind: 'resize',\n cardId: card.id,\n startX: event.clientX,\n startY: event.clientY,\n width: card.width,\n height: card.height,\n moved: false,\n }\n setGestureTick(value => value + 1)\n }\n\n /** Place a library picture in the middle of the current view. */\n const placeImage = useCallback((entry: LibraryEntry, imageIndex = 0): void => {\n if (board === undefined) return\n const image = entry.images[imageIndex]\n if (image === undefined) return\n const centre = stageCentre(board.viewport, stageSize())\n // Keep the picture's own aspect ratio; the card is sized to the picture,\n // not the picture squashed into a default card. The geometry comes from\n // `canvas-placement`, which the automatic placement shares.\n store.addCards([centeredImageCard({\n file: image.file,\n width: image.width,\n height: image.height,\n model: entry.model,\n prompt: entry.prompt,\n origin: entry.source === 'panel' ? 'panel' : 'chat',\n }, centre, unusedCardId(board))])\n setSelected(undefined)\n setPickerOpen(false)\n }, [board, stageSize, store])\n\n /** Add a note in the middle of the current view. */\n const addNote = useCallback((): void => {\n if (board === undefined) return\n const centre = stageCentre(board.viewport, stageSize())\n const id = unusedCardId(board)\n store.addCards([{\n id,\n kind: 'text',\n x: Math.round(centre.x - 120),\n y: Math.round(centre.y - 50),\n width: 240,\n height: 100,\n z: 0,\n text: '',\n fontSize: 16,\n }])\n setSelected(id)\n }, [board, stageSize, store])\n\n const boardCards = useMemo(() => [...(board?.cards ?? [])].sort((left, right) => left.z - right.z), [board?.cards])\n\n /**\n * Selecting a card also lifts it to the front.\n *\n * New cards land on top of everything (`addCards`), so without this a card you\n * click in order to move it can sit under pictures that were added later and be\n * impossible to drag out from underneath them. Only a card that is not already\n * on top is restacked, so clicking around does not rewrite the document.\n */\n const selectCard = useCallback((card: CanvasCard): void => {\n setSelected(card.id)\n const cards = store.getSnapshot().board?.cards ?? []\n const top = cards.reduce((highest, item) => Math.max(highest, item.z), card.z)\n if (card.z < top) store.updateCard(card.id, { z: top + 1 })\n }, [store])\n\n /**\n * Where one card's picture came from, for its badge.\n *\n * Cards written before the `origin` field existed have none, so it is inferred:\n * a board's own picture is a composite (its label says which kind), and a\n * library picture is looked up in the library the panel already holds.\n */\n const originOf = useCallback((card: CanvasCard): CanvasCardOrigin | undefined => {\n if (card.origin !== undefined) return card.origin\n if (card.source === 'canvas') return card.model === '裁剪' || card.model === '裁剪合成' ? 'crop' : 'annotation'\n for (const entry of library.entries) {\n if (entry.images.some(image => image.file === card.file)) return entry.source === 'panel' ? 'panel' : 'chat'\n }\n return undefined\n }, [library.entries])\n\n /**\n * Remove one card, and the file behind it when the board owns that file.\n *\n * The order matters: the board has to stop referencing the picture — and say so\n * on disk — before the delete route will agree to drop it, which is also what\n * keeps a second board's copy safe.\n */\n const removeCard = useCallback((card: CanvasCard): void => {\n store.removeCard(card.id)\n setSelected(undefined)\n void (async () => {\n await store.saveNow()\n if (card.source !== 'canvas' || card.file === undefined) return\n const result = await api.removeCanvasAsset(card.file)\n if (!result.ok) {\n showAssetNotice(`卡片已移除,但它的合成图文件没删掉:${result.message}`)\n return\n }\n showAssetNotice(result.value.removed === 0 ? '卡片已移除(合成图文件之前就不在了)。' : '卡片和它的合成图文件都已删除。')\n })()\n }, [api, store])\n\n /** Drop every board picture no card shows any more. */\n const pruneAssets = useCallback((): void => {\n void (async () => {\n const result = await api.pruneCanvasAssets()\n if (!result.ok) {\n showAssetNotice(`清理失败:${result.message}`)\n return\n }\n const { removed, bytes } = result.value\n showAssetNotice(removed === 0 ? '没有可清理的图片。' : `已清理 ${removed} 张没有用到的图片,释放 ${formatBytes(bytes)}。`)\n })()\n }, [api])\n\n /**\n * Pick one card from the layer list: select it, raise it, and bring it into view.\n *\n * A card that is completely covered by others cannot be clicked on the board at\n * all, so the list is the way back to it — and a card that is off-screen would be\n * selected invisibly, so the view follows it there too.\n */\n const pickFromLayers = useCallback((card: CanvasCard): void => {\n selectCard(card)\n const stage = stageSize()\n if (stage.width === 0 || stage.height === 0) return\n const centreX = card.x + card.width / 2\n const centreY = card.y + card.height / 2\n const screen = boardToScreen(viewport, centreX, centreY)\n const inside = screen.x > LAYER_REVEAL_MARGIN\n && screen.x < stage.width - LAYER_REVEAL_MARGIN\n && screen.y > LAYER_REVEAL_MARGIN\n && screen.y < stage.height - LAYER_REVEAL_MARGIN\n if (inside) return\n store.setViewport({\n ...viewport,\n x: Math.round(stage.width / 2 - centreX * viewport.k),\n y: Math.round(stage.height / 2 - centreY * viewport.k),\n })\n }, [selectCard, stageSize, store, viewport])\n\n /**\n * The selected card, when it is a picture the bar can act on. A note card has\n * no picture to send anywhere, so it gets no bar.\n */\n const selectedImageCard = useMemo(() => {\n if (selected === undefined) return undefined\n const card = board?.cards.find(candidate => candidate.id === selected)\n return card === undefined || card.kind !== 'image' || (card.file ?? '') === '' ? undefined : card\n }, [board?.cards, selected])\n /** The cards the two edit modes belong to, if they still exist. */\n const cropCard = board?.cards.find(candidate => candidate.id === cropping)\n const annotateCard = board?.cards.find(candidate => candidate.id === annotating)\n\n if (state.status === 'loading' && board === undefined) {\n return <p className={css.muted}>正在读取画布…</p>\n }\n if (board === undefined) {\n return <p className={css.muted}>{state.message ?? '没有打开的画布。'}</p>\n }\n\n const gridSize = 32 * viewport.k\n\n return (\n <div className={css.workspace}>\n <div className={css.toolbar}>\n <strong className={css.boardTitle} title=\"更换画布请用左侧列表\">{board.title}</strong>\n <Button onClick={() => { zoomCentre(-1) }} title=\"缩小\">−</Button>\n <span className={css.zoomLabel}>{Math.round(viewport.k * 100)}%</span>\n <Button onClick={() => { zoomCentre(1) }} title=\"放大\">+</Button>\n <Button onClick={() => { zoomExact(1) }} title=\"回到 100%\">100%</Button>\n <Button onClick={fit} title=\"让全部内容可见\">适应内容</Button>\n <span className={css.toolbarGap} />\n <Button onClick={addNote}>加备注</Button>\n <Button\n onClick={() => { setLayersOpen(open => !open) }}\n title=\"按图层从下到上列出板上的卡片;点一行就选中它并提到最上层\"\n data-dsh-seework-layers-toggle=\"\"\n >\n 图层\n </Button>\n <Button onClick={pruneAssets} title=\"删掉板上没有任何卡片引用的画布图片(标注/裁剪合成图)\">清理无用图片</Button>\n <Button\n variant=\"primary\"\n onClick={() => {\n onNeedLibrary()\n setPickerOpen(open => !open)\n }}\n >\n 从素材库添加\n </Button>\n <SaveState save={state.save} dirty={state.dirty} />\n </div>\n\n {assetNotice === undefined ? null : <p className={css.notice}>{assetNotice}</p>}\n\n {state.message !== undefined && state.save !== 'error'\n ? <p className={css.notice}>{state.message}</p>\n : null}\n\n <div\n className={css.surface}\n ref={surfaceRef}\n onPointerDown={beginPan}\n style={{ cursor: gestureRef.current?.kind === 'pan' ? 'grabbing' : 'default' }}\n >\n <div\n className={css.grid}\n style={{ backgroundSize: `${gridSize}px ${gridSize}px`, backgroundPosition: `${viewport.x}px ${viewport.y}px` }}\n />\n <div className={css.world} style={{ transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.k})` }}>\n {boardCards.map(card => (\n <CardView\n key={card.id}\n card={card}\n selected={selected === card.id}\n onSelect={() => { selectCard(card) }}\n onBeginMove={event => { beginMove(event, card) }}\n onBeginResize={event => { beginResize(event, card) }}\n onText={text => { store.updateCard(card.id, { text }) }}\n onRemove={() => { removeCard(card) }}\n badge={canvasOriginLabel(originOf(card))}\n removeHint={removeHint(card)}\n />\n ))}\n </div>\n {/* The layer list. A card buried under others cannot be clicked on the board\n at all, so this is the way back to it — and the only place the whole\n board's stacking order is visible at once. Top of the stack first. */}\n {layersOpen\n ? (\n <div\n className={css.layers}\n data-dsh-seework-layers=\"\"\n role=\"list\"\n aria-label=\"图层\"\n onPointerDown={event => { event.stopPropagation() }}\n >\n <p className={css.layersHead}>图层(上面的是最上层)</p>\n {[...boardCards].reverse().map((card, index) => (\n <button\n key={card.id}\n type=\"button\"\n role=\"listitem\"\n className={[css.layerRow, selected === card.id ? css.layerRowActive : ''].filter(Boolean).join(' ')}\n data-seework-layer-row={card.id}\n data-seework-layer-active={selected === card.id ? 'true' : undefined}\n onPointerDown={event => { event.stopPropagation() }}\n onClick={() => { pickFromLayers(card) }}\n >\n <span className={css.layerIndex}>{index + 1}</span>\n <span className={css.layerBadge}>{card.kind === 'image' ? canvasOriginLabel(originOf(card)) ?? '图片' : '备注'}</span>\n <span className={css.layerName}>{layerName(card)}</span>\n </button>\n ))}\n </div>\n )\n : null}\n\n {/* The command bar for the selected picture. It is HTML over the board,\n so it keeps its size at every zoom and never scales with the world.\n Hidden while an edit mode is open: those modes own the picture. */}\n {selectedImageCard === undefined || annotating !== undefined || cropping !== undefined\n ? null\n : (\n <NodeFloatingBar\n node={selectedImageCard}\n viewport={viewport}\n stageSize={stageSize()}\n onAddToConversation={() => {\n const file = selectedImageCard.file ?? ''\n if (file === '') return false\n return sendImageToConversation({\n url: canvasImageUrl(file, selectedImageCard.source),\n name: attachmentNameFor(file),\n })\n }}\n onAnnotate={() => { openMode('annotate', selectedImageCard) }}\n onCrop={() => { openMode('crop', selectedImageCard) }}\n />\n )}\n\n {/* Crop mode draws over the node in SCREEN coordinates, so its box and\n handles keep a constant size while the board is zoomed. */}\n {cropCard === undefined || modePicture === undefined\n ? null\n : (\n <CropOverlay\n node={nodeScreenRect(cropCard, viewport)}\n natural={modePicture.natural}\n onCancel={closeMode}\n onConfirm={rect => {\n void (async () => {\n const burned = await burnCrop(modePicture.url, rect)\n if (burned === undefined) return\n const size = croppedNodeSize(rect)\n const position = cropNodePosition(cropCard, size)\n await addBurnedCard(burned, { ...position, ...size }, 'crop')\n closeMode()\n })()\n }}\n />\n )}\n </div>\n\n {/* The annotation editor takes the whole panel: the picture is the work\n surface, and the board behind it would only compete for attention. */}\n {annotateCard === undefined || modePicture === undefined\n ? null\n : (\n <AnnotationEditor\n url={modePicture.url}\n natural={modePicture.natural}\n onCancel={closeMode}\n onSave={async dataUrl => {\n const image = await loadBurnedSize(dataUrl)\n await addBurnedCard(image, {\n x: annotateCard.x + annotateCard.width + 24,\n y: annotateCard.y,\n ...sizeKeepingAspect(annotateCard.width, image.width, image.height),\n }, 'annotation')\n closeMode()\n }}\n />\n )}\n {busy ? <p className={css.notice}>正在保存这张图…</p> : null}\n\n {pickerOpen\n ? (\n <LibraryPicker\n entries={library.entries}\n onPick={placeImage}\n onClose={() => { setPickerOpen(false) }}\n />\n )\n : null}\n </div>\n )\n}\n\n/** The save indicator. */\nfunction SaveState({ save, dirty }: { save: CanvasState['save']; dirty: boolean }): JSX.Element {\n const text = save === 'saving'\n ? '保存中…'\n : save === 'conflict'\n ? '已在别处被改动'\n : save === 'error'\n ? '保存失败'\n : dirty\n ? '待保存'\n : '已保存'\n const tone = save === 'conflict' ? css.saveWarn : save === 'error' ? css.saveError : dirty ? css.saveIdle : css.saveOk\n return <span className={`${css.saveState} ${tone}`}>{text}</span>\n}\n\n/** One card on the board. */\nfunction CardView({\n card,\n selected,\n badge,\n removeHint,\n onSelect,\n onBeginMove,\n onBeginResize,\n onText,\n onRemove,\n}: {\n card: CanvasCard\n selected: boolean\n /** Origin badge text (「标注合成」…), when the picture's source is known. */\n badge?: string | undefined\n /** What the ✕ promises to delete. */\n removeHint: string\n onSelect: () => void\n onBeginMove: (event: React.PointerEvent<HTMLElement>) => void\n onBeginResize: (event: React.PointerEvent<HTMLElement>) => void\n onText: (text: string) => void\n onRemove: () => void\n}): JSX.Element {\n return (\n <div\n data-seework-card={card.id}\n className={[css.card, selected ? css.cardSelected : ''].filter(Boolean).join(' ')}\n style={{ left: card.x, top: card.y, width: card.width, height: card.height, zIndex: Math.round(card.z) }}\n onPointerDown={onSelect}\n >\n <header className={css.cardBar} onPointerDown={onBeginMove} title=\"拖动移动\">\n <span className={css.cardKind} data-dsh-seework-card-origin={badge ?? ''}>\n {card.kind === 'image' ? badge ?? '图片' : '备注'}\n </span>\n <button\n type=\"button\"\n className={css.cardClose}\n title={removeHint}\n onPointerDown={event => { event.stopPropagation() }}\n onClick={onRemove}\n >\n ✕\n </button>\n </header>\n <div className={css.cardBody}>\n {card.kind === 'image' && card.file !== undefined\n ? <img className={css.cardImage} src={canvasImageUrl(card.file, card.source)} alt={card.prompt ?? ''} draggable={false} />\n : (\n <textarea\n className={css.cardText}\n value={card.text ?? ''}\n placeholder=\"写点什么…\"\n style={{ fontSize: card.fontSize ?? 16 }}\n onPointerDown={event => { event.stopPropagation() }}\n onChange={event => { onText(event.target.value) }}\n />\n )}\n </div>\n <span className={css.resizeHandle} onPointerDown={onBeginResize} title=\"拖动调整大小\" />\n </div>\n )\n}\n\n/** The picture picker, shown as a panel over the board. */\nfunction LibraryPicker({\n entries,\n onPick,\n onClose,\n}: {\n entries: LibraryEntry[]\n onPick: (entry: LibraryEntry, imageIndex: number) => void\n onClose: () => void\n}): JSX.Element {\n return (\n <>\n <div className={css.pickerBackdrop} onClick={onClose} />\n <div className={css.picker} role=\"dialog\" aria-label=\"从素材库添加\">\n <header className={css.pickerHeader}>\n <strong>从素材库添加</strong>\n <Button onClick={onClose}>关闭</Button>\n </header>\n {entries.length === 0\n ? <p className={css.muted}>素材库还是空的:先在对话里让 Agent 画一张。</p>\n : (\n <div className={css.pickerGrid}>\n {entries.flatMap(entry => entry.images.map((image, index) => (\n <button\n key={`${entry.id}-${index}`}\n type=\"button\"\n className={css.pickerItem}\n title={entry.prompt}\n onClick={() => { onPick(entry, index) }}\n >\n <img src={image.url} alt=\"\" loading=\"lazy\" />\n </button>\n )))}\n </div>\n )}\n </div>\n </>\n )\n}\n\n/** Board list + open/create/delete, shown beside the board. */\nexport function CanvasBoardList({ store }: { store: CanvasStore }): JSX.Element {\n const [tick, setTick] = useState(0)\n useEffect(() => store.subscribe(() => { setTick(value => value + 1) }), [store])\n void tick\n const state = store.getSnapshot()\n const [renaming, setRenaming] = useState(false)\n return ( <aside className={css.list}>\n <header className={css.listHeader}>\n <strong>画布</strong>\n <Button variant=\"primary\" onClick={() => { void store.open('') }}>新建</Button>\n </header>\n {state.boards.length === 0\n ? <p className={css.muted}>还没有画布。</p>\n : (\n <ul className={css.boardList}>\n {state.boards.map(summary => (\n <li key={summary.id}>\n <button\n type=\"button\"\n className={[css.boardRow, state.board?.id === summary.id ? css.boardRowActive : ''].filter(Boolean).join(' ')}\n onClick={() => { void store.open(summary.id) }}\n >\n <span className={css.boardName}>{summary.title}</span>\n <span className={css.boardMeta}>{summary.cardCount} 个卡片</span>\n </button>\n <button\n type=\"button\"\n className={css.boardDelete}\n title=\"删除这块画布\"\n onClick={() => {\n void store.deleteBoard(summary.id)\n }}\n >\n ✕\n </button>\n </li>\n ))}\n </ul>\n )}\n {state.board === undefined\n ? null\n : (\n <div className={css.listFooter}>\n {renaming\n ? (\n <TextInput\n defaultValue={state.board.title}\n autoFocus\n onBlur={event => {\n store.setTitle(event.target.value.trim() === '' ? state.board!.title : event.target.value.trim())\n setRenaming(false)\n void store.saveNow()\n }}\n onKeyDown={event => {\n if (event.key === 'Enter') (event.target as HTMLInputElement).blur()\n }}\n />\n )\n : <Button onClick={() => { setRenaming(true) }}>重命名当前画布</Button>}\n <span className={css.muted}>{state.boards.length} 块画布</span>\n </div>\n )}\n </aside>\n )\n}\n","/**\n * One dock for every SeeWork launcher.\n *\n * The surfaces are mounted independently (settings panel, library drawer, canvas\n * overlay), so their buttons cannot be laid out by a shared parent component.\n * Instead each surface renders its button into the dock element this module\n * owns, which is why a single owner exists at all: three buttons at fixed\n * coordinates would overlap, and each new surface would need another offset.\n */\n\nimport css from './surfaces.module.css'\n\n/** Marks the dock element. */\nexport const SURFACE_DOCK_ATTR = 'data-dsh-seework-dock'\n\n/**\n * The dock element, created on first use.\n *\n * Reuse is by attribute lookup, so a hot reload that mounts the surfaces again\n * does not leave a second dock behind.\n * @returns the dock element, or undefined when there is no document.\n */\nexport function surfaceDock(): HTMLElement | undefined {\n if (typeof document === 'undefined') return undefined\n // The attribute selector must pin the empty value: a bare `[attr]` matches\n // descendants too (the launcher buttons carry no such attribute, but the\n // selector reads as \"any element that has it\", which is how a nested lookup\n // could hand back the wrong node).\n const existing = document.querySelector<HTMLElement>(`[${SURFACE_DOCK_ATTR}=\"\"]`)\n if (existing !== null) return existing\n const dock = document.createElement('div')\n dock.setAttribute(SURFACE_DOCK_ATTR, '')\n dock.className = css.dock\n document.body.appendChild(dock)\n return dock\n}\n\n/**\n * Put one launcher button in the dock.\n * @param button - the button element (its classes come from the surface).\n * @returns disposer removing the button and the dock once it is empty.\n */\nexport function attachLauncher(button: HTMLElement): () => void {\n const dock = surfaceDock()\n if (dock === undefined) return () => {}\n dock.appendChild(button)\n return () => {\n button.remove()\n if (dock.childElementCount === 0) dock.remove()\n }\n}\n","/**\n * The DOM-mounted settings surface: the guaranteed way into the plugin's\n * settings when the shell does not expose a plugin-configuration slot to\n * third-party packages.\n *\n * It renders a quiet floating button in the lower-left corner of the viewport;\n * clicking it opens the same card the slot registration would render, inside a\n * local overlay. Nothing here depends on the shell's internal layout classes,\n * so it cannot break when the shell layout changes.\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport { SeeWorkApi } from './api.ts'\nimport { SeeWorkSettingsCard } from './SettingsCard.tsx'\nimport type { SeeWorkScope } from './settings-scope.ts'\nimport { attachLauncher } from './surfaces.ts'\nimport launcherCss from './surfaces.module.css'\nimport css from './settings-panel.module.css'\n\n/** Marks the mounted launcher (a stable hook for tests and DOM inspection). */\nexport const SETTINGS_LAUNCHER_ATTR = 'data-dsh-seework-settings-launcher'\n\n/** Cross-surface event: detail is the name of the surface that just opened. */\nexport const ACTIVATE_EVENT = 'dsh-seework-activate'\n\n/** The SeeWork surfaces subject to the single-open rule. */\nexport type SeeWorkSurface = 'settings' | 'library' | 'canvas'\n\n/**\n * Ask sibling SeeWork surfaces to step aside, and report which one is opening.\n * The DOM-mounted surfaces are siblings in `document.body`, so this is how they\n * avoid stacking on top of each other without sharing React state.\n * @param name - the surface that is opening.\n */\nexport function announceActivation(name: SeeWorkSurface): void {\n document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: name }))\n}\n\n/**\n * Ask one SeeWork surface to open, from anywhere.\n *\n * The header launcher lives in the shell's toolbar rather than in this plugin's\n * tree, so it cannot call a component's `setOpen` directly; announcing the\n * surface is the same path its own launcher takes, which means one code path\n * opens a surface however it was asked for.\n * @param surface - the surface to bring up.\n */\nexport function requestSurface(surface: SeeWorkSurface): void {\n if (typeof document === 'undefined') return\n announceActivation(surface)\n}\n\n/** The surface whose activation was announced last, for launcher highlighting. */\nlet activeSurface: SeeWorkSurface | undefined\n\n/**\n * The surface currently showing, as far as the shared activation event knows.\n * @returns the surface name, or undefined while none has been announced.\n */\nexport function activeSeeWorkSurface(): SeeWorkSurface | undefined {\n return activeSurface\n}\n\n/**\n * Follow the activation stream.\n * @param listener - called after each announcement with the surface it names.\n * @returns disposer removing the listener.\n */\nexport function onActivation(listener: (surface: SeeWorkSurface) => void): () => void {\n const handler = (event: Event): void => {\n const surface = (event as CustomEvent<SeeWorkSurface>).detail\n activeSurface = surface\n listener(surface)\n }\n document.addEventListener(ACTIVATE_EVENT, handler)\n return () => { document.removeEventListener(ACTIVATE_EVENT, handler) }\n}\n\n/** Mount the launcher + overlay.\n * @param scope - the bound settings scope.\n * @param api - the plugin route client.\n * @returns disposer unmounting everything this created.\n */\nexport function mountSettingsPanel(scope: SeeWorkScope, api: SeeWorkApi): () => void {\n const launcher = document.createElement('button')\n launcher.type = 'button'\n launcher.setAttribute(SETTINGS_LAUNCHER_ATTR, '')\n launcher.className = launcherCss.launcher\n launcher.textContent = 'SeeWork 设置'\n launcher.title = '设置 SeeAI Hub 地址、API Key 与图片模型'\n\n const overlay = document.createElement('div')\n overlay.className = css.overlay\n overlay.hidden = true\n overlay.setAttribute('role', 'dialog')\n overlay.setAttribute('aria-modal', 'true')\n overlay.setAttribute('aria-label', 'SeeWork 设置')\n\n const panel = document.createElement('div')\n panel.className = css.panel\n overlay.appendChild(panel)\n document.body.append(overlay)\n const detachLauncher = attachLauncher(launcher)\n\n const root: Root = createRoot(panel)\n root.render(<SeeWorkSettingsCard scope={scope} api={api} />)\n\n const close = (): void => { overlay.hidden = true }\n const onLauncher = (): void => {\n const next = overlay.hidden\n overlay.hidden = !next\n // Only one SeeWork surface should be open at a time; opening this one\n // retires the library drawer.\n if (next) announceActivation('settings')\n }\n const onOtherActivate = (event: Event): void => {\n if ((event as CustomEvent).detail === 'library') close()\n }\n const onOverlay = (event: MouseEvent): void => {\n if (event.target === overlay) close()\n }\n const onKey = (event: KeyboardEvent): void => {\n if (event.key === 'Escape' && !overlay.hidden) close()\n }\n launcher.addEventListener('click', onLauncher)\n overlay.addEventListener('click', onOverlay)\n document.addEventListener('keydown', onKey)\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n\n return () => {\n launcher.removeEventListener('click', onLauncher)\n overlay.removeEventListener('click', onOverlay)\n document.removeEventListener('keydown', onKey)\n document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)\n root.unmount()\n detachLauncher()\n overlay.remove()\n }\n}\n","/**\n * SeeWork's entry points in the shell's conversation header (top-right).\n *\n * The shell owns that toolbar, so these are registered into its utilities slot\n * rather than mounted on `document.body`. That is deliberate: buttons floating\n * over the viewport cover whatever the shell puts in the same corner, and the\n * header is where a user looks for \"open the side panel\".\n *\n * Degradation: a shell without the slot simply never calls the injection\n * callback, in which case the floating dock from `surfaces.ts` stays in place —\n * a plugin must not become unreachable because a slot moved.\n */\n\nimport { useEffect, useState } from 'react'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { announceActivation, onActivation, requestSurface, type SeeWorkSurface } from './settings-panel.tsx'\nimport css from './header-launchers.module.css'\n\n/**\n * Who is visibly showing, as reported by the surfaces themselves.\n *\n * The launcher cannot infer this from the activation event alone: a surface can\n * be closed from its own chrome (the drawer's 收起), and only the surface knows\n * that happened. Registration is the surface's job; the launcher only asks.\n */\nconst openProbes = new Map<SeeWorkSurface, () => boolean>()\n\n/**\n * Report whether a surface is currently showing.\n * @param surface - the surface.\n * @param probe - reads the surface's own open state.\n * @returns disposer retiring the report.\n */\nexport function reportSurfaceOpen(surface: SeeWorkSurface, probe: () => boolean): () => void {\n openProbes.set(surface, probe)\n return () => {\n if (openProbes.get(surface) === probe) openProbes.delete(surface)\n }\n}\n\n/**\n * Is that surface up right now?\n * @param surface - the surface to ask about.\n * @returns true when the surface is showing.\n */\nexport function isSurfaceOpen(surface: SeeWorkSurface): boolean {\n return openProbes.get(surface)?.() === true\n}\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface SlotMap {\n /**\n * Right-aligned conversation-header utilities. The shell declares it as a\n * `list`, so this plugin's entry sits beside the shell's own controls\n * instead of taking the header's single far-right corner.\n */\n 'conversation.session.header.utilities': { kind: 'list'; scope: 'session'; owner: { children?: never } }\n }\n}\n\n/** One launcher button: the surface it opens and how it reads. */\ninterface LauncherTarget {\n surface: SeeWorkSurface\n label: string\n title: string\n}\n\n/** The two entries, in display order. */\nexport const LAUNCHER_TARGETS: readonly LauncherTarget[] = [\n { surface: 'library', label: '素材库', title: '查看用 SeeWork 生成过的图片' },\n { surface: 'canvas', label: '画布', title: '把生成过的图片摆到画布上' },\n]\n\n/** Props the shell hands a utilities entry (it supplies nothing of its own). */\nexport interface HeaderLaunchersProps {\n children?: never\n}\n\n/**\n * How a launcher changes the state of the surface behind it.\n *\n * Production passes the sidebar-backed controller; a shell without the right\n * column passes the floating-surface fallback instead, which is why this is an\n * injected face rather than a direct call.\n */\nexport interface LauncherControls {\n /** Show the surface (expanding the column when it is collapsed). */\n open: (surface: SeeWorkSurface) => boolean\n /** Put the surface away when it is the visible one. */\n close: (surface: SeeWorkSurface) => boolean\n /** Whether that surface is showing right now. */\n isOpen: (surface: SeeWorkSurface) => boolean\n}\n\n/** What the launchers module needs from the composition root. */\nexport interface HeaderLauncherOptions {\n controls?: LauncherControls\n}\n\n/** The controls in force; the floating-surface fallback until proven otherwise. */\nlet controls: LauncherControls | undefined\n\n/**\n * Whether the header buttons are on screen.\n *\n * The conversation header only exists inside a session, so its buttons come and\n * go: on the home screen there are none, which is why the floating dock must\n * come back rather than be torn down for good.\n */\nlet headerPresent = false\nconst presenceListeners = new Set<(present: boolean) => void>()\n\n/**\n * Report whether the header's SeeWork buttons are mounted (called by the\n * component itself, so the answer is what is actually rendered).\n * @param present - true while the buttons are on screen.\n */\nexport function reportHeaderPresence(present: boolean): void {\n if (headerPresent === present) return\n headerPresent = present\n for (const listener of [...presenceListeners]) listener(present)\n}\n\n/**\n * Watch the header buttons' presence.\n * @param listener - called now and on every change.\n * @returns disposer removing the listener.\n */\nexport function onHeaderPresence(listener: (present: boolean) => void): () => void {\n presenceListeners.add(listener)\n listener(headerPresent)\n return () => { presenceListeners.delete(listener) }\n}\n\n/**\n * Install the launcher controls.\n * @param next - the controls to use.\n * @returns disposer restoring the previous set.\n */\nexport function setLauncherControls(next: LauncherControls | undefined): () => void {\n const previous = controls\n controls = next\n return () => { controls = previous }\n}\n\n/**\n * The header buttons.\n *\n * The open surface is tracked from the shared activation event rather than from\n * the surfaces themselves, because those are mounted as siblings in\n * `document.body` and share no React state with the header. Pressing the button\n * of the surface that is already up puts it away again.\n */\nexport function HeaderLaunchers(_props: HeaderLaunchersProps): JSX.Element {\n const [active, setActive] = useState<SeeWorkSurface | undefined>(undefined)\n useEffect(() => onActivation(surface => { setActive(surface) }), [])\n // The buttons' own lifetime is the truthful \"the header is here\" signal: it\n // covers entering and leaving a session without guessing from the DOM.\n useEffect(() => {\n reportHeaderPresence(true)\n return () => { reportHeaderPresence(false) }\n }, [])\n const press = (surface: SeeWorkSurface): void => {\n const current = controls\n if (current !== undefined) {\n if (current.isOpen(surface)) current.close(surface)\n else current.open(surface)\n return\n }\n if (isSurfaceOpen(surface)) {\n // Floating fallback: closing is the same shape of message as opening — a\n // surface whose name is not announced stands down, and announcing a\n // neighbour keeps \"exactly one SeeWork surface is up\" true.\n const other = LAUNCHER_TARGETS.find(target => target.surface !== surface)\n announceActivation(other?.surface ?? 'settings')\n return\n }\n requestSurface(surface)\n }\n return (\n <div className={css.group} data-dsh-seework-header-launchers=\"\">\n {LAUNCHER_TARGETS.map(target => (\n <button\n key={target.surface}\n type=\"button\"\n className={css.button}\n data-surface={target.surface}\n data-active={active === target.surface ? 'true' : undefined}\n title={target.title}\n aria-expanded={active === target.surface}\n onClick={() => { press(target.surface) }}\n >\n {target.label}\n </button>\n ))}\n </div>\n )\n}\n","/**\n * Mount point for the canvas surface.\n *\n * The canvas opens as a full-surface overlay rather than a drawer: a board\n * needs room, and the user is arranging pictures rather than reading a list.\n * Its launcher sits in the shared SeeWork dock, and the shared activation event\n * keeps only one SeeWork surface open at a time.\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { SeeWorkApi } from './api.ts'\nimport { LibraryStore } from './library-store.ts'\nimport { CanvasStore } from './canvas-store.ts'\nimport { CanvasBoard, CanvasBoardList } from './CanvasBoard.tsx'\nimport { reportSurfaceOpen } from './header-launchers.tsx'\nimport { ACTIVATE_EVENT, announceActivation, onActivation } from './settings-panel.tsx'\nimport { attachLauncher } from './surfaces.ts'\nimport launcherCss from './surfaces.module.css'\nimport css from './canvas-shell.module.css'\n\n/** Marks the canvas launcher. */\nexport const CANVAS_LAUNCHER_ATTR = 'data-dsh-seework-canvas-launcher'\n\n/** Everything the canvas surface owns, mounted into the dock's host element. */\nfunction CanvasSurface({ library, store, launcher }: {\n library: LibraryStore\n store: CanvasStore\n launcher: HTMLElement\n}): JSX.Element | null {\n const [open, setOpen] = useState(false)\n const openRef = useRef(false)\n\n /** Open or close, doing whatever the transition needs. */\n const setSurfaceOpen = useCallback((next: boolean): void => {\n if (openRef.current === next) return\n openRef.current = next\n setOpen(next)\n if (!next) {\n // Leaving the surface saves what is unsaved; the store survives so\n // reopening shows the same board.\n void store.close()\n return\n }\n announceActivation('canvas')\n void library.refresh()\n if (store.getSnapshot().board !== undefined) {\n void store.refreshList()\n return\n }\n void (async () => {\n await store.refreshList()\n const first = store.getSnapshot().boards[0]\n await store.open(first?.id ?? '')\n })()\n }, [library, store])\n\n // The launcher lives in the dock, outside this tree, so its label and click\n // handler are wired imperatively.\n useEffect(() => {\n const render = (): void => {\n const count = store.getSnapshot().boards.length\n launcher.textContent = count === 0 ? '画布' : `画布 ${count}`\n launcher.title = '把生成过的图片摆到画布上'\n launcher.setAttribute('aria-expanded', String(openRef.current))\n }\n render()\n const stop = store.subscribe(render)\n const onClick = (): void => { setSurfaceOpen(!openRef.current) }\n launcher.addEventListener('click', onClick)\n return () => {\n stop()\n launcher.removeEventListener('click', onClick)\n }\n }, [launcher, store, setSurfaceOpen])\n\n // Only one SeeWork surface is open at a time, and announcing this surface is\n // how anything outside this tree (the conversation header) asks for it.\n useEffect(() => {\n const onOtherActivate = (event: Event): void => {\n if ((event as CustomEvent).detail !== 'canvas') setSurfaceOpen(false)\n }\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n return () => { document.removeEventListener(ACTIVATE_EVENT, onOtherActivate) }\n }, [setSurfaceOpen])\n\n useEffect(() => onActivation(surface => {\n if (surface === 'canvas') setSurfaceOpen(true)\n }), [setSurfaceOpen])\n\n // The header button toggles, so it needs the surface's real open state.\n useEffect(() => reportSurfaceOpen('canvas', () => openRef.current), [])\n\n // Mounted only while open. Hiding it with `display: none` instead would leave\n // a second live board over the same store: every card rendered twice, two sets\n // of pointer listeners, and a 0×0 \"stage\" whose measurement could overwrite the\n // size the visible board (the sidebar tab) just reported.\n if (!open) return null\n return (\n <div className={css.overlay} role=\"dialog\" aria-modal=\"true\" aria-label=\"SeeWork 画布\">\n <header className={css.header}>\n <strong>SeeWork 画布</strong>\n <span className={css.headerHint}>把素材库里的图片摆到板上:拖标题栏移动、右下角调整大小、滚轮缩放;不做连线。</span>\n <button type=\"button\" className={css.close} onClick={() => { setSurfaceOpen(false) }}>关闭</button>\n </header>\n <div className={css.body}>\n <CanvasBoardList store={store} />\n <CanvasBoard\n store={store}\n library={{ entries: library.getSnapshot().entries }}\n onNeedLibrary={() => { void library.refresh() }}\n />\n </div>\n </div>\n )\n}\n\n/**\n * Mount the canvas launcher and fallback overlay.\n * @param api - the plugin route client (unused here; the store is resolved by the caller).\n * @param library - the shared material-library store (the picture source).\n * @param store - the shared canvas store, also rendered by the right-sidebar tab.\n * @returns disposer unmounting everything this created.\n */\nexport function mountCanvasSurfaces(api: SeeWorkApi, library: LibraryStore, store: CanvasStore): () => void {\n void api\n const host = document.createElement('div')\n document.body.appendChild(host)\n const launcher = document.createElement('button')\n launcher.type = 'button'\n launcher.setAttribute(CANVAS_LAUNCHER_ATTR, '')\n launcher.className = launcherCss.launcher\n const detachLauncher = attachLauncher(launcher)\n\n const root: Root = createRoot(host)\n root.render(<CanvasSurface library={library} store={store} launcher={launcher} />)\n return () => {\n root.unmount()\n detachLauncher()\n host.remove()\n }\n}\n\n/** Re-exported so the composition root can measure the board without importing it. */\nexport { stageSizeFor } from './CanvasBoard.tsx'\n","/**\n * Noticing a finished generation from the browser half.\n *\n * The host gives the page no push channel: an agent-side `generate_image` writes\n * a library entry and nothing tells the tab. So the page polls the library's\n * *identity* (newest entry id + total, a few dozen bytes) and, when it changes,\n * refreshes the shared library store — which is what updates the library tab, the\n * canvas's picture picker, and the launcher's count.\n *\n * What it deliberately does NOT do is touch the canvas. Auto-opening the column\n * and dropping the new picture onto the open board was the earlier behaviour, and\n * it turned a background generation into the user's screen rearranging itself;\n * placement is the user's call now (the canvas's own 「从素材库选图」)。 The\n * watcher therefore does not even receive the canvas store.\n *\n * Polling is the honest option here: a websocket into the shell is private, and\n * an SSE route would still be a long-lived connection the console is happy to\n * burn. The interval is skipped while the page is hidden, and a failed poll\n * changes nothing — a transient 500 from the host must not disturb the page.\n */\n\nimport type { SeeWorkApi } from './api.ts'\nimport type { LibraryStore } from './library-store.ts'\n\n/** How often the page asks the host what the newest entry is. */\nexport const WATCH_INTERVAL_MS = 2000\n\n/** What the watcher needs from the composition root. */\nexport interface GenerationWatchDeps {\n api: SeeWorkApi\n library: LibraryStore\n /** Poll interval; tests shorten it. */\n intervalMs?: number\n /** Timer injectors, so tests can drive the loop without real time. */\n setTimer?: (handler: () => void, ms: number) => ReturnType<typeof setInterval>\n clearTimer?: (handle: ReturnType<typeof setInterval>) => void\n}\n\n/**\n * Start watching for generations that finished in the host.\n * @param deps - the api and the shared library store.\n * @returns disposer stopping the watch.\n */\nexport function watchGenerations(deps: GenerationWatchDeps): () => void {\n const setTimer = deps.setTimer ?? ((handler, ms) => setInterval(handler, ms))\n const clearTimer = deps.clearTimer ?? (handle => { clearInterval(handle) })\n /** Newest id already accounted for. */\n let newestId: string | undefined\n /** False until the first successful look, which only adopts. */\n let observed = false\n let stopped = false\n let polling = false\n\n const poll = async (): Promise<void> => {\n if (stopped || polling) return\n // A hidden page is not being looked at; skipping keeps an idle background tab\n // quiet. The next poll after the page becomes visible catches up.\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return\n polling = true\n try {\n const result = await deps.api.libraryHead()\n if (stopped) return\n if (!result.ok) return\n const head = result.value\n if (!observed) {\n // First look: adopt what is already there. A page load must never treat\n // the whole existing library as brand new.\n observed = true\n newestId = head.newestId\n return\n }\n // An empty library reports no id: the FIRST generation into it is a change\n // from `undefined`, not something to adopt away.\n if (head.newestId === undefined || head.newestId === newestId) return\n newestId = head.newestId\n await deps.library.refresh()\n } finally {\n polling = false\n }\n }\n\n const handle = setTimer(() => { void poll() }, deps.intervalMs ?? WATCH_INTERVAL_MS)\n return () => {\n stopped = true\n clearTimer(handle)\n }\n}\n","/**\n * Browser-side access to the local material library plus the pure view helpers\n * the sidebar filters with.\n *\n * The host serves every entry (metadata + image URLs) from one route, so the\n * sidebar filters locally: that keeps typing in the search box instant and\n * means a filter never costs a round trip.\n */\n\nimport type { LibraryEntry, LibraryListResult } from '../protocol.ts'\nimport { SeeWorkApi } from './api.ts'\nimport { SnapshotStore } from './snapshot-store.ts'\n\n/** Library state as the sidebar renders it. */\nexport interface LibraryState {\n status: 'loading' | 'ready' | 'error'\n entries: LibraryEntry[]\n imageCount: number\n dataRoot: string\n /** Human-readable reason when `status` is 'error'. */\n error?: string\n}\n\n/** What the sidebar needs from the library. */\nexport class LibraryStore {\n private readonly store = new SnapshotStore<LibraryState>({\n status: 'loading',\n entries: [],\n imageCount: 0,\n dataRoot: '',\n })\n private inFlight: Promise<void> | undefined\n\n constructor(private readonly api: SeeWorkApi) {}\n\n getSnapshot(): LibraryState {\n return this.store.getSnapshot()\n }\n\n subscribe(listener: () => void): () => void {\n return this.store.subscribe(listener)\n }\n\n /** Re-read the library; concurrent calls share one request. */\n refresh(): Promise<void> {\n if (this.inFlight !== undefined) return this.inFlight\n const pending = this.load().finally(() => {\n if (this.inFlight === pending) this.inFlight = undefined\n })\n this.inFlight = pending\n return pending\n }\n\n /**\n * Delete one entry and its files.\n * @returns true when the host accepted the deletion.\n */\n async remove(id: string): Promise<boolean> {\n const result = await this.api.removeEntry(id)\n if (!result.ok) {\n this.store.update(draft => {\n draft.status = 'error'\n draft.error = result.message\n })\n return false\n }\n await this.refresh()\n return true\n }\n\n /** Delete every entry. */\n async clear(): Promise<boolean> {\n const result = await this.api.clearLibrary()\n if (!result.ok) {\n this.store.update(draft => {\n draft.status = 'error'\n draft.error = result.message\n })\n return false\n }\n await this.refresh()\n return true\n }\n\n private async load(): Promise<void> {\n const result = await this.api.library()\n if (!result.ok) {\n // A failed refresh keeps the entries already on screen: a transient\n // bridge hiccup must not blank the sidebar.\n this.store.update(draft => {\n draft.status = 'error'\n draft.error = result.message\n })\n return\n }\n const value: LibraryListResult = result.value\n this.store.update(draft => {\n draft.status = 'ready'\n draft.entries = value.entries\n draft.imageCount = value.imageCount\n draft.dataRoot = value.dataRoot\n delete draft.error\n })\n }\n}\n\n/** What a generation was for, as the filter chips present it. */\nexport type SourceFilter = 'all' | LibraryEntry['source']\n\n/** One active filter set. */\nexport interface LibraryFilter {\n /** Free text matched against the prompt. */\n query: string\n /** Model id, or '' for every model. */\n model: string\n source: SourceFilter\n /** Only entries that day, as `YYYY-MM-DD`, or '' for every day. */\n day: string\n}\n\n/** The empty filter (everything). */\nexport const EMPTY_FILTER: LibraryFilter = { query: '', model: '', source: 'all', day: '' }\n\n/** Model ids present in the library, most used first, for the filter menu. */\nexport function libraryModels(entries: LibraryEntry[]): Array<{ id: string; count: number }> {\n const counts = new Map<string, number>()\n for (const entry of entries) counts.set(entry.model, (counts.get(entry.model) ?? 0) + 1)\n return [...counts.entries()]\n .map(([id, count]) => ({ id, count }))\n .sort((left, right) => right.count - left.count || left.id.localeCompare(right.id))\n}\n\n/** Apply the sidebar filter (case-insensitive substring match on the prompt). */\nexport function filterEntries(entries: LibraryEntry[], filter: LibraryFilter): LibraryEntry[] {\n const query = filter.query.trim().toLowerCase()\n return entries.filter(entry => {\n if (filter.model !== '' && entry.model !== filter.model) return false\n if (filter.source !== 'all' && entry.source !== filter.source) return false\n if (filter.day !== '' && dayOf(entry.createdAt) !== filter.day) return false\n if (query !== '' && !entry.prompt.toLowerCase().includes(query)) return false\n return true\n })\n}\n\n/** Local calendar day of a timestamp, as `YYYY-MM-DD`. */\nexport function dayOf(timestamp: number): string {\n const date = new Date(timestamp)\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n return `${date.getFullYear()}-${month}-${day}`\n}\n\n/** One day's entries, for the sidebar's date grouping. */\nexport interface LibraryDay {\n day: string\n /** Human label: 今天 / 昨天 / the date. */\n label: string\n entries: LibraryEntry[]\n}\n\n/** Group entries into day sections, newest first (input order is preserved). */\nexport function groupByDay(entries: LibraryEntry[], now = Date.now()): LibraryDay[] {\n const today = dayOf(now)\n const yesterday = dayOf(now - 24 * 60 * 60 * 1000)\n const groups = new Map<string, LibraryEntry[]>()\n for (const entry of entries) {\n const day = dayOf(entry.createdAt)\n const bucket = groups.get(day)\n if (bucket === undefined) groups.set(day, [entry])\n else bucket.push(entry)\n }\n return [...groups.entries()]\n .sort((left, right) => right[0].localeCompare(left[0]))\n .map(([day, bucket]) => ({\n day,\n label: day === today ? '今天' : day === yesterday ? '昨天' : day,\n entries: bucket,\n }))\n}\n\n/** Prompt text clipped to a preview length. */\nexport function promptPreview(prompt: string, max = 60): string {\n const flat = prompt.replace(/\\s+/g, ' ').trim()\n return flat.length <= max ? flat : `${flat.slice(0, max - 1)}…`\n}\n\n/** Total bytes the library occupies, when every image reports a size. */\nexport function entryCost(entry: LibraryEntry): string | undefined {\n return entry.cost === undefined ? undefined : `¥${entry.cost.toFixed(2)}`\n}\n","/**\n * 「加到画布」: put the picture the user is looking at onto a board.\n *\n * The automatic version of this (a finished generation opening the column and\n * dropping itself onto the board) was removed on request — a background\n * generation must not rearrange the screen. What remains is the deliberate,\n * one-click version: the user says \"this one\", and only then does the plugin\n * place the card and bring the canvas up so the result is visible.\n *\n * The button lives on surfaces this plugin owns (the enlarged-image lightbox and\n * the conversation card), and they reach the canvas through the injected\n * {@link CanvasAddFace}: those modules are mounted inside shell render trees, so\n * they cannot import the composition root — the same reason the header launchers\n * take their controls as a face.\n *\n * Placement reuses the picker's maths (centre of the visible stage, card sized\n * to the picture's own aspect ratio) and, like the old automatic path, waits\n * briefly for the revealed board to report a usable stage: a card placed against\n * a zero stage lands in the viewport's corner.\n */\n\nimport { centeredImageCard, stageCentre, usableStage } from '../canvas-placement.ts'\nimport type { CanvasCardOrigin } from '../protocol.ts'\nimport type { LibraryStore } from './library-store.ts'\nimport { unusedCardId, type CanvasStore } from './canvas-store.ts'\n\n/** How long one measurement step waits, and how many are taken before giving up. */\nexport const STAGE_WAIT_STEP_MS = 50\nexport const STAGE_WAIT_ATTEMPTS = 30\n\n/** One picture to put on a board, as the callers know it. */\nexport interface CanvasAddTarget {\n /** Library file name the board's card will reference. */\n file: string\n width?: number | undefined\n height?: number | undefined\n /** Model that produced it, shown on the card. */\n model?: string | undefined\n /** Prompt snapshot, shown on the card. */\n prompt?: string | undefined\n /** Where it came from, shown as the card badge (read off the library otherwise). */\n origin?: CanvasCardOrigin | undefined\n}\n\n/** What a surface needs to offer 「加到画布」. */\nexport interface CanvasAddFace {\n /**\n * Place one picture and bring the canvas into view.\n * @param target - the picture to place.\n * @returns true once a card was added.\n */\n add: (target: CanvasAddTarget) => Promise<boolean>\n}\n\n/** The face in force, or undefined while the composition root has not set one. */\nlet face: CanvasAddFace | undefined\n\n/**\n * Install the face the surfaces call.\n * @param next - the face, or undefined to remove it.\n * @returns disposer restoring the previous face.\n */\nexport function setCanvasAddFace(next: CanvasAddFace | undefined): () => void {\n const previous = face\n face = next\n return () => { face = previous }\n}\n\n/** Whether 「加到画布」 can do anything right now (surfaces hide the button when not). */\nexport function canvasAddAvailable(): boolean {\n return face !== undefined\n}\n\n/**\n * Ask for one picture to be placed.\n *\n * Fire-and-forget by design: a click on a button must not make the caller wait,\n * and a failure leaves the picture exactly where it already is — in the library,\n * which is where the user would look for it anyway.\n *\n * @param target - the picture to place.\n */\nexport function addToCanvas(target: CanvasAddTarget): void {\n const current = face\n if (current === undefined) return\n void current.add(target).catch(() => {})\n}\n\n/** What the controller needs from the composition root. */\nexport interface CanvasAddDeps {\n library: LibraryStore\n canvas: CanvasStore\n /** Bring the canvas into view (right-sidebar tab, or the floating overlay). */\n reveal: () => void\n /** Visible stage size, measured from the board that is actually on screen. */\n stageSize: () => { width: number; height: number }\n /** Sleep seam; tests pass a no-op. */\n sleep?: (ms: number) => Promise<void>\n}\n\n/**\n * Build the face the surfaces use.\n * @param deps - both stores, how to reveal the canvas, and the stage measurement.\n * @returns the face.\n */\nexport function createCanvasAddFace(deps: CanvasAddDeps): CanvasAddFace {\n const sleep = deps.sleep ?? (ms => new Promise<void>(resolve => { setTimeout(resolve, ms) }))\n\n /** An open board, creating one when the user has none yet. */\n const openBoard = async (): Promise<void> => {\n if (deps.canvas.getSnapshot().board !== undefined) return\n await deps.canvas.refreshList()\n const first = deps.canvas.getSnapshot().boards[0]\n // 'open' with an empty id creates a board; the canvas tab would do the same\n // a moment later, so an empty deployment behaves like a fresh canvas.\n await deps.canvas.open(first?.id ?? '')\n }\n\n /**\n * Wait briefly for the board on screen to report its size.\n *\n * Counted in attempts rather than wall-clock: the step is the same 50 ms either\n * way, and a caller that injects `sleep` (tests) drives it without real time.\n */\n const waitForStage = async (): Promise<void> => {\n for (let attempt = 0; attempt < STAGE_WAIT_ATTEMPTS; attempt += 1) {\n const size = deps.stageSize()\n if (size.width > 0 && size.height > 0) return\n await sleep(STAGE_WAIT_STEP_MS)\n }\n }\n\n /** Fill in anything the caller did not know, from the library record. */\n const complete = (target: CanvasAddTarget): CanvasAddTarget => {\n if (target.width !== undefined && target.height !== undefined && target.origin !== undefined) return target\n const entry = deps.library.getSnapshot().entries.find(candidate =>\n candidate.images.some(image => image.file === target.file))\n if (entry === undefined) return target\n const image = entry.images.find(candidate => candidate.file === target.file)\n return {\n ...target,\n width: target.width ?? image?.width,\n height: target.height ?? image?.height,\n model: target.model ?? entry.model,\n prompt: target.prompt ?? entry.prompt,\n origin: target.origin ?? (entry.source === 'panel' ? 'panel' : 'chat'),\n }\n }\n\n return {\n async add(target) {\n await openBoard()\n // Reveal BEFORE placing: the card goes to the middle of what is on screen,\n // and only a board that is up can say how big that is.\n deps.reveal()\n await waitForStage()\n const board = deps.canvas.getSnapshot().board\n if (board === undefined) return false\n const picture = complete(target)\n const centre = stageCentre(board.viewport, usableStage(deps.stageSize()))\n deps.canvas.addCards([centeredImageCard({\n file: picture.file,\n width: picture.width,\n height: picture.height,\n model: picture.model,\n prompt: picture.prompt,\n origin: picture.origin,\n }, centre, unusedCardId(board))])\n // Persist right away: the user is watching the board, and the next thing\n // they may do is close the panel.\n await deps.canvas.saveNow()\n return true\n },\n }\n}\n\n/**\n * The library file a plugin image URL serves, when it is one.\n * @param src - an image URL (relative or absolute).\n * @returns the file name, or undefined for another route.\n */\nexport function libraryFileFromUrl(src: string): string | undefined {\n const marker = '/api/dsh-seework/library/image/'\n const at = src.indexOf(marker)\n if (at < 0) return undefined\n const file = src.slice(at + marker.length).split(/[?#]/)[0] ?? ''\n try {\n return file === '' ? undefined : decodeURIComponent(file)\n } catch {\n return undefined\n }\n}\n","/**\n * The large preview for one material-library entry: full-size images with\n * paging, the complete prompt with a copy action, the generation parameters,\n * and the destructive actions.\n *\n * It reads the entry out of the live library state rather than taking a copy,\n * so a refresh or a deletion elsewhere is reflected here instead of showing a\n * stale record.\n */\n\nimport type { LibraryEntry } from '../protocol.ts'\nimport { useEffect, useState } from 'react'\nimport { addToCanvas, canvasAddAvailable } from './canvas-add.ts'\nimport { attachmentNameFor, composerAvailable, sendImageToConversation } from './composer-draft.ts'\nimport { entryCost } from './library-store.ts'\nimport type { LibraryStore } from './library-store.ts'\nimport { Button, copyText } from './controls.tsx'\nimport css from './library-detail.module.css'\n\n/** Human label for where a generation came from. */\nexport function SourceBadge({ source }: { source: LibraryEntry['source'] }): JSX.Element {\n const label = source === 'agent' ? '对话' : source === 'canvas' ? '画布' : '面板'\n return <span className={`${css.badge} ${source === 'agent' ? css.badgeAgent : source === 'canvas' ? css.badgeCanvas : css.badgePanel}`}>{label}</span>\n}\n\n/** Props for the detail overlay. */\nexport interface LibraryDetailProps {\n store: LibraryStore\n /** Entry to show; the overlay closes itself when the id disappears. */\n entryId: string\n onClose: () => void\n}\n\n/** The detail overlay. */\nexport function LibraryDetail({ store, entryId, onClose }: LibraryDetailProps): JSX.Element | null {\n const [tick, setTick] = useState(0)\n useEffect(() => store.subscribe(() => { setTick(value => value + 1) }), [store])\n void tick\n const [index, setIndex] = useState(0)\n const [copied, setCopied] = useState(false)\n const [confirming, setConfirming] = useState(false)\n\n const entry = store.getSnapshot().entries.find(candidate => candidate.id === entryId)\n /** Whether the plugin can place pictures right now (no canvas face, no action). */\n const addable = canvasAddAvailable()\n /** Whether a session-scope tab is mounted to hand the picture to (see composer-draft). */\n const toComposer = composerAvailable()\n const [composerState, setComposerState] = useState<'idle' | 'sent' | 'failed'>('idle')\n\n /** Hand the picture on screen to the conversation composer. */\n const sendCurrentToComposer = async (): Promise<void> => {\n if (image === undefined) return\n const ok = await sendImageToConversation({\n url: image.url,\n name: attachmentNameFor(image.file),\n }).catch(() => false)\n setComposerState(ok ? 'sent' : 'failed')\n setTimeout(() => { setComposerState('idle') }, 2500)\n }\n useEffect(() => {\n const onKey = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') onClose()\n if (entry === undefined) return\n if (event.key === 'ArrowRight') setIndex(current => Math.min(entry.images.length - 1, current + 1))\n if (event.key === 'ArrowLeft') setIndex(current => Math.max(0, current - 1))\n }\n document.addEventListener('keydown', onKey)\n return () => { document.removeEventListener('keydown', onKey) }\n }, [entry, onClose])\n\n if (entry === undefined) {\n // Deleted while open (here or in another window): nothing left to show.\n return null\n }\n const image = entry.images[Math.min(index, entry.images.length - 1)]\n\n const copyPrompt = (): void => {\n void copyText(entry.prompt).then(() => {\n setCopied(true)\n setTimeout(() => { setCopied(false) }, 1500)\n }).catch(() => { setCopied(false) })\n }\n\n return (\n <div className={css.overlay} role=\"dialog\" aria-modal=\"true\" aria-label=\"素材详情\">\n <div className={css.sheet}>\n <div className={css.stage}>\n {image !== undefined\n ? (\n <img\n className={css.image}\n src={image.url}\n alt={entry.prompt.slice(0, 40)}\n />\n )\n : <p className={css.muted}>这条记录没有图片。</p>}\n {entry.images.length > 1\n ? (\n <div className={css.pager}>\n <Button\n onClick={() => { setIndex(current => Math.max(0, current - 1)) }}\n disabled={index === 0}\n title=\"上一张\"\n >\n ←\n </Button>\n <span className={css.pagerText}>{Math.min(index, entry.images.length - 1) + 1} / {entry.images.length}</span>\n <Button\n onClick={() => { setIndex(current => Math.min(entry.images.length - 1, current + 1)) }}\n disabled={index >= entry.images.length - 1}\n title=\"下一张\"\n >\n →\n </Button>\n </div>\n )\n : null}\n </div>\n\n <div className={css.side}>\n <div className={css.sideHeader}>\n <SourceBadge source={entry.source} />\n <span className={css.muted}>{new Date(entry.createdAt).toLocaleString('zh-CN')}</span>\n <Button onClick={onClose} title=\"关闭(Esc)\">关闭</Button>\n </div>\n\n {image !== undefined\n ? (\n <div className={css.stageActions}>\n {addable\n ? (\n <Button\n variant=\"primary\"\n data-dsh-seework-add-to-canvas=\"\"\n title=\"把当前这张图放到画布上\"\n onClick={() => {\n addToCanvas({\n file: image.file,\n width: image.width,\n height: image.height,\n model: entry.model,\n prompt: entry.prompt,\n })\n }}\n >\n 加到画布\n </Button>\n )\n : null}\n {toComposer\n ? (\n <Button\n variant=\"outline\"\n data-dsh-seework-add-to-composer=\"\"\n title=\"把当前这张图放进对话输入框当草稿附件\"\n onClick={() => { void sendCurrentToComposer() }}\n >\n 加入到对话框\n </Button>\n )\n : null}\n <span className={css.muted}>\n 当前这张 {image.file}\n {composerState === 'sent' ? ' · 已放进输入框' : ''}\n {composerState === 'failed' ? ' · 放入输入框失败' : ''}\n </span>\n </div>\n )\n : null}\n\n <section className={css.block}>\n <div className={css.blockHead}>\n <h3 className={css.blockTitle}>提示词</h3>\n {/* A real button, on its own row: sitting under the text made it\n read as part of the prompt instead of as the copy control. */}\n <Button\n variant=\"outline\"\n className={css.copyButton}\n onClick={copyPrompt}\n title=\"复制这段提示词到剪贴板\"\n aria-label=\"复制提示词\"\n >\n {copied ? '已复制 ✓' : '复制提示词'}\n </Button>\n </div>\n <pre className={css.prompt}>{entry.prompt}</pre>\n </section>\n\n <section className={css.block}>\n <h3 className={css.blockTitle}>生成参数</h3>\n <dl className={css.facts}>\n <dt>模型</dt><dd>{entry.model}</dd>\n <dt>档位</dt><dd>{entry.resolution === '' ? '(未指定)' : entry.resolution}</dd>\n <dt>比例</dt><dd>{entry.aspectRatio === '' ? '(未指定)' : entry.aspectRatio}</dd>\n <dt>尺寸</dt><dd>{image === undefined || image.width === undefined || image.height === undefined ? '未知' : `${image.width} × ${image.height} 像素`}</dd>\n <dt>格式</dt><dd>{entry.outputFormat === '' ? '(网关默认)' : entry.outputFormat}</dd>\n <dt>张数</dt><dd>{entry.n}</dd>\n {entry.cost === undefined ? null : <><dt>扣费</dt><dd>{entryCost(entry)}</dd></>}\n {entry.refNames === undefined || entry.refNames.length === 0\n ? null\n : <><dt>参考图</dt><dd>{entry.refNames.join('、')}</dd></>}\n </dl>\n </section>\n\n <section className={css.block}>\n <h3 className={css.blockTitle}>文件</h3>\n <ul className={css.files}>\n {entry.images.map((item, position) => (\n <li key={item.file}>\n <a className={css.fileLink} href={item.url} target=\"_blank\" rel=\"noreferrer\">\n {item.file}\n </a>\n <span className={css.muted}>\n {item.width !== undefined && item.height !== undefined ? `${item.width}×${item.height}` : ''}\n {' '}\n {item.mime}\n {position === Math.min(index, entry.images.length - 1) ? '(当前)' : ''}\n </span>\n </li>\n ))}\n </ul>\n </section>\n\n <section className={css.block}>\n {confirming\n ? (\n <div className={css.confirm}>\n <span>删除后图片文件也会从磁盘移除,确定吗?</span>\n <div className={css.confirmActions}>\n <Button\n onClick={() => {\n void store.remove(entry.id).then(ok => {\n if (ok) onClose()\n else setConfirming(false)\n })\n }}\n >\n 确定删除\n </Button>\n <Button onClick={() => { setConfirming(false) }}>取消</Button>\n </div>\n </div>\n )\n : (\n <Button onClick={() => { setConfirming(true) }}>删除这条记录</Button>\n )}\n </section>\n </div>\n </div>\n </div>\n )\n}\n","/**\n * The SeeWork material library sidebar (phase 2).\n *\n * Everything the user ever generated through this plugin is already on disk and\n * indexed host-side, so this surface is a view over `library/list`: search,\n * filter by model / source / day, group by day, and open a large preview.\n *\n * Mounted at the DOM level (a launcher button plus a left drawer) rather than\n * through a shell slot, for the same reason the settings surface is: a\n * third-party plugin must not depend on shell-private layout.\n */\n\nimport { useEffect, useMemo, useState } from 'react'\nimport type { LibraryEntry } from '../protocol.ts'\nimport {\n EMPTY_FILTER,\n entryCost,\n filterEntries,\n groupByDay,\n libraryModels,\n promptPreview,\n type LibraryFilter,\n type LibraryState,\n type LibraryStore,\n} from './library-store.ts'\nimport { Button, TextInput } from './controls.tsx'\nimport { addToCanvas, canvasAddAvailable } from './canvas-add.ts'\nimport { SourceBadge } from './library-detail.tsx'\nimport css from './library-panel.module.css'\n\n/** Sidebar props (it owns no lifecycle of its own — the mount point does). */\nexport interface LibraryPanelProps {\n store: LibraryStore\n /** Close the drawer (the launcher toggles it). */\n onClose: () => void\n /** Open one entry in the large preview. */\n onOpen: (id: string) => void\n /**\n * Hide the 收起 button. In the right sidebar the tab strip's own ✕ closes the\n * surface, so a second close control would be redundant chrome.\n */\n hideClose?: boolean\n /**\n * Render as a right-sidebar tab body: fill the shell's panel instead of\n * floating over the viewport.\n */\n tab?: boolean\n}\n\n/** The library drawer body. */\nexport function LibraryPanel({ store, onClose, onOpen, hideClose = false, tab = false }: LibraryPanelProps): JSX.Element {\n const [tick, setTick] = useState(0)\n useEffect(() => store.subscribe(() => { setTick(value => value + 1) }), [store])\n void tick\n\n const state: LibraryState = store.getSnapshot()\n const [filter, setFilter] = useState<LibraryFilter>(EMPTY_FILTER)\n const [refreshing, setRefreshing] = useState(false)\n /** Whether the plugin can place pictures right now (no canvas face, no button). */\n const addable = canvasAddAvailable()\n\n const filtered = useMemo(() => filterEntries(state.entries, filter), [state.entries, filter])\n const days = useMemo(() => groupByDay(filtered), [filtered])\n const models = useMemo(() => libraryModels(state.entries), [state.entries])\n const active = filter.query !== '' || filter.model !== '' || filter.source !== 'all' || filter.day !== ''\n\n const refresh = (): void => {\n setRefreshing(true)\n void store.refresh().finally(() => { setRefreshing(false) })\n }\n\n return (\n <aside className={tab ? `${css.panel} ${css.tabPanel}` : css.panel} aria-label=\"SeeWork 素材库\">\n <header className={css.header}>\n <div className={css.titleRow}>\n <h2 className={css.title}>SeeWork 素材库</h2>\n <div className={css.headerActions}>\n <Button onClick={refresh} disabled={refreshing} title=\"重新读取素材库\">\n {refreshing ? '刷新中…' : '刷新'}\n </Button>\n {hideClose\n ? null\n : <Button onClick={onClose} title=\"收起素材库\">收起</Button>}\n </div>\n </div>\n <TextInput\n value={filter.query}\n placeholder=\"搜索提示词…\"\n spellCheck={false}\n onChange={event => { setFilter(current => ({ ...current, query: event.target.value })) }}\n />\n <div className={css.filters}>\n <label className={css.filter}>\n <span className={css.filterLabel}>模型</span>\n <select\n className={css.select}\n value={filter.model}\n onChange={event => { setFilter(current => ({ ...current, model: event.target.value })) }}\n >\n <option value=\"\">全部</option>\n {models.map(model => (\n <option key={model.id} value={model.id}>{model.id}({model.count})</option>\n ))}\n </select>\n </label>\n <label className={css.filter}>\n <span className={css.filterLabel}>来源</span>\n <select\n className={css.select}\n value={filter.source}\n onChange={event => { setFilter(current => ({ ...current, source: event.target.value as LibraryFilter['source'] })) }}\n >\n <option value=\"all\">全部</option>\n <option value=\"agent\">对话生成</option>\n <option value=\"panel\">面板生成</option>\n <option value=\"canvas\">画布生成</option>\n </select>\n </label>\n </div>\n {filter.day !== ''\n ? (\n <div className={css.activeFilters}>\n <button type=\"button\" className={css.chip} onClick={() => { setFilter(current => ({ ...current, day: '' })) }}>\n 只看 {filter.day} ✕\n </button>\n </div>\n )\n : null}\n </header>\n\n <div className={css.body}>\n {state.status === 'error'\n ? <p className={css.error}>{state.error ?? '素材库读取失败。'}{state.entries.length > 0 ? '(下面是上次读到的内容)' : ''}</p>\n : null}\n {state.status === 'loading' && state.entries.length === 0\n ? <p className={css.muted}>正在读取素材库…</p>\n : null}\n {state.status !== 'loading' && state.entries.length === 0\n ? (\n <div className={css.empty}>\n <p className={css.emptyTitle}>还没有生成过图片</p>\n <p className={css.muted}>在对话里让 Agent 画一张图,成品就会出现在这里。</p>\n </div>\n )\n : null}\n {state.entries.length > 0 && filtered.length === 0\n ? (\n <p className={css.muted}>\n 没有符合当前条件的图片。\n <button type=\"button\" className={css.linkButton} onClick={() => { setFilter(EMPTY_FILTER) }}>清除筛选</button>\n </p>\n )\n : null}\n\n {days.map(day => (\n <section key={day.day} className={css.day}>\n <button\n type=\"button\"\n className={css.dayHeader}\n title={`只看 ${day.day}`}\n onClick={() => { setFilter(current => ({ ...current, day: current.day === day.day ? '' : day.day })) }}\n >\n <span>{day.label}</span>\n <span className={css.dayCount}>{day.entries.length}</span>\n </button>\n <ul className={css.list}>\n {day.entries.map(entry => {\n const first = entry.images[0]\n return (\n <li key={entry.id}>\n <article className={css.card}>\n <button\n type=\"button\"\n className={css.thumbButton}\n onClick={() => { onOpen(entry.id) }}\n title=\"查看大图\"\n >\n <img\n className={css.thumb}\n src={first?.url ?? ''}\n alt={promptPreview(entry.prompt, 24)}\n loading=\"lazy\"\n />\n {entry.images.length > 1\n ? <span className={css.imageCount}>{entry.images.length}</span>\n : null}\n </button>\n <div className={css.cardBody}>\n <button type=\"button\" className={css.promptButton} onClick={() => { onOpen(entry.id) }}>\n {promptPreview(entry.prompt)}\n </button>\n <div className={css.meta}>\n <SourceBadge source={entry.source} />\n <span className={css.metaText}>{entry.model}</span>\n {entry.cost !== undefined\n ? <span className={css.metaText}>{entryCost(entry)}</span>\n : null}\n <time className={css.metaText} dateTime={new Date(entry.createdAt).toISOString()}>\n {new Date(entry.createdAt).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}\n </time>\n {/* Puts the picture the card shows onto the board, and brings\n the canvas up so the result is visible. */}\n {addable && first !== undefined\n ? (\n <button\n type=\"button\"\n className={css.addButton}\n data-dsh-seework-add-to-canvas=\"\"\n title=\"把这张图放到画布上\"\n onClick={() => {\n addToCanvas({\n file: first.file,\n width: first.width,\n height: first.height,\n model: entry.model,\n prompt: entry.prompt,\n })\n }}\n >\n 加到画布\n </button>\n )\n : null}\n </div>\n </div>\n </article>\n </li>\n )\n })}\n </ul>\n </section>\n ))}\n </div>\n\n <footer className={css.footer}>\n <span>\n {filtered.length === state.entries.length\n ? `${state.entries.length} 次生成 · ${state.imageCount} 张图`\n : `筛出 ${filtered.length} / ${state.entries.length} 次生成`}\n </span>\n {state.dataRoot !== ''\n ? <span className={css.footerPath} title={state.dataRoot}>{state.dataRoot}</span>\n : null}\n {active\n ? <button type=\"button\" className={css.linkButton} onClick={() => { setFilter(EMPTY_FILTER) }}>清除筛选</button>\n : null}\n </footer>\n </aside>\n )\n}\n","/**\n * Mount point for the material-library surfaces: a launcher button in the\n * shared SeeWork dock, the left drawer, and the detail overlay.\n *\n * The store is passed in rather than created here: the canvas also reads the\n * library (its picture picker), and two stores would mean two caches of the\n * same list — one of them stale.\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport type { SeeWorkApi } from './api.ts'\nimport type { LibraryStore } from './library-store.ts'\nimport { LibraryPanel } from './LibraryPanel.tsx'\nimport { LibraryDetail } from './library-detail.tsx'\nimport { reportSurfaceOpen } from './header-launchers.tsx'\nimport { ACTIVATE_EVENT, announceActivation, onActivation } from './settings-panel.tsx'\nimport { attachLauncher } from './surfaces.ts'\nimport { useEffect, useRef, useState } from 'react'\nimport launcherCss from './surfaces.module.css'\nimport css from './library-panel.module.css'\n\n/** Marks the library launcher so other surfaces can find it by selector. */\nexport const LIBRARY_LAUNCHER_ATTR = 'data-dsh-seework-library-launcher'\n\n/** The drawer and its detail overlay; the launcher lives in the dock. */\nfunction LibrarySurfaces({ store, api, launcher }: {\n store: LibraryStore\n api: SeeWorkApi\n launcher: HTMLElement\n}): JSX.Element | null {\n void api\n const [open, setOpen] = useState(false)\n const [detailId, setDetailId] = useState<string | undefined>(undefined)\n const [tick, setTick] = useState(0)\n useEffect(() => store.subscribe(() => { setTick(value => value + 1) }), [store])\n void tick\n\n const state = store.getSnapshot()\n\n // The launcher button is outside this tree (it lives in the shared dock), so\n // its label and click handler are wired imperatively.\n useEffect(() => {\n const render = (): void => {\n const current = store.getSnapshot()\n launcher.textContent = ''\n launcher.append('素材库')\n if (current.entries.length > 0) {\n const badge = document.createElement('span')\n badge.className = launcherCss.launcherCount\n badge.textContent = String(current.imageCount)\n launcher.append(badge)\n }\n launcher.title = '查看用 SeeWork 生成过的图片'\n launcher.setAttribute('aria-expanded', String(open))\n }\n render()\n const stop = store.subscribe(render)\n return () => { stop() }\n }, [launcher, open, store])\n\n useEffect(() => {\n const onClick = (): void => {\n const next = !open\n setOpen(next)\n if (next) {\n announceActivation('library')\n // The conversation may have generated something since the last look.\n void store.refresh()\n }\n }\n launcher.addEventListener('click', onClick)\n return () => { launcher.removeEventListener('click', onClick) }\n }, [launcher, open, store])\n\n // Only one SeeWork surface is open at a time, and announcing this surface is\n // how anything outside this tree (the conversation header) asks for it.\n useEffect(() => {\n const onOtherActivate = (event: Event): void => {\n if ((event as CustomEvent).detail !== 'library') setOpen(false)\n }\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n return () => { document.removeEventListener(ACTIVATE_EVENT, onOtherActivate) }\n }, [])\n\n // The header launcher asks for the library by announcing it; the surface has\n // this one entry point so there is no second way to open it.\n useEffect(() => onActivation(surface => {\n if (surface !== 'library') return\n setOpen(true)\n // The conversation may have generated something since the last look.\n void store.refresh()\n }), [store])\n\n // The header button toggles, which means it has to know whether this surface\n // is really up — the drawer can also be closed from its own chrome.\n const openRef = useRef(false)\n openRef.current = open\n useEffect(() => reportSurfaceOpen('library', () => openRef.current), [])\n\n if (!open) return null\n return (\n <>\n <LibraryPanel\n store={store}\n onClose={() => {\n setOpen(false)\n setDetailId(undefined)\n }}\n onOpen={id => { setDetailId(id) }}\n />\n {detailId === undefined\n ? null\n : <LibraryDetail store={store} entryId={detailId} onClose={() => { setDetailId(undefined) }} />}\n </>\n )\n}\n\n/**\n * Mount the library launcher, drawer and detail overlay.\n * @param api - the plugin route client.\n * @param store - the shared library store (also read by the canvas).\n * @returns disposer unmounting everything this created.\n */\nexport function mountLibraryPanel(api: SeeWorkApi, store: LibraryStore): () => void {\n const host = document.createElement('div')\n document.body.appendChild(host)\n const launcher = document.createElement('button')\n launcher.type = 'button'\n launcher.setAttribute(LIBRARY_LAUNCHER_ATTR, '')\n launcher.className = launcherCss.launcher\n const detachLauncher = attachLauncher(launcher)\n // Warm the count badge so the launcher is meaningful before it is opened.\n void store.refresh()\n\n const root: Root = createRoot(host)\n root.render(<LibrarySurfaces store={store} api={api} launcher={launcher} />)\n return () => {\n root.unmount()\n detachLauncher()\n host.remove()\n }\n}\n","/**\n * Browser-side settings scope for the `dsh-seework` namespace.\n *\n * The shell's own settings transport only serves namespaces it knows, so a\n * third-party plugin re-serves its namespace over its own same-origin,\n * loopback-only HTTP pair (the settings bridge mounted by the host half). This\n * module implements the same read/write contract the shell's scopes expose —\n * serialized writes, revision-fenced mutations, a recovery read after a\n * refusal — over that bridge.\n */\n\nimport { SnapshotStore } from './snapshot-store.ts'\nimport { SETTINGS_API, type ModelConfig } from '../protocol.ts'\n\n/** The fields this plugin's settings card edits. */\nexport interface SeeWorkConfig {\n enabled?: boolean\n allowAgentGeneration?: boolean\n announceToAgent?: boolean\n apiUrl?: string\n /** SeeAI Hub service base used only for the model catalog. */\n serviceUrl?: string\n /** Redacted on the wire: presence comes from the `secrets` sidecar. */\n apiKey?: string\n models?: ModelConfig[]\n defaultModel?: string\n defaultAspectRatio?: string\n outputFormat?: string\n dataDir?: string\n}\n\n/** One settings path-op as the bridge consumes it. */\nexport type SettingsOp = { op: 'set'; path: string[]; value: unknown } | { op: 'unset'; path: string[] }\n\n/** Snapshot shape shared with the shell's `SettingsScope` consumers. */\nexport interface ScopeSnapshot<T> {\n status: 'loading' | 'ready' | 'unavailable'\n value: T | undefined\n base: unknown\n user: unknown\n revision: number | undefined\n writable: boolean\n}\n\n/** The bound scope plus the secret-presence view the card needs. */\nexport interface SeeWorkScope {\n getSnapshot(): ScopeSnapshot<SeeWorkConfig>\n subscribe(listener: () => void): () => void\n /** Queue a bridge refresh. */\n load(): Promise<void>\n set(field: string, value: unknown): Promise<void>\n unset(field: string): Promise<void>\n /** Apply several path ops in one revision-fenced call (atomic save). */\n mutateOps(ops: SettingsOp[]): Promise<void>\n /** Whether a stored API key currently exists (the wire never returns it). */\n getSecretSet(field: string): boolean\n subscribeSecrets(listener: () => void): () => void\n}\n\n/** Wire shape of one namespace view from the bridge. */\ninterface BridgeView {\n ns: string\n value: unknown\n base?: unknown\n user?: unknown\n revision: number\n secrets?: Array<{ path: string[]; set: boolean }>\n}\n\n/** The bridge response envelope. */\ntype BridgeEnvelope =\n | { ok: true; value: { namespaces?: BridgeView[]; writable?: boolean } | BridgeView }\n | { ok: false; code: string; message: string }\n\n/** POST one bridge request and decode its envelope. */\nasync function post(path: string, body: unknown, fetchFn: typeof fetch): Promise<BridgeEnvelope> {\n try {\n const response = await fetchFn(path, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!response.ok) {\n return { ok: false, code: 'internal', message: `设置接口返回 HTTP ${response.status}` }\n }\n return await response.json() as BridgeEnvelope\n } catch {\n return { ok: false, code: 'internal', message: '设置接口不可达' }\n }\n}\n\n/** The scope implementation over the bridge. */\nclass BridgeScope implements SeeWorkScope {\n private readonly store: SnapshotStore<ScopeSnapshot<SeeWorkConfig>>\n private readonly secrets: SnapshotStore<Record<string, boolean>>\n private tail: Promise<void> = Promise.resolve()\n private disposed = false\n\n constructor(private readonly fetchFn: typeof fetch) {\n this.store = new SnapshotStore<ScopeSnapshot<SeeWorkConfig>>({\n status: 'loading',\n value: undefined,\n base: undefined,\n user: undefined,\n revision: undefined,\n writable: false,\n })\n this.secrets = new SnapshotStore<Record<string, boolean>>({})\n }\n\n getSnapshot(): ScopeSnapshot<SeeWorkConfig> {\n return this.store.getSnapshot()\n }\n\n subscribe(listener: () => void): () => void {\n return this.store.subscribe(listener)\n }\n\n subscribeSecrets(listener: () => void): () => void {\n return this.secrets.subscribe(listener)\n }\n\n getSecretSet(field: string): boolean {\n return this.secrets.getSnapshot()[field] === true\n }\n\n load(): Promise<void> {\n return this.enqueue(() => this.read())\n }\n\n set(field: string, value: unknown): Promise<void> {\n return this.enqueue(() => this.write([{ op: 'set', path: [field], value }]))\n }\n\n unset(field: string): Promise<void> {\n return this.enqueue(() => this.write([{ op: 'unset', path: [field] }]))\n }\n\n mutateOps(ops: SettingsOp[]): Promise<void> {\n return this.enqueue(() => this.write(ops))\n }\n\n /** Queue one operation behind every earlier one. */\n private enqueue(operation: () => Promise<void>): Promise<void> {\n if (this.disposed) return Promise.resolve()\n const task = this.tail.then(async () => {\n if (this.disposed) return\n await operation()\n })\n this.tail = task.catch(() => {})\n return task\n }\n\n private async read(): Promise<void> {\n const envelope = await post(SETTINGS_API.describe, {}, this.fetchFn)\n if (this.disposed) return\n if (!envelope.ok) {\n this.store.update(draft => { draft.status = 'unavailable' })\n return\n }\n const { namespaces, writable } = envelope.value as { namespaces?: BridgeView[]; writable?: boolean }\n const view = namespaces?.[0]\n if (view === undefined) {\n this.store.update(draft => {\n draft.status = 'unavailable'\n draft.writable = writable === true\n })\n this.secrets.set({})\n return\n }\n this.accept(view, writable)\n }\n\n private async write(ops: SettingsOp[]): Promise<void> {\n const revision = this.getSnapshot().revision\n const envelope = await post(SETTINGS_API.mutate, {\n ns: 'dsh-seework',\n ops,\n ...revision === undefined ? {} : { expectedRevision: revision },\n }, this.fetchFn)\n if (this.disposed) return\n if (!envelope.ok) {\n // A refused write (validation or revision conflict) leaves the form to\n // re-read the authoritative document rather than guessing the outcome.\n await this.read()\n return\n }\n this.accept(envelope.value as BridgeView, undefined)\n }\n\n /** Fold one bridge view into the snapshot and the secret-presence sidecar. */\n private accept(view: BridgeView, writable: boolean | undefined): void {\n this.store.update(draft => {\n draft.revision = view.revision\n draft.base = view.base\n draft.user = view.user\n if (writable !== undefined) draft.writable = writable\n draft.status = 'ready'\n draft.value = view.value as SeeWorkConfig\n })\n const bits: Record<string, boolean> = {}\n for (const secret of view.secrets ?? []) bits[secret.path.join('.')] = secret.set\n this.secrets.set(bits)\n }\n}\n\n/**\n * Bind the plugin's settings scope and start its initial read.\n * @param fetchFn - the fetch implementation (the page's own fetch on loopback).\n * @returns the bound scope.\n */\nexport function bindSeeWorkScope(fetchFn: typeof fetch = fetch): SeeWorkScope {\n const scope = new BridgeScope(fetchFn)\n void scope.load()\n return scope\n}\n","/**\n * Conversation images: show them small, open them big.\n *\n * The transcript renders our images itself (the assistant's markdown reply, and\n * the card we register into the tool row), so this module owns the two things\n * that make them usable rather than obnoxious:\n *\n * 1. **A display cap.** A 1K–4K result is a multi-megabyte picture; rendered at\n * its natural size one image fills the whole window. Markdown cannot carry a\n * size, and the shell's own `<img>` is not ours to class, so the cap is a\n * style rule keyed on the plugin's own URL prefix — it cannot leak onto\n * anybody else's images, and the card's own thumbnails (which have their own\n * attribute) keep their own smaller size.\n * 2. **Click to enlarge.** The shell's markdown renders a bare `<img>` with no\n * link, and our card's link opens a raw tab; instead a plain left click on\n * one of these images opens an in-app lightbox (Esc or a click outside\n * closes it). Ctrl/middle click still falls through to the browser, so\n * \"open the original in a tab\" stays available.\n *\n * Failure policy matches the rest of the plugin: no slot, no shell hook, nothing\n * here throws — a page without the chat (the home screen) simply never matches.\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport { addToCanvas, canvasAddAvailable, type CanvasAddTarget } from './canvas-add.ts'\nimport css from './chat-images.module.css'\n\n/** Marker on the card's own thumbnails, which size themselves. */\nexport const CARD_IMAGE_ATTR = 'data-dsh-seework-card-image'\n\n/** Attribute marking the lightbox host, so anything can find it. */\nexport const LIGHTBOX_ATTR = 'data-dsh-seework-image-zoom'\n\n/**\n * Images this module sizes and zooms: images the chat nodes render, whose source\n * is one of this plugin's routes. Scoped to the chat, so the material library and\n * the canvas keep their own layout.\n */\nexport const CHAT_IMAGE_SELECTOR = `[data-chat-anchor-key] img[src*=\"/api/dsh-seework/\"]:not([${CARD_IMAGE_ATTR}])`\n\n/** Same scope, but including the card's thumbnails, for the zoom click. */\nconst ZOOMABLE_SELECTOR = `[data-chat-anchor-key] img[src*=\"/api/dsh-seework/\"]`\n\n/** Cap on a conversation image's rendered size (the original opens on click). */\nconst THUMBNAIL_MAX_PX = 320\n\n/** The style rule that keeps a generated picture from taking the whole window. */\nexport const CHAT_IMAGE_CSS = `${CHAT_IMAGE_SELECTOR} {\n max-width: ${THUMBNAIL_MAX_PX}px;\n max-height: ${THUMBNAIL_MAX_PX}px;\n width: auto;\n height: auto;\n border-radius: 10px;\n cursor: zoom-in;\n}\n${ZOOMABLE_SELECTOR} {\n cursor: zoom-in;\n}\n`\n\n/** Is this element one of the conversation images we own? */\nfunction zoomableImage(target: EventTarget | null): HTMLImageElement | undefined {\n if (!(target instanceof Element)) return undefined\n const image = target.closest('img')\n if (image === null || !(image instanceof HTMLImageElement)) return undefined\n return image.matches(ZOOMABLE_SELECTOR) ? image : undefined\n}\n\n/** Whether this click should open the lightbox rather than the browser's own. */\nfunction isPlainLeftClick(event: MouseEvent): boolean {\n return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey\n}\n\n/**\n * The enlarged view: the picture at up to the viewport, closed by a click\n * outside. When the picture is one the plugin can place, it also offers\n * 「加到画布」 — the deliberate, one-click way to put it on a board.\n */\nexport function ChatImageLightbox(\n { src, onClose, onAddToCanvas }: { src: string; onClose: () => void; onAddToCanvas?: (() => void) | undefined },\n): JSX.Element {\n return (\n <div\n className={css.backdrop}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"查看大图\"\n onClick={onClose}\n >\n <img\n className={css.full}\n src={src}\n alt=\"生成的图片(大图)\"\n onClick={event => { event.stopPropagation() }}\n />\n {onAddToCanvas === undefined ? null : (\n <div className={css.actions} onClick={event => { event.stopPropagation() }}>\n <button\n type=\"button\"\n className={css.action}\n data-dsh-seework-add-to-canvas=\"\"\n onClick={onAddToCanvas}\n >\n 加到画布\n </button>\n </div>\n )}\n </div>\n )\n}\n\n/** One lightbox at a time, mounted on demand. */\nlet lightbox: { host: HTMLElement; root: Root } | undefined\n\n/**\n * Show one conversation image enlarged.\n * @param src - the image's source (the original, not a thumbnail copy).\n * @param doc - the document to mount into (tests pass their jsdom document).\n * @param target - the library picture behind it, when the plugin can place it.\n */\nexport function openChatImage(src: string, doc: Document = document, target?: CanvasAddTarget): void {\n closeChatImage()\n const host = doc.createElement('div')\n host.setAttribute(LIGHTBOX_ATTR, '')\n doc.body.appendChild(host)\n const root = createRoot(host)\n lightbox = { host, root }\n const onAdd = target === undefined || !canvasAddAvailable()\n ? undefined\n : () => {\n // Close first: the overlay covers the window, and the point of adding is\n // to see the board it landed on.\n closeChatImage()\n addToCanvas(target)\n }\n root.render(<ChatImageLightbox src={src} onClose={() => { closeChatImage() }} onAddToCanvas={onAdd} />)\n}\n\n/** Put the enlarged view away, when there is one. */\nexport function closeChatImage(): void {\n const current = lightbox\n if (current === undefined) return\n lightbox = undefined\n current.root.unmount()\n current.host.remove()\n}\n\n/**\n * Install the cap and the click-to-enlarge behaviour.\n *\n * @param doc - the document to watch (tests pass their jsdom document).\n * @param resolveTarget - turns an image URL into the library picture behind it,\n * when the plugin can place that picture (see {@link CanvasAddTarget}).\n * @returns disposer removing the style, the listeners, and any open lightbox.\n */\nexport function installChatImageZoom(\n doc: Document = document,\n resolveTarget?: (src: string) => CanvasAddTarget | undefined,\n): () => void {\n const style = doc.createElement('style')\n style.setAttribute('data-dsh-seework', 'chat-image-zoom')\n style.textContent = CHAT_IMAGE_CSS\n doc.head.appendChild(style)\n\n const onClick = (event: MouseEvent): void => {\n const image = zoomableImage(event.target)\n if (image === undefined || !isPlainLeftClick(event)) return\n // The image sits inside shell chrome (and, in the card, inside a link): take\n // the click before either of them turns it into a page navigation.\n event.preventDefault()\n event.stopPropagation()\n const src = image.currentSrc === '' ? image.src : image.currentSrc\n const target = resolveTarget === undefined ? undefined : resolveTarget(src)\n // The card tells us which library picture its thumbnail shows; a markdown\n // image says it through the URL. Either way the lightbox can offer the action.\n openChatImage(src, doc, target)\n }\n doc.addEventListener('click', onClick, true)\n\n // Escape closes the enlarged view. Owned here rather than inside the lightbox\n // component: the overlay lives in its own React root, while the plugin's own\n // lifetime is exactly the lifetime of this listener.\n const onKey = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') closeChatImage()\n }\n doc.addEventListener('keydown', onKey)\n\n return () => {\n doc.removeEventListener('click', onClick, true)\n doc.removeEventListener('keydown', onKey)\n style.remove()\n closeChatImage()\n }\n}\n","/**\n * SeeWork's image card inside the conversation (the tool-call row).\n *\n * A generated image is deliberately NOT part of the model-visible tool result:\n * the result text stays JSON so text-only conversation models keep working, and\n * the attachment references ride the result's presentation metadata instead.\n * That leaves the row itself generic — a wall of JSON — unless the plugin that\n * owns the tool supplies its own view, which is what this module does.\n *\n * Why the images are fetched here rather than through the shell:\n *\n * - the shell's `tool.call.images` gallery slot is declared as a child slot by\n * the built-in `read_image` row and a slot has exactly one owner, so a second\n * declarer would make the whole client bundle fail to load;\n * - the shell's session-authorized `loadImage` resolves attachments the session\n * log references, and a tool result's images are not in it (by design), so it\n * would refuse them;\n * - therefore the references come from the persisted `block.meta` and the bytes\n * from this plugin's own loopback-only attachment route.\n *\n * Failure policy: this view never throws. Anything it cannot read simply does\n * not render, and the shell's flattened result text remains the fallback.\n */\n\nimport { useMemo, useState } from 'react'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { ATTACHMENT_API, isImageMedia } from '../protocol.ts'\nimport { addToCanvas, canvasAddAvailable } from './canvas-add.ts'\nimport css from './tool-card.module.css'\n\n/** One image record as the host persists it in the tool result's metadata. */\nexport interface ToolCardImage {\n attachment_id: string\n media_type: string\n bytes: number\n width: number\n height: number\n name?: string\n /** Library file name, when the host sent one (see `presentationMeta`). */\n file?: string\n}\n\n/**\n * The frozen call/result node the shell hands a keyed tool view.\n *\n * Spelled here (like this package's other slot contracts) instead of importing\n * the sibling UI package: only the fields this view reads are declared, and\n * every one of them is treated as unvalidated wire data.\n */\nexport interface ToolCardBlock {\n /** Absent while the call is still running (the shell's running-call shape). */\n kind?: string\n content?: unknown\n meta?: unknown\n isError?: boolean\n}\n\n/** Owner payload the shell supplies to a `tool.call.toolview` registration. */\nexport interface ToolCardProps {\n callId?: string\n toolName?: string\n block?: ToolCardBlock\n}\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface SlotMap {\n /**\n * Keyed per wire Tool name: a hit REPLACES the shell's generic row with the\n * registered view, which is how SeeWork's results render as picture cards.\n */\n 'tool.call.toolview': { kind: 'keyed'; scope: 'session'; owner: ToolCardProps }\n }\n}\n\n/**\n * Tool names whose results carry images.\n *\n * `get_seework_task` is no longer registered by the host half — SeeAI Hub's image\n * API is synchronous, so the plugin stopped offering a task handle — but this key\n * stays on purpose: results of that name are already persisted in session logs,\n * and dropping the key would turn those historical rows back into raw JSON. Same\n * reasoning as the board still reading cards written before `origin` existed.\n *\n * `edit_image` deliberately does **not** get that treatment (#664): the tool was\n * merged into `generate_image` while this plugin is still in development, and a\n * historical edit row is allowed to fall back to the shell's generic row. Its\n * images are still in the material library, so nothing is lost by not having a\n * bespoke card for it.\n */\nexport const TOOL_CARD_KEYS = ['generate_image', 'get_seework_task'] as const\n\n/**\n * Priority of these registrations inside the keyed slot.\n *\n * Two plugins can (and on a real machine do) claim the same wire tool name — the\n * reference image plugin registers its own view for `generate_image`. The slot\n * registry refuses a second registration at the SAME priority and fails the\n * whole plugin that lost the race (observed live: the other plugin's loader\n * entry failed with \"already has an entry for key generate_image at priority 0 …\n * register at a different priority to shadow it\"), so claiming a distinct, lower\n * priority is not a nicety — it is how both plugins stay loadable, and \"lowest\n * renders\" makes this card the one shown.\n *\n * That is the right winner here: this view reads the images from the result's\n * persisted `meta`, which both plugins emit under the same field names, while a\n * view that reads them out of the result content has nothing to show (tool\n * results are not model-visible content).\n */\nconst TOOL_CARD_PRIORITY = -10\n\n/** Human-readable state for the result JSON's `status`. */\nfunction statusLabel(status: string): string {\n if (status === 'running' || status === 'queued') return '生成中'\n if (status === 'failed') return '生成失败'\n if (status === 'cancelled') return '已取消'\n return '已生成'\n}\n\n/**\n * Card titles. The shell prints the wire tool name above the row, which is not\n * something a user should have to read, so the card says what it did instead.\n * @param toolName - the wire tool name.\n * @returns the display title.\n */\nfunction cardTitle(toolName: string): string {\n if (toolName === 'get_seework_task') return 'SeeWork 生图任务'\n return 'SeeWork 生图'\n}\n\n/** Whether the node is a settled result rather than a running call. */\nfunction isSettled(block: ToolCardBlock | undefined): boolean {\n return block !== undefined && typeof block.kind === 'string'\n}\n\n/** The result's own JSON text, if the row carries exactly what we wrote. */\nfunction resultText(block: ToolCardBlock | undefined): string {\n if (block === undefined || !Array.isArray(block.content)) return ''\n return block.content\n .flatMap(part => {\n if (typeof part !== 'object' || part === null) return []\n const { type, text } = part as { type?: unknown; text?: unknown }\n return type === 'text' && typeof text === 'string' ? [text] : []\n })\n .join('\\n')\n}\n\n/** `status` / `message` from that JSON, degrading to the raw text. */\nfunction resultInfo(block: ToolCardBlock | undefined): { status: string; message: string } {\n if (!isSettled(block)) return { status: 'running', message: '正在生成图片…' }\n const text = resultText(block)\n try {\n const parsed = JSON.parse(text) as { status?: unknown; message?: unknown }\n return {\n status: typeof parsed.status === 'string' ? parsed.status : block?.isError === true ? 'failed' : 'completed',\n message: typeof parsed.message === 'string' ? parsed.message : '',\n }\n } catch {\n return { status: block?.isError === true ? 'failed' : 'completed', message: text }\n }\n}\n\n/**\n * Narrow the persisted metadata's `images` into references we can request.\n *\n * All-or-nothing per record: a half-valid reference would make the route answer\n * 400 for a picture the row claims to have, so it is dropped instead.\n *\n * @param meta - the result's persisted presentation metadata, of unknown shape.\n * @returns the valid image records, in result order.\n */\nexport function imagesOf(meta: unknown): ToolCardImage[] {\n if (typeof meta !== 'object' || meta === null || Array.isArray(meta)) return []\n const images = (meta as { images?: unknown }).images\n if (!Array.isArray(images)) return []\n const out: ToolCardImage[] = []\n for (const value of images) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) continue\n const raw = value as Record<string, unknown>\n const { attachment_id: attachmentId, media_type: mediaType, bytes, width, height, name, file } = raw\n if (typeof attachmentId !== 'string' || attachmentId === '') continue\n if (typeof mediaType !== 'string' || !isImageMedia(mediaType)) continue\n if (typeof bytes !== 'number' || !Number.isSafeInteger(bytes) || bytes < 1) continue\n if (typeof width !== 'number' || !Number.isSafeInteger(width) || width < 1) continue\n if (typeof height !== 'number' || !Number.isSafeInteger(height) || height < 1) continue\n out.push({\n attachment_id: attachmentId,\n media_type: mediaType,\n bytes,\n width,\n height,\n ...typeof name === 'string' && name !== '' ? { name } : {},\n ...typeof file === 'string' && file !== '' ? { file } : {},\n })\n }\n return out\n}\n\n/**\n * Library file names carried by the result's own JSON text.\n *\n * A row recorded before `presentationMeta` included `file` still has the name in\n * its model-facing JSON, and those older rows keep their button this way.\n *\n * @param block - the settled result node.\n * @returns the file names in result order (empty strings where absent).\n */\nexport function filesFromResultText(block: ToolCardBlock | undefined): string[] {\n try {\n const parsed = JSON.parse(resultText(block)) as { images?: unknown }\n if (!Array.isArray(parsed.images)) return []\n return parsed.images.map(image => {\n const file = typeof image === 'object' && image !== null ? (image as { file?: unknown }).file : undefined\n return typeof file === 'string' ? file : ''\n })\n } catch {\n return []\n }\n}\n\n/**\n * The URL that serves one such record back.\n * @param image - one validated image record.\n * @returns the same-origin route URL with the complete reference.\n */\nexport function imageUrl(image: ToolCardImage): string {\n const query = new URLSearchParams({\n attachment_id: image.attachment_id,\n media_type: image.media_type,\n bytes: String(image.bytes),\n width: String(image.width),\n height: String(image.height),\n })\n return `${ATTACHMENT_API.image}?${query.toString()}`\n}\n\n/**\n * The conversation card for one SeeWork image-generating tool call.\n * @param props - the shell's owner payload for this call.\n * @returns the card row.\n */\nexport function ToolCard(props: ToolCardProps): JSX.Element {\n const block = props.block\n const { status, message } = resultInfo(block)\n const images = useMemo(() => imagesOf(block?.meta), [block?.meta])\n // Older rows carry the library file name only in their model-facing JSON.\n const fallbackFiles = useMemo(() => filesFromResultText(block), [block])\n const [broken, setBroken] = useState<ReadonlySet<string>>(new Set())\n const name = props.toolName ?? 'generate_image'\n\n return (\n <section className={css.root} data-state={status} data-tool={name}>\n <header className={css.header}>\n <strong className={css.title}>{cardTitle(name)}</strong>\n <span className={css.status}>{statusLabel(status)}</span>\n </header>\n {message === '' ? null : <p className={css.message}>{message}</p>}\n {images.length === 0 ? null : (\n <div className={css.images}>\n {images.map((image, index) => {\n const url = imageUrl(image)\n if (broken.has(image.attachment_id)) {\n return <span key={image.attachment_id} className={css.missing}>图片已不可读</span>\n }\n // The board references the library file, so the action needs one; a\n // picture whose entry was pruned has none to offer.\n const file = image.file ?? fallbackFiles[index] ?? ''\n const addable = file !== '' && canvasAddAvailable()\n return (\n <div key={image.attachment_id} className={css.thumb}>\n <a\n className={css.imageLink}\n href={url}\n target=\"_blank\"\n rel=\"noreferrer\"\n title={image.name ?? '打开原图'}\n >\n <img\n className={css.image}\n src={url}\n alt={image.name ?? '生成的图片'}\n width={image.width}\n height={image.height}\n loading=\"lazy\"\n data-dsh-seework-card-image=\"\"\n onError={() => {\n setBroken(previous => new Set(previous).add(image.attachment_id))\n }}\n />\n </a>\n {addable ? (\n <button\n type=\"button\"\n className={css.action}\n data-dsh-seework-add-to-canvas=\"\"\n title=\"把这张图放到画布上\"\n onClick={() => {\n // Model and prompt come from the library record; the card\n // only knows what it is showing.\n addToCanvas({ file, width: image.width, height: image.height })\n }}\n >\n 加到画布\n </button>\n ) : null}\n </div>\n )\n })}\n </div>\n )}\n </section>\n )\n}\n\n/**\n * Register the card for every image-bearing tool name.\n *\n * The registration is deferred through `slots.inject` because the slot exists\n * only while the shell's tool-presentation package is mounted; a shell that\n * never declares it simply never calls back, leaving the generic row. A refused\n * registration (an occupied key, say) is logged and must not fail the fiber.\n *\n * @param ctx - client root context (services: slots).\n * @returns disposer unregistering the views.\n */\nexport function registerToolCards(ctx: unknown): () => void {\n const context = ctx as { slots?: { inject?: unknown; register?: unknown } }\n const slots = context.slots\n if (slots === undefined || typeof slots.inject !== 'function' || typeof slots.register !== 'function') {\n return () => {}\n }\n const disposers: Array<() => void> = []\n const stop = (slots.inject as (name: string, callback: () => void) => () => void).call(slots, 'tool.call.toolview', () => {\n for (const key of TOOL_CARD_KEYS) {\n try {\n disposers.push((slots.register as (options: unknown, component: unknown) => () => void).call(slots, {\n name: 'tool.call.toolview',\n key,\n priority: TOOL_CARD_PRIORITY,\n }, ToolCard as unknown as (props: ToolCardProps) => JSX.Element))\n } catch (error) {\n console.warn(`[dsh-seework] tool card rejected for ${key}:`, error)\n }\n }\n })\n return () => {\n stop()\n for (const dispose of disposers.splice(0)) dispose()\n }\n}\n","/**\n * The material library as a right-sidebar tab body.\n *\n * The body renders the same panel the floating drawer renders, and the store is\n * the shared one, so a refresh triggered elsewhere (a new generation) is visible\n * here without this component doing anything.\n */\n\nimport { useEffect, useState } from 'react'\nimport type { LibraryStore } from './library-store.ts'\nimport { setComposerFace, type ComposerInput } from './composer-draft.ts'\nimport { LibraryPanel } from './LibraryPanel.tsx'\nimport { LibraryDetail } from './library-detail.tsx'\n\n/**\n * Props the sidebar seat supplies: the shared store, plus the session-scope facts\n * 「加入到对话框」 needs (the seat is session-scoped; the floating drawer is not).\n */\nexport interface LibraryTabBodyProps {\n /** Injected by the shell's tab seat; unused until the library needs tab actions. */\n hooks?: { tabInfo?: unknown }\n /** The session this tab is showing. */\n sessionId?: string\n /** The composer's attachment actions for that session. */\n inputActions?: ComposerInput\n}\n\n/**\n * Build the tab body for one store.\n *\n * The shell renders tab bodies without plugin-provided props, so the store is\n * bound here and the component receives only the seat's own props.\n * @param store - the shared library store.\n * @returns a component the tab seat can render.\n */\nexport function libraryTabBody(store: LibraryStore): (props: LibraryTabBodyProps) => JSX.Element {\n return function LibraryTabBody(props: LibraryTabBodyProps): JSX.Element {\n const [detailId, setDetailId] = useState<string | undefined>(undefined)\n // Opening the tab is the user asking \"what do I have?\": re-read, because the\n // store may have been loaded before the conversation generated anything.\n useEffect(() => { void store.refresh() }, [store])\n const sessionId = props.sessionId\n const inputActions = props.inputActions\n // Same contract as the canvas tab: the seat owns the session scope, so the\n // composer face is published for exactly as long as this tab is mounted.\n useEffect(() => {\n if (sessionId === undefined || inputActions === undefined) return\n return setComposerFace({ sessionId, input: inputActions })\n }, [inputActions, sessionId])\n return (\n <>\n <LibraryPanel\n store={store}\n tab\n hideClose\n onClose={() => { setDetailId(undefined) }}\n onOpen={id => { setDetailId(id) }}\n />\n {detailId === undefined\n ? null\n : <LibraryDetail store={store} entryId={detailId} onClose={() => { setDetailId(undefined) }} />}\n </>\n )\n }\n}\n\n/**\n * Factory form used by the tab registry: the registry closes over the store.\n * Kept separate from {@link LibraryTabBodyProps} so tests can render the body\n * without a shell.\n */\nexport type LibraryTabBodyFactory = ReturnType<typeof libraryTabBody>\n","/**\n * The canvas as a right-sidebar tab body.\n *\n * The board is the same component the full-screen overlay renders; only the\n * frame changes. The board list becomes a collapsible strip instead of a fixed\n * column, because a 320px tab cannot afford a 200px sidebar next to the board.\n */\n\nimport { useEffect, useState } from 'react'\nimport type { CanvasStore } from './canvas-store.ts'\nimport type { LibraryEntry } from '../protocol.ts'\nimport { CanvasBoard, CanvasBoardList } from './CanvasBoard.tsx'\nimport { setComposerFace, type ComposerInput } from './composer-draft.ts'\nimport { Button } from './controls.tsx'\nimport css from './canvas-shell.module.css'\n\n/** What the tab body needs from the plugin's composition root. */\nexport interface CanvasTabDeps {\n store: CanvasStore\n /** The shared library, for the picture picker. */\n library: { entries: LibraryEntry[] }\n /** Load the library (the picker needs it even if the drawer was never opened). */\n onNeedLibrary: () => void\n}\n\n/**\n * Props the sidebar seat supplies.\n *\n * The seat is session-scoped, which is what makes 「加入到对话框」 possible at\n * all: it hands over the session identity and the composer's input actions.\n */\nexport interface CanvasTabBodyProps {\n /** Injected by the shell's tab seat. */\n hooks?: { tabInfo?: unknown }\n /** The session this tab is showing. */\n sessionId?: string\n /** The composer's attachment actions for that session. */\n inputActions?: ComposerInput\n}\n\n/**\n * Build the canvas tab body for one store.\n * @param deps - the canvas store and the shared library face.\n * @returns a component the tab seat can render.\n */\nexport function canvasTabBody(deps: CanvasTabDeps): (props: CanvasTabBodyProps) => JSX.Element {\n return function CanvasTabBody(props: CanvasTabBodyProps): JSX.Element {\n const [listOpen, setListOpen] = useState(false)\n const store = deps.store\n const sessionId = props.sessionId\n const inputActions = props.inputActions\n // The session-scope seat is the only place that knows which conversation the\n // user is looking at, so the composer face lives exactly as long as this tab.\n useEffect(() => {\n if (sessionId === undefined || inputActions === undefined) return\n return setComposerFace({ sessionId, input: inputActions })\n }, [inputActions, sessionId])\n // Opening a board is normally the overlay's job; the tab is its own way in,\n // so it has to load the list and open something itself — otherwise the\n // board area just says \"正在读取画布…\" forever.\n useEffect(() => {\n if (store.getSnapshot().board !== undefined) {\n void store.refreshList()\n return\n }\n void (async () => {\n await store.refreshList()\n if (store.getSnapshot().board !== undefined) return\n const first = store.getSnapshot().boards[0]\n await store.open(first?.id ?? '')\n })()\n }, [store])\n return (\n <div className={css.tabOverlay} data-dsh-seework-canvas-tab=\"\">\n <header className={css.tabHeader}>\n <Button onClick={() => { setListOpen(open => !open) }} title=\"切换画布列表\">\n {listOpen ? '收起列表' : '画布列表'}\n </Button>\n <span className={css.headerHint}>拖标题栏移动、右下角调整大小、滚轮缩放</span>\n </header>\n {listOpen\n ? (\n <div className={css.tabList}>\n <CanvasBoardList store={store} />\n </div>\n )\n : null}\n <CanvasBoard\n store={store}\n library={deps.library}\n onNeedLibrary={deps.onNeedLibrary}\n />\n </div>\n )\n }\n}\n\n/** The factory's component type, for tests. */\nexport type CanvasTabBodyFactory = ReturnType<typeof canvasTabBody>\n","/**\n * SeeWork's right-sidebar tabs: the material library and the canvas.\n *\n * The shell owns the right column, so both surfaces are contributed as tab\n * types rather than mounted on `document.body`:\n *\n * 1. `ctx.sidebarRightTabs.register({ id, kind, title })` declares the type;\n * 2. the body registers into the keyed `sidebar.right.pane.tab` seat under the\n * same id, and the chip title into `sidebar.right.pane.tab.title`.\n *\n * Opening is `ctx.sidebarRight.openTab(kind)`: the controller expands the column\n * if it was collapsed, which is what \"auto-open\" means for the shell — the\n * plugin never touches layout itself.\n *\n * Degradation: every call is guarded, and the caller keeps the floating dock. A\n * shell without the right sidebar must still be able to show these surfaces.\n */\n\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'\nimport type { LibraryStore } from './library-store.ts'\nimport type { CanvasStore } from './canvas-store.ts'\nimport type { LibraryEntry } from '../protocol.ts'\nimport { libraryTabBody } from './LibraryTab.tsx'\nimport { canvasTabBody } from './CanvasTab.tsx'\n\n/** Tab type ids, also the keys the bodies and titles register under. */\nexport const LIBRARY_TAB_ID = 'dsh-seework/library'\nexport const CANVAS_TAB_ID = 'dsh-seework/canvas'\n\n/** Tab kinds: what `openTab` names. */\nexport const LIBRARY_TAB_KIND = 'seework-library'\nexport const CANVAS_TAB_KIND = 'seework-canvas'\n\n/** The two surfaces as tab types. */\nexport interface SeeWorkTabTarget {\n id: string\n kind: string\n title: string\n}\n\n/** Both tab types in strip order. */\nexport const TAB_TARGETS: readonly SeeWorkTabTarget[] = [\n { id: LIBRARY_TAB_ID, kind: LIBRARY_TAB_KIND, title: '素材库' },\n { id: CANVAS_TAB_ID, kind: CANVAS_TAB_KIND, title: '画布' },\n]\n\n/** The stores the tab bodies render from (the same ones the fallback panels use). */\nexport interface TabStores {\n library: LibraryStore\n canvas: CanvasStore\n /** Library entries for the canvas picture picker. */\n entries: () => LibraryEntry[]\n /** Load the library when the picker asks for it. */\n onNeedLibrary: () => void\n}\n\n/**\n * The slice of `ctx` these registrations use.\n *\n * Services come through `ctx.get(name)`, never `ctx.<name>`: the client\n * runner's guard refuses an undeclared property read (and reports it as a\n * plugin failure), while `get` is the sanctioned probe that answers `undefined`\n * for a service this page does not have. Declaring the sidebar services in\n * `inject` is not an option either — a package whose provider is ordered\n * differently parks the whole plugin, which takes the settings, library and\n * canvas surfaces down with the tabs.\n */\ninterface SidebarClientFace {\n get?: (name: string) => unknown\n}\n\n/** The tab registry as this module uses it. */\ninterface TabRegistry {\n register: (definition: unknown) => () => void\n}\n\n/** The column controller as this module uses it. */\ninterface SidebarController {\n openTab: (kind: string, options?: { revealIfOpened?: boolean }) => void\n active: () => { id: string; kind: string } | undefined\n close: (tabId: string) => void\n}\n\n/** The slot registry as this module uses it. */\ninterface SlotRegistry {\n inject: (name: string, callback: () => void) => () => void\n register: (options: unknown, component: unknown) => () => void\n}\n\n/** Look up one optional service without tripping the client guard. */\nfunction service<T>(ctx: ClientContext, name: string): T | undefined {\n try {\n const value = (ctx as unknown as SidebarClientFace).get?.(name)\n return value === null || value === undefined ? undefined : value as T\n } catch (error) {\n console.warn(`[dsh-seework] service \"${name}\" is unavailable:`, error)\n return undefined\n }\n}\n\n/**\n * Is the right column part of this page at all?\n * @param ctx - client root context.\n * @returns true when both sidebar services are present.\n */\nexport function sidebarAvailable(ctx: ClientContext): boolean {\n return service<TabRegistry>(ctx, 'sidebarRightTabs') !== undefined\n && service<SidebarController>(ctx, 'sidebarRight') !== undefined\n}\n\n/**\n * Register both tab types and their bodies.\n * @param ctx - client root context carrying `slots`, `sidebarRightTabs` and `sidebarRight`.\n * @param stores - the shared stores the bodies render from.\n * @returns the disposer plus whether the tabs actually landed (a shell without\n * the column keeps its floating launchers).\n */\nexport function registerSidebarTabs(ctx: ClientContext, stores: TabStores): {\n dispose: () => void\n tabsAvailable: boolean\n} {\n const tabs = service<TabRegistry>(ctx, 'sidebarRightTabs')\n const slots = service<SlotRegistry>(ctx, 'slots')\n const disposers: Array<() => void> = []\n let registered = 0\n const guard = (work: () => (() => void) | undefined): void => {\n try {\n const dispose = work()\n if (dispose !== undefined) disposers.push(dispose)\n } catch (error) {\n console.warn('[dsh-seework] right sidebar rejected a tab:', error)\n }\n }\n\n for (const target of TAB_TARGETS) {\n guard(() => {\n const dispose = tabs?.register({\n id: target.id,\n kind: target.kind,\n title: () => target.title,\n })\n if (dispose !== undefined) registered += 1\n return dispose\n })\n const body = target.kind === LIBRARY_TAB_KIND\n ? libraryTabBody(stores.library)\n : canvasTabBody({\n store: stores.canvas,\n get library() { return { entries: stores.entries() } },\n onNeedLibrary: stores.onNeedLibrary,\n })\n // The deferred callback is guarded on its own: `slots.inject` may run it\n // immediately (a shell that already declared the slot), and a registry that\n // refuses the key throws from inside it rather than from `inject`.\n guard(() => slots?.inject('sidebar.right.pane.tab', () => {\n try {\n return slots.register({\n name: 'sidebar.right.pane.tab',\n key: target.id,\n }, body)\n } catch (error) {\n console.warn('[dsh-seework] right sidebar rejected the tab body:', error)\n return undefined\n }\n }))\n guard(() => slots?.inject('sidebar.right.pane.tab.title', () => {\n try {\n return slots.register({\n name: 'sidebar.right.pane.tab.title',\n key: target.id,\n }, () => target.title)\n } catch (error) {\n console.warn('[dsh-seework] right sidebar rejected the tab title:', error)\n return undefined\n }\n }))\n }\n\n return {\n dispose: () => {\n for (const dispose of disposers.reverse()) dispose()\n },\n tabsAvailable: registered === TAB_TARGETS.length,\n }\n}\n\n/**\n * Show one tab, expanding the column when it was collapsed.\n * @param ctx - client root context carrying `sidebarRight`.\n * @param kind - the tab kind to reveal.\n * @returns true when the request was accepted.\n */\nexport function openSeeWorkTab(ctx: ClientContext, kind: string): boolean {\n const controller = service<SidebarController>(ctx, 'sidebarRight')\n if (controller === undefined) return false\n try {\n controller.openTab(kind, { revealIfOpened: true })\n return true\n } catch (error) {\n console.warn('[dsh-seework] could not open the sidebar tab:', error)\n return false\n }\n}\n\n/**\n * Is that tab the one the column is showing right now?\n * @param ctx - client root context carrying `sidebarRight`.\n * @param kind - the tab kind to test.\n * @returns true when it is the active tab.\n */\nexport function isSeeWorkTabActive(ctx: ClientContext, kind: string): boolean {\n const controller = service<SidebarController>(ctx, 'sidebarRight')\n try {\n return controller?.active()?.kind === kind\n } catch {\n return false\n }\n}\n\n/**\n * Put one tab away.\n * @param ctx - client root context carrying `sidebarRight`.\n * @param kind - the tab kind to close, when it is the active one.\n * @returns true when a tab was closed.\n */\nexport function closeSeeWorkTab(ctx: ClientContext, kind: string): boolean {\n const controller = service<SidebarController>(ctx, 'sidebarRight')\n try {\n const active = controller?.active()\n if (active === undefined || active.kind !== kind) return false\n controller?.close(active.id)\n return true\n } catch (error) {\n console.warn('[dsh-seework] could not close the sidebar tab:', error)\n return false\n }\n}\n","/**\n * Browser-half entry for the dsh-seework plugin — runs inside the dsh web GUI.\n *\n * Responsibilities:\n * 1. bind the plugin's own settings scope (its bridge routes serve the\n * namespace the shell's settings transport does not know about);\n * 2. register the settings card into the shell's plugin-configuration slot\n * when that slot exists, and fall back to a DOM-mounted settings panel\n * when it does not — a plugin must never be un-configurable;\n * 3. declare the not-yet-typed slot.\n *\n * Failure policy: DOM and slot problems are logged, never thrown. The web shell\n * fails the whole boot when a plugin apply throws, and an external plugin must\n * not take the GUI down.\n */\n\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { SeeWorkApi } from './api.ts'\nimport { SeeWorkSettingsCard, type SettingsCardFace } from './SettingsCard.tsx'\nimport { mountCanvasSurfaces, stageSizeFor } from './canvas-panel.tsx'\nimport { CanvasStore } from './canvas-store.ts'\nimport { watchGenerations } from './generation-watch.ts'\nimport { HeaderLaunchers, onHeaderPresence, setLauncherControls, type LauncherControls } from './header-launchers.tsx'\nimport { LibraryStore } from './library-store.ts'\nimport { mountLibraryPanel } from './library-panel.tsx'\nimport { bindSeeWorkScope, type SeeWorkScope } from './settings-scope.ts'\nimport { mountSettingsPanel, requestSurface } from './settings-panel.tsx'\nimport { installChatImageZoom } from './chat-images.tsx'\nimport { createCanvasAddFace, libraryFileFromUrl, setCanvasAddFace } from './canvas-add.ts'\nimport { setComposerProbe, type ComposerDrafts } from './composer-draft.ts'\nimport { registerToolCards } from './tool-card.tsx'\nimport {\n CANVAS_TAB_KIND,\n LIBRARY_TAB_KIND,\n closeSeeWorkTab,\n isSeeWorkTabActive,\n openSeeWorkTab,\n registerSidebarTabs,\n sidebarAvailable,\n type TabStores,\n} from './sidebar-tabs.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface SlotMap {\n /**\n * One page inside the Plugins settings section, declared at runtime by the\n * feature that owns that section. Spelled here so this package can register\n * without depending on the sibling UI package; a shell that does not declare\n * it makes the registration throw, which the client half turns into the\n * DOM-mounted fallback instead of a boot failure.\n */\n 'settings.plugin.item': { kind: 'keyed'; scope: 'root'; owner: SettingsPluginItemOwnerProps }\n }\n}\n\n/** Owner share of a plugin card (the section supplies nothing). */\nexport interface SettingsPluginItemOwnerProps {\n /** Marker field: card owner props are intentionally empty. */\n children?: never\n}\n\n/** The slot face registered into `settings.plugin.item`. */\ninterface PluginCardProps extends SettingsPluginItemOwnerProps, SettingsCardFace {}\n\n/**\n * The plugin's client half declares only `slots` as a required service.\n *\n * The right-sidebar services are optional for this plugin: declaring them would\n * park the WHOLE plugin (settings, library and canvas included) whenever the\n * boot graph orders the column's own plugin differently, which is a worse\n * failure than not having tabs. They are probed through `ctx.get` instead.\n */\nexport const inject = ['slots']\n\n/** Settings namespace this plugin owns. */\nconst NS = 'dsh-seework'\n\n/**\n * Position of the SeeWork page among the settings dialog's nav entries.\n * The shipped sections use 10 (models), 15 (plugins) and 20 (agent presets), so\n * a larger number lands after them — the end of the list, where a third-party\n * plugin belongs.\n */\nconst SETTINGS_SECTION_ORDER = 40\n\n/**\n * Mount the plugin's browser surfaces.\n * @param ctx - client root context (services: slots).\n */\nexport function apply(ctx: ClientContext): void {\n const scope: SeeWorkScope = bindSeeWorkScope()\n const api = new SeeWorkApi()\n\n // The floating panel mounts first and is removed once the shell takes a\n // settings surface. Ordering it this way (rather than predicting the shell's\n // answer up front) means there is never a window in which SeeWork has no\n // reachable settings UI at all — a slot's answer only arrives on a later tick.\n const disposeFloatingSettings = mountFallback(scope, api)\n let floatingSettingsRetired = false\n const retireFloatingSettings = (): void => {\n if (floatingSettingsRetired) return\n floatingSettingsRetired = true\n disposeFloatingSettings?.()\n }\n // One settings page, one place. The nav page is the home; the tab inside the\n // Plugins section is only the fallback for a shell that has no nav slot at\n // all, because offering the same card twice is just clutter.\n //\n // The nav slot may already be declared when this runs (its callback fires\n // immediately) or be declared later (its callback fires then), so both orders\n // have to end up at the same place: if the section landed, do not keep a tab.\n let sectionLanded = false\n let disposeSlot: (() => void) | undefined\n const disposeSection = registerSettingsSection(ctx, scope, api, () => {\n sectionLanded = true\n retireFloatingSettings()\n disposeSlot?.()\n disposeSlot = undefined\n })\n if (!sectionLanded) disposeSlot = registerSettingsSlot(ctx, scope, api, retireFloatingSettings, () => sectionLanded)\n\n // The canvas store is created here rather than inside the panel mount: the\n // right-sidebar tab and the floating fallback must render the SAME board, and\n // two stores would mean two boards fighting over one document.\n const canvas = new CanvasStore(api)\n\n // The library store is shared: the drawer and the library tab show it, the\n // canvas picks pictures from it, and two stores would mean one of them showing\n // a stale list.\n const library = new LibraryStore(api)\n // The floating launchers mount first: the right column's services only exist\n // once its own plugin has applied, which the boot graph may order after this\n // one. When the tabs do land, the launchers retire themselves.\n const disposeLibrary = mountLibrary(api, library)\n const disposeCanvas = mountCanvas(api, library, canvas)\n // A floating launcher is hidden — never removed — while the conversation\n // header shows its own buttons, and comes back the moment those go away (the\n // home screen has no session, so it has no header). Removing them instead\n // would leave a shell with no way in at all.\n const stopPresence = onHeaderPresence(present => { setFloatingLaunchersHidden(present) })\n const attachTabs = registerSidebarTabsWhenReady(ctx, {\n library,\n canvas,\n entries: () => library.getSnapshot().entries,\n onNeedLibrary: () => { void library.refresh() },\n })\n // Top-right entry points. The floating dock stays even when the header slot\n // accepts them: an external plugin must remain reachable on a shell that does\n // not expose the header slot or the right column at all.\n const launchers = registerHeaderLaunchers(ctx)\n // The conversation card for this plugin's own tool results. The result text is\n // JSON on purpose (text-only models must keep working), so without this view a\n // finished generation shows up as a wall of JSON instead of its pictures.\n const disposeToolCards = registerToolCards(ctx)\n // Generated pictures are big; the transcript shows them capped and one click\n // opens the original in place. Independent of the view above, because the\n // assistant's own markdown reply renders images in the shell's markup.\n const disposeChatImageZoom = installChatImageZoom(document, src => {\n // A markdown reply image points at the library route, so its file name is in\n // the URL and the library record supplies the rest.\n const file = libraryFileFromUrl(src)\n return file === undefined ? undefined : { file }\n })\n // 「加到画布」: the user points at one picture, and only then does the plugin\n // place a card and bring the canvas up. Automatic placement stayed removed.\n const restoreCanvasAdd = setCanvasAddFace(createCanvasAddFace({\n library,\n canvas,\n reveal: () => {\n if (openSeeWorkTab(ctx, CANVAS_TAB_KIND)) return\n requestSurface('canvas')\n },\n stageSize: () => stageSizeFor(canvas),\n }))\n // 「加入到对话框」 needs the shell's conversation service; the session identity\n // comes from whichever of our tab bodies is mounted (they are session-scoped).\n // A probe, not a captured value: plugins attach in boot order, so resolving at\n // call time is the only thing that cannot come up empty by accident.\n const restoreComposer = setComposerProbe(() => probeService<ComposerDrafts>(ctx, 'conversation'))\n // Point the header buttons at the right-sidebar tabs; without the column they\n // keep working through the floating surfaces.\n const restoreControls = setLauncherControls(launchers.controls)\n // The host pushes nothing, so the page asks what the newest entry is; a change\n // means a generation finished (an agent one included) and the shared library\n // store is refreshed. Deliberately nothing else: the canvas is NOT opened and\n // nothing is placed on it — a background generation must not rearrange the\n // screen, and adding a picture to a board is the canvas's own 「从素材库选图」.\n const stopWatching = watchGenerations({ api, library })\n\n ctx.effect(() => () => {\n stopWatching()\n stopPresence()\n restoreControls()\n launchers.dispose()\n disposeToolCards()\n disposeChatImageZoom()\n restoreCanvasAdd()\n restoreComposer()\n attachTabs.dispose()\n disposeSection?.()\n disposeSlot?.()\n disposeFloatingSettings?.()\n disposeLibrary?.()\n disposeCanvas?.()\n }, 'dsh-seework: settings, library and canvas surfaces')\n}\n\n/**\n * Probe one optional service without tripping the client guard.\n *\n * Services come through `ctx.get(name)`, never `ctx.<name>` (the guard refuses\n * an undeclared property read), and never through `inject` (a package ordered\n * differently would park the whole plugin — settings, library and canvas with it).\n *\n * @param ctx - client root context.\n * @param name - the service name.\n * @returns the service, or undefined when this page has none.\n */\nfunction probeService<T>(ctx: ClientContext, name: string): T | undefined {\n try {\n const value = (ctx as unknown as { get?: (name: string) => unknown }).get?.(name)\n return value === null || value === undefined ? undefined : value as T\n } catch (error) {\n console.warn(`[dsh-seework] service \"${name}\" is unavailable:`, error)\n return undefined\n }\n}\n\n/**\n * Show or hide the floating library/canvas launchers.\n *\n * Hidden rather than removed: they are the home screen's only way into the two\n * surfaces (the conversation header, and with it the header buttons, exists\n * only inside a session), so they have to be able to come back. The settings\n * button is left alone — its slot is the one a shell is most likely to lack.\n *\n * @param hidden - true while the conversation header's own buttons are on screen.\n */\nfunction setFloatingLaunchersHidden(hidden: boolean): void {\n if (typeof document === 'undefined') return\n for (const attr of ['data-dsh-seework-library-launcher', 'data-dsh-seework-canvas-launcher']) {\n document.querySelectorAll<HTMLElement>(`[${attr}]`).forEach(button => {\n button.style.display = hidden ? 'none' : ''\n })\n }\n}\n\n/**\n * Register the right-sidebar tabs as soon as the column's services exist.\n *\n * The services are optional (see the `inject` note), so this probes for them on\n * an interval — cheap, bounded, and it never throws when they never appear. The\n * tabs only matter once the user opens something, so a short delay costs\n * nothing, while parking the whole plugin on the dependency would take the\n * settings, library and canvas surfaces down with it.\n *\n * @param ctx - client root context.\n * @param stores - the shared stores the tab bodies render from.\n * @param onAvailable - called once the tabs actually registered.\n * @returns disposer cancelling the watch and unregistering what landed.\n */\nfunction registerSidebarTabsWhenReady(\n ctx: ClientContext,\n stores: TabStores,\n): { dispose: () => void } {\n let tabs: { dispose: () => void; tabsAvailable: boolean } | undefined\n let cancelled = false\n let attempts = 0\n const MAX_ATTEMPTS = 40\n const attempt = (): void => {\n if (cancelled || tabs !== undefined) return\n attempts += 1\n if (sidebarAvailable(ctx)) {\n const registered = registerSidebarTabs(ctx, stores)\n if (registered.tabsAvailable) {\n tabs = registered\n return\n }\n // Services exist but the registry refused: retrying will not help.\n registered.dispose()\n cancelled = true\n return\n }\n if (attempts < MAX_ATTEMPTS) setTimeout(attempt, 250)\n }\n // The column's plugin may already have applied (a reload), in which case the\n // very next tick is enough.\n setTimeout(attempt, 0)\n return {\n dispose: () => {\n cancelled = true\n tabs?.dispose()\n tabs = undefined\n },\n }\n}\n\n/**\n * Contribute the SeeWork entry buttons to the conversation header's utilities,\n * and teach them to work the right-sidebar tabs.\n *\n * Same degradation contract as the settings slot: a shell without the utilities\n * slot simply never calls the injection callback, leaving the floating dock as\n * the way in. A shell with the header but without the right column keeps the\n * buttons working through the floating surfaces.\n *\n * @param ctx - client root context (services: slots).\n * @returns the disposer and the launcher controller face.\n */\nfunction registerHeaderLaunchers(ctx: ClientContext): {\n dispose: () => void\n controls: LauncherControls\n} {\n const controls: LauncherControls = {\n isOpen: surface => isSeeWorkTabActive(ctx, surface === 'library' ? LIBRARY_TAB_KIND : CANVAS_TAB_KIND),\n open: surface => {\n const kind = surface === 'library' ? LIBRARY_TAB_KIND : CANVAS_TAB_KIND\n if (openSeeWorkTab(ctx, kind)) return true\n // No right column on this shell: fall back to the floating surface.\n requestSurface(surface)\n return false\n },\n close: surface => {\n const kind = surface === 'library' ? LIBRARY_TAB_KIND : CANVAS_TAB_KIND\n if (closeSeeWorkTab(ctx, kind)) return true\n requestSurface(surface === 'library' ? 'canvas' : 'library')\n return false\n },\n }\n\n try {\n const slots = (ctx as unknown as { slots?: { inject?: unknown; register?: unknown } }).slots\n if (slots === undefined || typeof slots.inject !== 'function' || typeof slots.register !== 'function') {\n return { dispose: () => {}, controls }\n }\n let dispose: (() => void) | undefined\n const injected = slots.inject as (name: string, callback: () => void) => () => void\n const stop = injected.call(slots, 'conversation.session.header.utilities', () => {\n try {\n dispose = (slots.register as (options: unknown, component: unknown) => () => void).call(slots, {\n name: 'conversation.session.header.utilities',\n id: NS,\n order: -20,\n }, HeaderLaunchers as unknown as () => JSX.Element)\n } catch (error) {\n console.warn('[dsh-seework] header utilities slot rejected the launchers:', error)\n }\n })\n return {\n dispose: () => {\n dispose?.()\n stop()\n },\n controls,\n }\n } catch (error) {\n console.warn('[dsh-seework] header utilities slot unavailable:', error)\n return { dispose: () => {}, controls }\n }\n}\n\n/**\n * Contribute the SeeWork page to the settings dialog's own navigation.\n *\n * `settings.plugin.item` is a tab INSIDE the Plugins section, which is easy to\n * miss; `settings.section` is a first-class page in the nav list beside\n * 通用设置 / 模型 / 插件 / Agent 预设, which is where a user looks for a plugin\n * they just installed. The shipped sections order themselves 10 / 15 / 20, so\n * this one sits after them.\n *\n * While this section is accepted, {@link registerSettingsSlot} is not kept: the\n * same card in two places is clutter. A shell without the nav slot still gets\n * the Plugins tab, and one without either keeps the floating panel.\n *\n * @param ctx - client root context (services: slots).\n * @param scope - the bound settings scope the card reads and writes.\n * @param api - the plugin route client.\n * @param onAccepted - called once the nav actually took the section.\n * @returns the disposer when the slot exists, undefined when it does not.\n */\nfunction registerSettingsSection(\n ctx: ClientContext,\n scope: SeeWorkScope,\n api: SeeWorkApi,\n onAccepted: () => void,\n): (() => void) | undefined {\n try {\n const slots = (ctx as unknown as { slots?: { inject?: unknown; register?: unknown } }).slots\n if (slots === undefined || typeof slots.inject !== 'function' || typeof slots.register !== 'function') return undefined\n const face: SettingsCardFace = { scope, api }\n let dispose: (() => void) | undefined\n const injected = slots.inject as (name: string, callback: () => void) => () => void\n const stop = injected.call(slots, 'settings.section', () => {\n try {\n dispose = (slots.register as (options: unknown, component: unknown) => () => void).call(slots, {\n name: 'settings.section',\n id: NS,\n order: SETTINGS_SECTION_ORDER,\n label: () => 'SeeWork',\n inject: () => face,\n }, SeeWorkSettingsCard as unknown as (props: { close: () => void } & SettingsCardFace) => JSX.Element)\n onAccepted()\n } catch (error) {\n console.warn('[dsh-seework] settings navigation rejected the section:', error)\n }\n })\n return () => {\n dispose?.()\n stop()\n }\n } catch (error) {\n console.warn('[dsh-seework] settings navigation slot unavailable:', error)\n return undefined\n }\n}\n\n/**\n * Contribute the settings card to the shell's plugin-configuration slot.\n *\n * Both the registration AND the deferred `inject` callback are guarded. A shell\n * that never declares the slot simply never runs the callback — that is the\n * `slots.inject` contract, not an error — so the fallback panel stays until\n * `onAccepted` says a card actually landed. A callback that throws (the\n * registry refuses a slot it considers occupied, say) is logged and leaves the\n * fallback in place; it must not fail the plugin's fiber, because the shell\n * reports a failed fiber to the user as a boot problem.\n *\n * @param onAccepted - called once the slot has actually accepted the card, so\n * the caller can retire its fallback surface.\n * @returns the disposer when the slot exists, undefined when it does not.\n */\nfunction registerSettingsSlot(\n ctx: ClientContext,\n scope: SeeWorkScope,\n api: SeeWorkApi,\n onAccepted: () => void,\n /**\n * Asked when this slot's deferred callback finally runs: true means another\n * settings surface already won, so this registration must not happen at all.\n * Without it, a nav page that lands first would leave this tab registered by a\n * callback that arrives afterwards — the duplicate this fallback exists to\n * avoid.\n */\n skipWhen?: () => boolean,\n): (() => void) | undefined {\n try {\n const slots = (ctx as unknown as { slots?: { inject?: unknown; register?: unknown } }).slots\n if (slots === undefined || typeof slots.inject !== 'function' || typeof slots.register !== 'function') return undefined\n const face: SettingsCardFace = { scope, api }\n let dispose: (() => void) | undefined\n const injected = slots.inject as (name: string, callback: () => void) => () => void\n const stop = injected.call(slots, 'settings.plugin.item', () => {\n if (skipWhen?.() === true) return\n try {\n dispose = (slots.register as (options: unknown, component: unknown) => () => void).call(slots, {\n name: 'settings.plugin.item',\n key: NS,\n inject: () => face,\n }, SeeWorkSettingsCard as unknown as (props: PluginCardProps) => JSX.Element)\n onAccepted()\n } catch (error) {\n console.warn('[dsh-seework] settings slot rejected the card:', error)\n }\n })\n return () => {\n dispose?.()\n stop()\n }\n } catch (error) {\n console.warn('[dsh-seework] settings slot unavailable:', error)\n return undefined\n }\n}\n\n/** DOM-mounted settings launcher (the no-slot path). */\nfunction mountFallback(scope: SeeWorkScope, api: SeeWorkApi): (() => void) | undefined {\n try {\n return mountSettingsPanel(scope, api)\n } catch (error) {\n console.warn('[dsh-seework] settings panel mount failed:', error)\n return undefined\n }\n}\n\n/**\n * Mount the material-library launcher and drawer. A DOM mount failure degrades\n * the library only — the plugin never takes the GUI down with it.\n */\nfunction mountLibrary(api: SeeWorkApi, store: LibraryStore): (() => void) | undefined {\n try {\n return mountLibraryPanel(api, store)\n } catch (error) {\n console.warn('[dsh-seework] library panel mount failed:', error)\n return undefined\n }\n}\n\n/**\n * Mount the canvas launcher and overlay. Sharing the library store means the\n * picture picker shows what the drawer shows.\n */\nfunction mountCanvas(api: SeeWorkApi, store: LibraryStore, canvas: CanvasStore): (() => void) | undefined {\n try {\n return mountCanvasSurfaces(api, store, canvas)\n } catch (error) {\n console.warn('[dsh-seework] canvas mount failed:', error)\n return undefined\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["css","apply","css","ops: Parameters<SeeWorkScope['mutateOps']>[0]","parts: string[]","models: ModelConfig[]","css","seen: string[]","ASSUMED_STAGE: { readonly width: number; readonly height: number }","ANNOTATION_COLORS: readonly string[]","ANNOTATION_WIDTHS: readonly number[]","minX","minY","hits: number[]","css","CROP_ASPECTS: readonly CropAspectOption[]","CROP_HANDLES: readonly CropHandle[]","HANDLE_AXES: Record<CropHandle, { x: -1 | 0 | 1; y: -1 | 0 | 1 }>","HANDLE_CURSORS: Record<CropHandle, string>","css","conversationProbe: (() => ComposerDrafts | undefined) | undefined","face: ComposerFace | undefined","face","service","placement: FloatingBarPlacement","css","cryptoObj: Crypto | undefined","next: CanvasDocument","css","removeHint","css","activeSurface: SeeWorkSurface | undefined","launcherCss","css","root: Root","LAUNCHER_TARGETS: readonly LauncherTarget[]","controls: LauncherControls | undefined","css","css","launcherCss","root: Root","handle","newestId: string | undefined","api: SeeWorkApi","value: LibraryListResult","EMPTY_FILTER: LibraryFilter","face: CanvasAddFace | undefined","sleep","image","css","state: LibraryState","css","launcherCss","root: Root","fetchFn: typeof fetch","bits: Record<string, boolean>","css","lightbox: { host: HTMLElement; root: Root } | undefined","out: ToolCardImage[]","css","disposers: Array<() => void>","css","TAB_TARGETS: readonly SeeWorkTabTarget[]","disposers: Array<() => void>","scope: SeeWorkScope","disposeSlot: (() => void) | undefined","tabs: { dispose: () => void; tabsAvailable: boolean } | undefined","controls: LauncherControls","dispose: (() => void) | undefined","face: SettingsCardFace","face"],"sources":["../src/protocol.ts","../src/client/api.ts","../src/client/controls.tsx","../src/client/VersionRow.tsx","../src/client/SettingsCard.tsx","../src/canvas-limits.ts","../src/canvas-viewport.ts","../src/canvas-placement.ts","../src/client/annotations.ts","../src/client/burn-image.ts","../src/client/AnnotationEditor.tsx","../src/client/crop-rect.ts","../src/client/CropOverlay.tsx","../src/client/composer-draft.ts","../src/client/floating-bar.ts","../src/client/NodeFloatingBar.tsx","../src/client/snapshot-store.ts","../src/client/canvas-store.ts","../src/client/CanvasBoard.tsx","../src/client/surfaces.ts","../src/client/settings-panel.tsx","../src/client/header-launchers.tsx","../src/client/canvas-panel.tsx","../src/client/generation-watch.ts","../src/client/library-store.ts","../src/client/canvas-add.ts","../src/client/library-detail.tsx","../src/client/LibraryPanel.tsx","../src/client/library-panel.tsx","../src/client/settings-scope.ts","../src/client/chat-images.tsx","../src/client/tool-card.tsx","../src/client/LibraryTab.tsx","../src/client/CanvasTab.tsx","../src/client/sidebar-tabs.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Wire contract shared by the host and browser halves of dsh-seework: the\n * settings namespace, the route paths, the SeeAI Hub gateway request/response\n * shapes, and the local material-library record shape.\n *\n * Pure types + constants — safe for the client bundle to inline.\n */\n\n/** Settings namespace this plugin owns (host settings seam + bridge). */\nexport const SEEWORK_SETTINGS_NAMESPACE = 'dsh-seework'\n\n/**\n * Aspect ratios the gateway serves uniformly (#658/D-16).\n *\n * One list for every model, in the order a user should see them — the catalog's\n * own arrays are lexicographically sorted sets, so they are not a usable order\n * for a picker.\n */\nexport const UNIFIED_ASPECT_RATIOS = ['1:1', '4:3', '3:4', '16:9', '9:16', '3:2', '2:3', '21:9'] as const\n\n/** Factory default for the ratio setting (#658/D-13/D-16). */\nexport const DEFAULT_ASPECT_RATIO = '3:4'\n\n/** Factory default for the output-format setting (#658/D-13). */\nexport const DEFAULT_OUTPUT_FORMAT = 'png'\n\n/** Formats to offer when no saved model declares any. */\nexport const OUTPUT_FORMAT_FALLBACKS = ['png', 'jpeg', 'webp'] as const\n\n/**\n * Whether a list contains a value, ignoring case (#658).\n *\n * The one membership rule both halves need: the request layer asks whether the\n * saved model can take the configured output format, and the settings card asks\n * whether the stored value is already among the offered options.\n */\nexport function includesIgnoringCase(list: readonly string[], wanted: string): boolean {\n return list.some(value => value.toLowerCase() === wanted.toLowerCase())\n}\n\n/**\n * Default SeeAI Hub Gateway base URL — the OpenAI-compatible root, i.e. the\n * part that `/images/generations` is appended to. Local development gateway is\n * the default so a fresh install talks to the working stack; point it at a\n * deployment domain in the settings card.\n */\nexport const DEFAULT_API_URL = 'http://127.0.0.1:8080/v1'\n\n/**\n * Default SeeAI Hub **service** base URL, used only for the model catalog.\n *\n * `docs/api/README.md` is explicit that the two surfaces are separate and that\n * neither answers the other's paths: generation is the Gateway (`/v1/*`), while\n * `GET /api/v1/catalog/models` is the service. Deriving one from the other\n * works only behind a reverse proxy that mounts both under one host, which is\n * why the catalog address is its own setting.\n */\nexport const DEFAULT_SERVICE_URL = 'http://127.0.0.1:8081'\n\n/**\n * Most images this plugin asks for in one request.\n *\n * **This is the plugin's own ceiling, not a contract value.**\n * `docs/api/images.md` documents `n` as a positive integer whose ceiling is the\n * *model's* own maximum — published since catalog v2 as the `n` descriptor's\n * `max` — so the per-model limit comes from the catalog, while this constant is\n * the plugin's own guard against a runaway `n` turning into a runaway bill.\n *\n * It is stated once because several places have to agree and each used to spell\n * `10` itself. Every consumer now goes through the two compositions below rather\n * than clamping on its own: the normalizer (`settings.ts`), the outgoing body\n * (`engine.ts`), the tool argument (`agent-tools.ts`) and the promise the\n * announcement makes (`model-summary.ts`), which must never exceed what is\n * actually sent.\n *\n * The two compositions of it live here too ({@link imageCountCeiling} and\n * {@link effectiveImageCount}), because spelling `min(10, max(1, …))` at each\n * call site is how the task record and the outgoing body drifted apart (#668).\n */\nexport const MAX_IMAGES_PER_REQUEST = 10\n\n/**\n * The most images one request may ask for, given a model's declared ceiling.\n *\n * A **declared** ceiling below one is read as one image — {@link ModelConfig.maxImages}\n * is \"1 when unknown\" — while a declared ceiling above the wire ceiling is cut\n * back to it. Omitting `modelCap` means \"no model is known at this call site\"\n * (the send-time guard), so only the wire ceiling applies.\n *\n * @param modelCap - the model's declared ceiling, when the catalog declared one.\n * @returns the ceiling this model's requests are held to.\n */\nexport function imageCountCeiling(modelCap?: number): number {\n const declared = typeof modelCap === 'number' && Number.isFinite(modelCap)\n ? Math.trunc(modelCap)\n : MAX_IMAGES_PER_REQUEST\n return Math.min(MAX_IMAGES_PER_REQUEST, Math.max(1, declared))\n}\n\n/**\n * The image count a request actually carries: what was asked for, clamped into\n * the model's own ceiling.\n *\n * Every caller that records or sends a count uses this, so the task, the library\n * entry and the outgoing body cannot disagree.\n *\n * @param requested - the caller's `n`.\n * @param modelCap - the model's declared ceiling, when the catalog declared one.\n * @returns the count that goes on the wire.\n */\nexport function effectiveImageCount(requested: number, modelCap?: number): number {\n const asked = Number.isFinite(requested) ? Math.trunc(requested) : 1\n return Math.min(imageCountCeiling(modelCap), Math.max(1, asked))\n}\n\n/** Same-origin, loopback-only settings bridge for this plugin's namespace. */\nexport const SETTINGS_API = {\n describe: '/api/dsh-seework/settings/describe',\n mutate: '/api/dsh-seework/settings/mutate',\n /**\n * Folder choosing for the material directory, over the host's\n * `ctx.directoryPicker` seam: `directoryPicker` reports how the host can choose\n * (opening nothing), `pickDirectory` opens one chooser and waits.\n */\n directoryPicker: '/api/dsh-seework/settings/directory-picker',\n pickDirectory: '/api/dsh-seework/settings/pick-directory',\n} as const\n\n/**\n * How the host can choose a directory, mirroring the DSH seam's capabilities.\n *\n * `native` opens one OS chooser on the host's screen; `browse` only serves listing\n * primitives for the shell's own in-app browser (a remote client cannot reach the\n * host's display); `none` means no picker is composed at all.\n */\nexport type DirectoryPickerKind = 'native' | 'browse' | 'none'\n\n/** What the host reports about choosing a directory. */\nexport interface DirectoryPickerStatus {\n kind: DirectoryPickerKind\n /** Operator-facing note for anything other than `native`. */\n message?: string\n}\n\n/** What one pick attempt produced, as the route reports it. */\nexport interface PickDirectoryResult {\n /** The chosen absolute directory; absent when the operator cancelled. */\n path?: string\n cancelled?: boolean\n}\n\n/** SeeAI Hub model-catalog discovery (host-mediated, keeps no secrets). */\nexport const CATALOG_API = {\n models: '/api/dsh-seework/catalog/models',\n /**\n * Run one automatic detection round and report what changed (#652).\n *\n * The card calls this when it opens; the host throttles it against the\n * low-frequency background timer, so opening the card twice does not re-request.\n */\n refresh: '/api/dsh-seework/catalog/refresh',\n} as const\n\n/** The image-generation proxy route: the browser submits, the host calls upstream. */\nexport const GENERATE_API = '/api/dsh-seework/generate' as const\n\n/** Host-resident generation queue (browser side of the shared runtime). */\nexport const TASK_API = {\n list: '/api/dsh-seework/tasks/list',\n cancel: '/api/dsh-seework/tasks/cancel',\n} as const\n\n/** Update discovery for a published install, and the install it triggers. */\nexport const UPDATE_API = {\n /** Read the running version, the registry's newest, and how this copy got here. */\n status: '/api/dsh-seework/update/status',\n /**\n * Start installing the newest published version.\n *\n * Answers as soon as the install has been *started*, not when it finishes: the\n * install re-composes the profile and tears this plugin's routes down, so a\n * handler that waited for it could not deliver its own response.\n */\n apply: '/api/dsh-seework/update/apply',\n} as const\n\n/** How this copy of the plugin reached the profile. */\nexport type InstallKind = 'registry' | 'local' | 'unknown'\n\n/** What the settings card renders in its version row. */\nexport interface UpdateStatus {\n /** The version running right now. */\n current: string\n /** The newest version the registry offers, when the check could read one. */\n latest?: string\n /** How this copy was installed; only `registry` is offered an update button. */\n kind: InstallKind\n /** Whether `latest` is strictly newer than `current`. */\n updateAvailable: boolean\n /** Why no answer was available, when there was none. */\n error?: string\n}\n\n/** What the update route reports once it has started an install. */\nexport interface UpdateStart {\n /** Whether an install was actually started. */\n started: boolean\n /** The version being installed. */\n to?: string\n /** Why nothing was started. */\n error?: string\n}\n\n/**\n * The local material library — every image this plugin generates is written\n * under the data root and indexed, which is what the sidebar library and the\n * canvas read back.\n */\nexport const LIBRARY_API = {\n list: '/api/dsh-seework/library/list',\n /** Cheap identity poll: the newest entry id and the total. */\n head: '/api/dsh-seework/library/head',\n remove: '/api/dsh-seework/library/remove',\n clear: '/api/dsh-seework/library/clear',\n /** Prefix route: `/api/dsh-seework/library/image/<file>`. */\n image: '/api/dsh-seework/library/image',\n} as const\n\n/**\n * The library's identity without its contents: what a browser polls to notice a\n * generation that finished in the host (agent tools write entries the page is\n * never told about).\n */\nexport interface LibraryHead {\n total: number\n /** Newest entry id, absent while the library is empty. */\n newestId?: string\n /** Newest entry's creation time, absent while the library is empty. */\n newestAt?: number\n}\n\n/**\n * One durable image an Agent tool result points at.\n *\n * A tool result is not model-visible content, so the shell's own image loader\n * refuses to resolve it (`session.readAttachment` proves reachability from the\n * session log). The conversation card therefore asks this route with the\n * complete reference the host persisted in the tool result's presentation\n * metadata, and the host re-validates every field before reading the store.\n *\n * Prefix route: `/api/dsh-seework/attachment/image?attachment_id=…&media_type=…`.\n */\nexport const ATTACHMENT_API = {\n image: '/api/dsh-seework/attachment/image',\n} as const\n\n/** Image media types the attachment store accepts, as a runtime list. */\nexport const IMAGE_MEDIA_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/gif'] as const\n\n/** One of {@link IMAGE_MEDIA_TYPES}. */\nexport type ImageMedia = (typeof IMAGE_MEDIA_TYPES)[number]\n\n/**\n * Whether a wire value is an image media type this plugin may read and serve.\n * @param value - the candidate media type.\n * @returns true when it is one of the accepted image types.\n */\nexport function isImageMedia(value: string): value is ImageMedia {\n return (IMAGE_MEDIA_TYPES as readonly string[]).includes(value)\n}\n\n/** Data root the library lives under (overridable in settings). */\nexport const DEFAULT_DATA_DIR_NAME = 'dsh-seework'\n\n/**\n * Canvas documents live under `<data root>/canvas/`. The board is spatial, not\n * a node graph: every card is just a placed rectangle, and the only structure\n * is what the user sees (see `docs/architecture.md`).\n */\nexport const CANVAS_API = {\n list: '/api/dsh-seework/canvas/list',\n create: '/api/dsh-seework/canvas/create',\n read: '/api/dsh-seework/canvas/read',\n save: '/api/dsh-seework/canvas/save',\n remove: '/api/dsh-seework/canvas/remove',\n /**\n * Canvas-owned pictures (a burned annotation, a crop): the board writes one\n * with POST here, and reads it back from `<asset>/<file>`.\n *\n * They deliberately never enter the material library — the library records what\n * was generated, and a marked-up copy is the board's own material.\n */\n asset: '/api/dsh-seework/canvas/asset',\n /**\n * Housekeeping for those pictures: `assets` enumerates them (and says how much\n * of them no card shows any more), `pruneAssets` deletes exactly those, and\n * `removeAsset` deletes one — refused while a card still shows it.\n *\n * `assets` reads naturally as a *collection* path, but it is registered\n * separately from the `asset` prefix route, which matches `asset/<file>` only.\n */\n assets: '/api/dsh-seework/canvas/assets',\n pruneAssets: '/api/dsh-seework/canvas/assets/prune',\n removeAsset: '/api/dsh-seework/canvas/asset/remove',\n} as const\n\n/** One canvas-owned picture on disk. */\nexport interface CanvasAssetSummary {\n file: string\n bytes: number\n /** Whether a card on some board still shows it. */\n referenced: boolean\n}\n\n/** What the canvas housekeeping route reports. */\nexport interface CanvasAssetListing {\n files: CanvasAssetSummary[]\n /** Pictures no card references — the ones 「清理无用图片」 deletes. */\n orphans: number\n orphanBytes: number\n}\n\n/** One canvas-owned image, as the write route answers. */\nexport interface CanvasImageRef {\n file: string\n url: string\n mime: string\n width?: number\n height?: number\n}\n\n/** Where an image card's file lives: the material library, or the board's own assets. */\nexport type CanvasCardSource = 'library' | 'canvas'\n\n/**\n * Where an image card's picture came from.\n *\n * `chat` and `panel` mirror the material library's own `source` (the library\n * records whether a generation was asked for in a conversation or from the\n * panel); `annotation` and `crop` are composites this board made itself. Cards\n * written before this field existed have none, and the viewer infers one.\n */\nexport type CanvasCardOrigin = 'chat' | 'panel' | 'annotation' | 'crop'\n\n/**\n * Human label for one origin, for the card badge.\n * @param origin - the card's origin, when it has one.\n * @returns the badge text, or undefined when there is nothing honest to show.\n */\nexport function canvasOriginLabel(origin: CanvasCardOrigin | undefined): string | undefined {\n if (origin === 'chat') return '对话生成'\n if (origin === 'panel') return '面板生成'\n if (origin === 'annotation') return '标注合成'\n if (origin === 'crop') return '裁剪合成'\n return undefined\n}\n\n/**\n * The URL that serves one image card's picture.\n * @param file - the file name (library entry image, or canvas asset).\n * @param source - which store it belongs to; absent means the material library.\n * @returns the same-origin route URL.\n */\nexport function canvasImageUrl(file: string, source: CanvasCardSource = 'library'): string {\n return source === 'canvas'\n ? `${CANVAS_API.asset}/${file}`\n : `${LIBRARY_API.image}/${file}`\n}\n\n/** Canvas viewport: translation in board units plus the zoom factor. */\nexport interface CanvasViewport {\n x: number\n y: number\n /** Zoom factor, clamped to [CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX]. */\n k: number\n}\n\n/** The kinds of card a board can hold. */\nexport type CanvasCardKind = 'image' | 'text'\n\n/** One card placed on a board. */\nexport interface CanvasCard {\n id: string\n kind: CanvasCardKind\n /** Board coordinates of the card's top-left corner. */\n x: number\n y: number\n /** Board size in pixels. */\n width: number\n height: number\n /** Paint order; higher is on top. */\n z: number\n /** Image cards: the material-library file name (`library/image/<file>`). */\n file?: string\n /**\n * Image cards: which store `file` belongs to. Absent means the material library,\n * so every board written before canvas assets existed keeps working.\n */\n source?: CanvasCardSource\n /**\n * Image cards: where the picture came from (conversation generation, panel\n * generation, or this board's own annotation/crop). Shown as the card badge, and\n * what makes a board's own pictures distinguishable from library material.\n */\n origin?: CanvasCardOrigin\n /** Image cards: a snapshot of what it came from, for the card caption. */\n model?: string\n prompt?: string\n /** Text cards: the note body. */\n text?: string\n /** Text cards: font size in board pixels. */\n fontSize?: number\n}\n\n/** One board document. */\nexport interface CanvasDocument {\n id: string\n title: string\n /** Increments on every accepted save; the save route fences on it. */\n revision: number\n viewport: CanvasViewport\n cards: CanvasCard[]\n createdAt: number\n updatedAt: number\n}\n\n/** One board in the canvas list. */\nexport interface CanvasSummary {\n id: string\n title: string\n revision: number\n cardCount: number\n createdAt: number\n updatedAt: number\n}\n\n/** Canvas list payload. */\nexport interface CanvasListResult {\n canvases: CanvasSummary[]\n dataRoot: string\n}\n\n/** Generation modes: text-to-image, or image-to-image with reference images. */\nexport type GenerateMode = 'text' | 'edit'\n\n/** One model in the local catalog (what the user selected from the gateway). */\nexport interface ModelConfig {\n /** Model name sent as `model` to the gateway. */\n id: string\n /** Optional display name; empty means \"same as id\". */\n label?: string\n /**\n * Accepted `resolution` tiers, e.g. ['1K', '2K']. Empty means the model has\n * no tier vocabulary and requests omit `resolution` entirely.\n *\n * The array is **ordered, lowest first**: since catalog v2 an enum\n * descriptor's `values` order *is* the family's declared order, so this list\n * doubles as the ranking the announcement and the agent read.\n */\n resolutions: string[]\n /**\n * The tier a request uses when the caller names none: the model's own\n * declared default (`supported_parameters.resolution.default`).\n *\n * Deliberately **not** the same thing as `resolutions[0]`: a model may\n * declare a default that is not its lowest tier. Empty means the contract\n * declared none — a breach of the \"`resolution.default` is always published\"\n * invariant — and the plugin then omits `resolution` rather than guessing a\n * tier (it reports the missing default in the announcement instead).\n */\n resolutionDefault?: string\n /** Accepted `aspect_ratio` values, e.g. ['16:9', '1:1'] (declared order). */\n aspectRatios: string[]\n /** Accepted `output_format` values. */\n outputFormats: string[]\n /** Maximum images per request (1 when unknown). */\n maxImages: number\n /** Maximum reference images per request (0 = text-to-image only). */\n maxReferenceImages: number\n /**\n * Whether these capabilities came from the SeeAI Hub catalog at all.\n *\n * This decides what an *empty* list means. `true` = \"the catalog described\n * this model and listed nothing for that field, so the model does not take it\n * — omit it\". `false` = \"we know nothing — the entry carried no\n * `supported_parameters` at all (a bare OpenAI-compatible `/models` list, or\n * a catalog snapshot published before v2) — pass the user's value through\".\n * Getting this backwards either breaks bare gateways or turns defaults into\n * 400s.\n */\n capabilitiesKnown?: boolean\n /**\n * When this model's capabilities were last read from the catalog (epoch ms).\n *\n * Written on a **successful** discovery and kept in the saved settings, so a\n * surface that dates its data can say how fresh it is: a failed refresh leaves\n * the previous models (and this stamp) untouched, so an old value is the honest\n * signal that the snapshot may be stale. **Absent means unknown** — a settings\n * document written before this field existed — and there is deliberately no\n * sentinel value for that state.\n */\n discoveredAt?: number\n}\n\n/**\n * A generation request from the browser or an agent tool. The host resolves\n * the credentials, maps this onto the SeeAI Hub field vocabulary, and persists\n * the result into the library.\n */\nexport interface GenerateRequest {\n mode: GenerateMode\n /** Model id from the configured catalog. */\n model: string\n /** The prompt; SeeAI Hub validates length per model. */\n prompt: string\n /** Resolution tier (e.g. '2K'), 'auto', or '' to omit. */\n resolution: string\n /** Aspect ratio (e.g. '16:9'), 'auto', or '' to omit. */\n aspectRatio: string\n /** Output format: 'png' | 'jpeg' | 'webp', or '' to omit (gateway default). */\n outputFormat: string\n /**\n * Number of images; a positive integer. `docs/api/images.md` leaves the\n * ceiling to the model's catalog entry, and this plugin caps it at\n * {@link MAX_IMAGES_PER_REQUEST} on the way out.\n */\n n: number\n /** Reference images as data URLs (edit mode only). */\n imageUrls: string[]\n /** Original reference-image names, kept for the library record. */\n refNames?: string[]\n}\n\n/** Why one parameter never reached the gateway. */\nexport type DropReason =\n /** The catalog did not list the field for this model (or listed no fields at all). */\n | 'unsupported_field'\n /** The field is accepted by this model, but not with that value. */\n | 'invalid_value'\n\n/** One parameter left out of a request, with the reason it was left out. */\nexport interface DroppedParameter {\n field: string\n value: string\n /** The values the model does accept (empty = \"the model declares none\"). */\n allowed: string[]\n /** Machine-readable reason, so callers need not parse the message. */\n reason: DropReason\n}\n\n/** One generated image, normalized host-side to base64. */\nexport interface GeneratedImage {\n /** Raw base64 payload (no data: prefix). */\n b64: string\n /** MIME type of the payload, e.g. image/png. */\n mime: string\n /** Upstream revised prompt, when the provider returns one. */\n revisedPrompt?: string\n}\n\n/** Successful generation outcome. */\nexport interface GenerateResult {\n images: GeneratedImage[]\n /** Amount charged by the gateway for this request (CNY), when reported. */\n cost?: number\n}\n\n/** One image reference inside a library entry (a served URL, never base64). */\nexport interface LibraryImageRef {\n /** Same-origin URL: `${LIBRARY_API.image}/<file>`. */\n url: string\n /** Stored file name (what `remove` and the canvas address). */\n file: string\n mime: string\n /** Intrinsic size, when known. */\n width?: number\n height?: number\n revisedPrompt?: string\n}\n\n/** One library entry: a single generation and everything it produced. */\nexport interface LibraryEntry {\n id: string\n createdAt: number\n mode: GenerateMode\n model: string\n prompt: string\n resolution: string\n aspectRatio: string\n /**\n * Quality is **no longer a plugin parameter** (#661): every generation writes\n * an empty string here. The field stays because entries written before that\n * change carry a real value and the library must still read them back.\n */\n quality: string\n outputFormat: string\n n: number\n images: LibraryImageRef[]\n /** Amount charged by the gateway (CNY), when reported. */\n cost?: number\n /** Reference-image names (edit mode), for display only. */\n refNames?: string[]\n /** Where the generation came from. */\n source: 'agent' | 'panel' | 'canvas'\n /** Session the agent call belonged to, when applicable. */\n sessionId?: string\n /** Free-form user labels (phase 2 filtering). */\n tags?: string[]\n}\n\n/** Library list payload. */\nexport interface LibraryListResult {\n entries: LibraryEntry[]\n total: number\n /** On-disk image count across all entries (storage usage, sidebar footer). */\n imageCount: number\n /** Absolute data root the library is written to. */\n dataRoot: string\n}\n\n/** Generation task states. */\nexport type GenerationTaskStatus = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled'\n\n/** One host-resident generation task (shared by the panel and agent tools). */\nexport interface GenerationTask {\n id: string\n request: GenerateRequest\n status: GenerationTaskStatus\n createdAt: number\n startedAt?: number\n finishedAt?: number\n result?: GenerateResult\n /** The library entry written for this task, once it completed. */\n entryId?: string\n error?: string\n /** What asked for this task. */\n source: 'agent' | 'panel' | 'canvas'\n /** Owning agent session, when an agent asked for it. */\n sessionId?: string\n /**\n * Parameters left out because the selected model does not declare them.\n * Reported rather than hidden: a silently downgraded request should be\n * visible to whoever asked for it.\n */\n droppedParameters?: DroppedParameter[]\n}\n\n/**\n * One source the catalog discovery tried, with what it answered.\n */\nexport interface CatalogAttempt {\n url: string\n /** `ok` / `http <status>` / `unreachable` / `unusable`. */\n outcome: string\n /** Image models this source yielded (0 when it did not answer usefully). */\n models: number\n}\n\n/**\n * Catalog discovery result shown by the settings card.\n *\n * `models` is the **same shape the settings document stores** (#669): discovery\n * used to answer with a second, nearly identical type (`CatalogModel`) that every\n * consumer projected by hand, which is how one field addition became four\n * coordinated edits.\n */\nexport interface CatalogResult {\n models: ModelConfig[]\n /** Which kind of source answered: the SeeAI Hub catalog, or `/models`. */\n origin: 'catalog' | 'models'\n /** The URL that produced the returned list. */\n catalogUrl: string\n /** Total models the upstream reported before image filtering. */\n scanned: number\n /** Every source that was tried, in order — the \"why is it empty\" evidence. */\n attempts: CatalogAttempt[]\n}\n\n/**\n * What one automatic detection round changed (#652).\n *\n * The three id lists are the whole of the product decision this feature makes:\n * capabilities of a saved model are adopted (`adopted`), while a model the user\n * has not saved (`added`) and one the catalog stopped describing (`missing`) are\n * only reported and left for the user to decide.\n */\nexport interface CatalogRefreshSummary {\n /** When the catalog was successfully read (epoch ms). */\n refreshedAt: number\n /** Saved models whose catalog description changed and was adopted. */\n adopted: string[]\n /** Catalog models the user has not saved — reported, never adopted. */\n added: string[]\n /** Saved models the catalog no longer describes — reported, never removed. */\n missing: string[]\n}\n\n/** The result of asking for an automatic round (`catalog/refresh`). */\nexport interface CatalogRefreshOutcome {\n /** False when a throttle, a missing configuration, or a failure stopped it. */\n ran: boolean\n /** Why it did not run: `throttled` | `not-configured` | `failed`. */\n skipped?: 'throttled' | 'not-configured' | 'failed'\n /** Present once a round succeeded (also on the last success after a failure). */\n result?: CatalogRefreshSummary\n /** The catalog answer, so the card can stage models the user has not saved. */\n catalog?: CatalogResult\n}\n","/**\n * Browser-side API client for the plugin's own routes. Every call goes to the\n * same origin and the host half talks to SeeAI Hub, so the API key never\n * reaches the page.\n */\n\nimport {\n CANVAS_API,\n CATALOG_API,\n GENERATE_API,\n LIBRARY_API,\n SETTINGS_API,\n TASK_API,\n UPDATE_API,\n type CanvasAssetListing,\n type CanvasDocument,\n type CanvasImageRef,\n type CanvasListResult,\n type CanvasSummary,\n type CatalogResult,\n type CatalogRefreshOutcome,\n type DirectoryPickerStatus,\n type GenerateRequest,\n type GenerationTask,\n type LibraryEntry,\n type LibraryHead,\n type LibraryListResult,\n type PickDirectoryResult,\n type UpdateStart,\n type UpdateStatus,\n} from '../protocol.ts'\n\n/** One decoded bridge/route response. */\ntype Envelope<T> = { ok: true; value: T } | { ok: false; code: string; message: string }\n\n/** POST one JSON request and decode the envelope. */\nasync function postJson<T>(path: string, body: unknown): Promise<Envelope<T>> {\n try {\n const response = await fetch(path, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n const payload = await response.json() as Envelope<T>\n if (typeof payload === 'object' && payload !== null && 'ok' in payload) return payload\n return { ok: false, code: 'internal', message: `接口返回了未知形状(HTTP ${response.status})` }\n } catch {\n return { ok: false, code: 'internal', message: '接口不可达' }\n }\n}\n\n/** The generation and library routes the panel/canvas consume. */\nexport class SeeWorkApi {\n /** Discover the deployment's image models (never persists anything). */\n catalog(input: { apiUrl?: string; serviceUrl?: string; apiKey?: string } = {}): Promise<Envelope<CatalogResult>> {\n return postJson<CatalogResult>(CATALOG_API.models, input)\n }\n\n /**\n * Run one automatic detection round and adopt refreshed capabilities (#652).\n *\n * Called when the card opens. The host throttles it against its own background\n * timer, so reopening the card does not re-request; an unconfigured plugin\n * answers `skipped: 'not-configured'` without touching the network.\n *\n * @returns what the round changed — the catalog answer is included so the card\n * can stage models the user has not saved yet.\n */\n catalogRefresh(): Promise<Envelope<CatalogRefreshOutcome>> {\n return postJson<CatalogRefreshOutcome>(CATALOG_API.refresh, {})\n }\n\n /** Submit one generation and wait for its task to settle. */\n generate(request: GenerateRequest & { waitForCompletion?: boolean }): Promise<Envelope<{ task: GenerationTask }>> {\n return postJson<{ task: GenerationTask }>(GENERATE_API, request)\n }\n\n /** Every retained task, newest first. */\n tasks(): Promise<Envelope<{ tasks: GenerationTask[] }>> {\n return postJson<{ tasks: GenerationTask[] }>(TASK_API.list, {})\n }\n\n /** Cancel a queued or running task. */\n cancel(taskId: string): Promise<Envelope<{ task: GenerationTask }>> {\n return postJson<{ task: GenerationTask }>(TASK_API.cancel, { taskId })\n }\n\n /** The material library, newest first. */\n library(): Promise<Envelope<LibraryListResult>> {\n return postJson<LibraryListResult>(LIBRARY_API.list, {})\n }\n\n /**\n * The library's identity only — what the page polls to notice a generation\n * that finished in the host.\n */\n libraryHead(): Promise<Envelope<LibraryHead>> {\n return postJson<LibraryHead>(LIBRARY_API.head, {})\n }\n\n /** Remove one library entry and its files. */\n removeEntry(id: string): Promise<Envelope<{ entries: LibraryEntry[] }>> {\n return postJson<{ entries: LibraryEntry[] }>(LIBRARY_API.remove, { id })\n }\n\n /** Remove every library entry. */\n clearLibrary(): Promise<Envelope<{ entries: LibraryEntry[] }>> {\n return postJson<{ entries: LibraryEntry[] }>(LIBRARY_API.clear, {})\n }\n\n /** Every stored board, newest-updated first. */\n canvases(): Promise<Envelope<CanvasListResult>> {\n return postJson<CanvasListResult>(CANVAS_API.list, {})\n }\n\n /** Create a fresh board. */\n createCanvas(title?: string): Promise<Envelope<{ canvas: CanvasDocument }>> {\n return postJson<{ canvas: CanvasDocument }>(CANVAS_API.create, title === undefined ? {} : { title })\n }\n\n /** Read one board. */\n readCanvas(id: string): Promise<Envelope<{ canvas: CanvasDocument }>> {\n return postJson<{ canvas: CanvasDocument }>(CANVAS_API.read, { id })\n }\n\n /** Save a board, fenced by the revision it was read at. */\n saveCanvas(canvas: CanvasDocument, expectedRevision: number): Promise<Envelope<{ canvas: CanvasDocument }>> {\n return postJson<{ canvas: CanvasDocument }>(CANVAS_API.save, { canvas, expectedRevision })\n }\n\n /** Delete a board. */\n removeCanvas(id: string): Promise<Envelope<{ canvases: CanvasSummary[] }>> {\n return postJson<{ canvases: CanvasSummary[] }>(CANVAS_API.remove, { id })\n }\n\n /**\n * Ask how the host can choose a folder (opens nothing).\n *\n * `native` means one OS chooser on the host's screen; `browse` means the shell's\n * own in-app browser serves listings instead, and `none` means there is no picker\n * at all — in both latter cases the card offers no button.\n */\n directoryPicker(): Promise<Envelope<DirectoryPickerStatus>> {\n return postJson<DirectoryPickerStatus>(SETTINGS_API.directoryPicker, {})\n }\n\n /**\n * Open the host's folder chooser and wait for the operator.\n * @returns the chosen absolute directory, or `cancelled: true`.\n */\n pickDirectory(): Promise<Envelope<PickDirectoryResult>> {\n return postJson<PickDirectoryResult>(SETTINGS_API.pickDirectory, {})\n }\n\n /**\n * Store one canvas-owned picture (a burned annotation or a crop).\n *\n * It never enters the material library: the library records what was generated,\n * and a marked-up copy belongs to the board that produced it.\n *\n * @param dataUrl - the image as a PNG data URL.\n * @returns the stored image's file name and URL, or an error envelope.\n */\n writeCanvasAsset(dataUrl: string): Promise<Envelope<{ image: CanvasImageRef }>> {\n return postJson<{ image: CanvasImageRef }>(CANVAS_API.asset, { dataUrl })\n }\n\n /**\n * Enumerate the board's own pictures and how much of them is unreferenced.\n * @returns each file with its size and whether a card still shows it.\n */\n canvasAssets(): Promise<Envelope<CanvasAssetListing>> {\n return postJson<CanvasAssetListing>(CANVAS_API.assets, {})\n }\n\n /** Delete every board picture no card shows any more. */\n pruneCanvasAssets(): Promise<Envelope<{ removed: number; bytes: number }>> {\n return postJson<{ removed: number; bytes: number }>(CANVAS_API.pruneAssets, {})\n }\n\n /**\n * Delete one board picture.\n *\n * Refused while a card still shows it, so removing a card and dropping its file\n * has to happen in that order (and a second board's copy is never pulled away).\n *\n * @param file - the picture's file name.\n */\n removeCanvasAsset(file: string): Promise<Envelope<{ removed: number; bytes: number }>> {\n return postJson<{ removed: number; bytes: number }>(CANVAS_API.removeAsset, { file })\n }\n\n /**\n * Read the running version, the registry's newest, and how this copy got here.\n *\n * Never fails on a host without a plugin manager or with an unreachable\n * registry: the answer carries an `error` and the card renders it.\n */\n updateStatus(): Promise<Envelope<UpdateStatus>> {\n return postJson<UpdateStatus>(UPDATE_API.status, {})\n }\n\n /**\n * Start installing the newest published version.\n *\n * Answers as soon as the install has been **started**, not when it finishes:\n * applying it re-composes the profile and unloads this plugin, so there is\n * nothing left to answer a later outcome. The caller polls `updateStatus`\n * until the version changes.\n */\n applyUpdate(): Promise<Envelope<UpdateStart>> {\n return postJson<UpdateStart>(UPDATE_API.apply, {})\n }\n}\n","/**\n * Local form controls for the SeeWork settings card.\n *\n * The shell's `dsh-client-ui-primitives` package cannot be a runtime dependency\n * of this plugin: it is not in the shared module baseline, and bundling it drags\n * its whole syntax-highlighting dependency tree into this bundle. These four\n * controls carry the same interaction contract and are styled with the shell's\n * `--dsw-*` design tokens, so they follow the active theme.\n */\n\nimport type { ButtonHTMLAttributes, InputHTMLAttributes, ReactNode } from 'react'\nimport css from './controls.module.css'\n\n/** Render a button.\n * @param props.variant - visual family; `primary` is the confirming action.\n */\nexport function Button({ variant = 'ghost', className, children, ...rest }: {\n variant?: 'primary' | 'ghost' | 'outline'\n className?: string | undefined\n children?: ReactNode\n} & ButtonHTMLAttributes<HTMLButtonElement>): JSX.Element {\n const variantClass = variant === 'primary' ? css.buttonPrimary : variant === 'outline' ? css.buttonOutline : ''\n return (\n <button\n type=\"button\"\n className={[css.button, variantClass, className].filter(Boolean).join(' ')}\n {...rest}\n >\n {children}\n </button>\n )\n}\n\n/** Render a text input. */\nexport function TextInput({ className, ...rest }: { className?: string | undefined } & InputHTMLAttributes<HTMLInputElement>): JSX.Element {\n return <input className={[css.input, className].filter(Boolean).join(' ')} {...rest} />\n}\n\n/** Render a toggle switch.\n * @param props.label - accessible name, rendered beside the control.\n */\nexport function Toggle({ checked, onChange, label, title, disabled }: {\n checked: boolean\n onChange: (next: boolean) => void\n label: string\n title?: string | undefined\n disabled?: boolean\n}): JSX.Element {\n return (\n <label className={css.toggle} title={title}>\n <input\n type=\"checkbox\"\n role=\"switch\"\n checked={checked}\n disabled={disabled}\n onChange={event => { onChange(event.target.checked) }}\n />\n <span>{label}</span>\n </label>\n )\n}\n\n/** Render a small status pill. */\nexport function Pill({ children }: { children: ReactNode }): JSX.Element {\n return <span className={css.pill}>{children}</span>\n}\n\n/**\n * Copy text to the clipboard, preferring the async Clipboard API and falling\n * back to a hidden textarea (older browsers and non-secure origins).\n * @param text - the text to place on the clipboard.\n * @returns settlement after a copy attempt succeeded.\n */\nexport async function copyText(text: string): Promise<void> {\n if (navigator.clipboard?.writeText !== undefined) {\n await navigator.clipboard.writeText(text)\n return\n }\n const area = document.createElement('textarea')\n area.value = text\n area.setAttribute('readonly', '')\n area.style.position = 'fixed'\n area.style.opacity = '0'\n document.body.appendChild(area)\n try {\n area.select()\n if (!document.execCommand('copy')) throw new Error('copy refused')\n } finally {\n area.remove()\n }\n}\n","/**\n * The version row: what is running, what the registry offers, and the button\n * that installs it.\n *\n * Starting an update unloads this plugin — the profile is re-composed and these\n * routes go away with it — so the request that asked for the install is the last\n * one the old code serves. The row therefore never waits for an outcome: it\n * polls the status route until the running version changes, and reads a failed\n * poll as \"still working\" rather than as an error, because that failure is\n * exactly what a host in the middle of a reload looks like.\n *\n * The page itself is still running the bundle it booted with, so the last word\n * is always \"refresh\": the host is on the new version the moment the install\n * lands, the tab is not.\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { UpdateStatus } from '../protocol.ts'\nimport type { SeeWorkApi } from './api.ts'\nimport { Button } from './controls.tsx'\nimport css from './settings-card.module.css'\n\n/** How often the row asks again while an install is running. */\nconst POLL_MS = 2000\n\n/** How many polls before the row stops waiting and asks for a page refresh. */\nconst MAX_POLLS = 45\n\n/** Wait one interval. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => { setTimeout(resolve, ms) })\n}\n\n/** What the row is doing right now. */\ntype Phase = 'checking' | 'idle' | 'updating' | 'done'\n\n/**\n * Render the settings card's version section.\n * @param props.api - the plugin route client.\n */\nexport function VersionRow({ api }: { api: SeeWorkApi }): JSX.Element {\n const [status, setStatus] = useState<UpdateStatus | undefined>(undefined)\n const [phase, setPhase] = useState<Phase>('checking')\n const [note, setNote] = useState<string | undefined>(undefined)\n\n const check = useCallback(async (): Promise<void> => {\n setPhase('checking')\n setNote(undefined)\n const answer = await api.updateStatus()\n if (answer.ok) {\n setStatus(answer.value)\n setNote(answer.value.error)\n } else {\n setNote(answer.message)\n }\n setPhase('idle')\n }, [api])\n\n useEffect(() => { void check() }, [check])\n\n const apply = useCallback(async (): Promise<void> => {\n setPhase('updating')\n setNote(undefined)\n const started = await api.applyUpdate()\n if (!started.ok) {\n setNote(started.message)\n setPhase('idle')\n return\n }\n const from = status?.current\n for (let attempt = 0; attempt < MAX_POLLS; attempt++) {\n await sleep(POLL_MS)\n const answer = await api.updateStatus()\n // A failed poll is the reload in progress, not a failure.\n if (!answer.ok) continue\n if (answer.value.current !== from) {\n setStatus(answer.value)\n setPhase('done')\n return\n }\n }\n // Either the host came back on the same version or it never came back; both\n // leave the tab running a bundle older than the host, so refreshing is the\n // next step either way.\n setPhase('done')\n }, [api, status?.current])\n\n const local = status?.kind === 'local'\n\n return (\n <>\n <div className={css.field}>\n <span className={css.label}>当前版本</span>\n <div className={css.dirRow}>\n <span className={css.fixedValue} data-dsh-seework-version=\"\" title={status?.current}>\n {status?.current ?? '…'}\n </span>\n <Button\n variant=\"outline\"\n disabled={phase === 'checking' || phase === 'updating'}\n onClick={() => { void check() }}\n >\n {phase === 'checking' ? '检查中…' : '检查更新'}\n </Button>\n </div>\n </div>\n\n {local\n ? (\n <p className={css.hint} data-dsh-seework-version-kind=\"local\">\n 这是本地目录安装,更新走 <code>pnpm build &amp;&amp; pnpm sync</code>,这里不提供更新。\n </p>\n )\n : phase === 'updating'\n ? <p className={css.hint}>正在更新:宿主重载插件期间界面可能短暂失联,属正常。</p>\n : phase === 'done'\n ? (\n <p className={css.ok} data-dsh-seework-version-state=\"done\">\n 更新已开始。请<strong>重启 DeepSeek Harness</strong> 让新版本生效——桌面客户端没有刷新页面的入口。\n </p>\n )\n : status?.updateAvailable === true && status.latest !== undefined\n ? (\n <div className={css.row}>\n <Button variant=\"primary\" onClick={() => { void apply() }}>\n 更新到 {status.latest}\n </Button>\n </div>\n )\n : status?.latest !== undefined\n ? <p className={css.muted}>已是最新版本(注册表上是 {status.latest})。</p>\n : null}\n\n {note !== undefined ? <p className={css.warn}>{note}</p> : null}\n </>\n )\n}\n","/**\n * The SeeWork settings card (Settings → Plugins → SeeWork).\n *\n * Edits the SeeAI Hub connection (API address + user API key), the image-model\n * catalog, and generation defaults. The local data root is shown but **not\n * editable**: it is the plugin's own storage location, and nobody should have to\n * type a filesystem path into a form (user report: 「素材目录不应该手动填写地址」).\n * Changing it stays possible through the plugin's settings document.\n *\n * The address and key are staged rather than written on every keystroke:\n * \"检测可用模型\" probes exactly what is on screen, and \"保存连接\" persists it.\n * That matters because the API key is a `role('secret')` field — the wire never\n * returns the stored value, so an accidental overwrite could not be undone.\n */\n\nimport { DEFAULT_ASPECT_RATIO, DEFAULT_OUTPUT_FORMAT, OUTPUT_FORMAT_FALLBACKS, UNIFIED_ASPECT_RATIOS, includesIgnoringCase, type DirectoryPickerStatus, type ModelConfig } from '../protocol.ts'\nimport { Button, Pill, TextInput, Toggle } from './controls.tsx'\nimport { VersionRow } from './VersionRow.tsx'\nimport { useCallback, useEffect, useState } from 'react'\nimport { SeeWorkApi } from './api.ts'\nimport type { SeeWorkConfig, SeeWorkScope } from './settings-scope.ts'\nimport css from './settings-card.module.css'\n\n/** What the card controller hands its component. */\nexport interface SettingsCardFace {\n scope: SeeWorkScope\n api: SeeWorkApi\n}\n\n/** Persisted catalog entries, normalized for display. */\nfunction selectedModels(config: SeeWorkConfig | undefined): ModelConfig[] {\n return Array.isArray(config?.models) ? config.models : []\n}\n\n/** Re-render on every scope or secret change. */\nfunction useScopeSnapshot(scope: SeeWorkScope): { config: SeeWorkConfig | undefined; status: string; keySet: boolean } {\n const [tick, setTick] = useState(0)\n useEffect(() => {\n const bump = (): void => { setTick(value => value + 1) }\n const stops = [scope.subscribe(bump), scope.subscribeSecrets(bump)]\n return () => { for (const stop of stops) stop() }\n }, [scope])\n void tick\n const snapshot = scope.getSnapshot()\n return { config: snapshot.value, status: snapshot.status, keySet: scope.getSecretSet('apiKey') }\n}\n\n/** The settings card. */\nexport function SeeWorkSettingsCard({ scope, api }: SettingsCardFace): JSX.Element {\n const { config, status, keySet } = useScopeSnapshot(scope)\n const [draftApiUrl, setDraftApiUrl] = useState<string | undefined>(undefined)\n const [draftServiceUrl, setDraftServiceUrl] = useState<string | undefined>(undefined)\n const [draftApiKey, setDraftApiKey] = useState('')\n const [candidates, setCandidates] = useState<ModelConfig[] | undefined>(undefined)\n /**\n * Whether the staged rows came from the automatic round (#652).\n *\n * The model section seeds its ticks differently for the two sources: a manual\n * detection stages exactly the saved choices, while the automatic round also\n * pre-ticks the models the catalog gained, so adding one is a single\n * 「保存模型」. It is a prop rather than section state because only the parent\n * knows which round produced the rows.\n */\n const [stagedFromAuto, setStagedFromAuto] = useState(false)\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<{ tone: 'ok' | 'warn' | 'error'; text: string } | undefined>(undefined)\n /** The data root the host actually resolved, for the read-only display. */\n const [resolvedRoot, setResolvedRoot] = useState('')\n /** How the host can choose a folder (asking opens nothing). */\n const [picker, setPicker] = useState<DirectoryPickerStatus | undefined>(undefined)\n\n const apiUrl = draftApiUrl ?? config?.apiUrl ?? ''\n const serviceUrl = draftServiceUrl ?? config?.serviceUrl ?? ''\n const persisted = selectedModels(config)\n /** Whether the user moved the data root away from the default. */\n const customRoot = (config?.dataDir ?? '').trim() !== ''\n\n /**\n * Ask the host where the library really lives.\n *\n * The configured value may be empty (the default) or relative to DSH_HOME, and\n * only the host knows — so the card shows the host's answer instead of guessing.\n */\n const refreshRoot = useCallback(async (): Promise<void> => {\n const result = await api.library()\n if (result.ok) setResolvedRoot(result.value.dataRoot)\n }, [api])\n\n useEffect(() => {\n let live = true\n // Where the data lives, and whether this host can open a folder chooser.\n void api.library().then(result => {\n if (live && result.ok) setResolvedRoot(result.value.dataRoot)\n }).catch(() => {})\n void api.directoryPicker().then(result => {\n if (live && result.ok) setPicker(result.value)\n }).catch(() => {})\n return () => { live = false }\n }, [api])\n\n /** Open the host's folder chooser and store what it returns. */\n const chooseDirectory = useCallback(async (): Promise<void> => {\n setBusy(true)\n setNotice(undefined)\n const result = await api.pickDirectory()\n if (!result.ok) {\n setBusy(false)\n setNotice({ tone: 'warn', text: result.message })\n return\n }\n // Cancelling the dialog is not an error and needs no message.\n if (result.value.path === undefined) {\n setBusy(false)\n return\n }\n const path = result.value.path\n await scope.set('dataDir', path)\n await scope.load()\n await refreshRoot()\n setBusy(false)\n setNotice({ tone: 'ok', text: `素材目录已改为 ${path}(旧目录里的文件没有被删)。` })\n }, [api, refreshRoot, scope])\n\n /** Go back to the plugin's own default location. */\n const restoreDefaultDirectory = useCallback(async (): Promise<void> => {\n setBusy(true)\n setNotice(undefined)\n await scope.unset('dataDir')\n await scope.load()\n await refreshRoot()\n setBusy(false)\n setNotice({ tone: 'ok', text: '素材目录已恢复默认(旧目录里的文件没有被删)。' })\n }, [refreshRoot, scope])\n\n /** Write one scalar field and re-read the section. */\n const write = useCallback((field: string, value: unknown): void => {\n void scope.set(field, value).then(() => scope.load())\n }, [scope])\n\n /** Persist the staged connection fields. */\n const saveConnection = useCallback(async (): Promise<void> => {\n setBusy(true)\n const ops: Parameters<SeeWorkScope['mutateOps']>[0] = []\n if (apiUrl.trim() !== (config?.apiUrl ?? '')) ops.push({ op: 'set', path: ['apiUrl'], value: apiUrl.trim() })\n if (serviceUrl.trim() !== (config?.serviceUrl ?? '')) ops.push({ op: 'set', path: ['serviceUrl'], value: serviceUrl.trim() })\n if (draftApiKey.trim() !== '') ops.push({ op: 'set', path: ['apiKey'], value: draftApiKey.trim() })\n if (ops.length === 0) {\n setBusy(false)\n setNotice({ tone: 'warn', text: '没有需要保存的改动。' })\n return\n }\n await scope.mutateOps(ops)\n await scope.load()\n setDraftApiUrl(undefined)\n setDraftServiceUrl(undefined)\n setDraftApiKey('')\n setBusy(false)\n setNotice({ tone: 'ok', text: '连接设置已保存。' })\n }, [apiUrl, config?.apiUrl, config?.serviceUrl, draftApiKey, scope, serviceUrl])\n\n /**\n * The automatic detection round that runs when the card opens (#652).\n *\n * It keeps the saved snapshot fresh without the user doing anything: the host\n * adopts refreshed capabilities itself (that never changes the selection), and\n * this only surfaces what it *found* — models the user has not saved are staged\n * as ticked rows, so adding them is one 「保存模型」 rather than a re-detect.\n *\n * Deliberately silent on everything else: no configuration yet, a throttled\n * round, an unreachable service, or a plugin with nothing saved all resolve to\n * \"do nothing here\". The card's own 「检测可用模型」 remains the explicit path\n * that reports failures.\n */\n useEffect(() => {\n if (status !== 'ready') return\n let live = true\n void api.catalogRefresh().then(result => {\n if (!live || !result.ok || !result.value.ran) return\n const summary = result.value.result\n const catalog = result.value.catalog\n if (summary === undefined || catalog === undefined) return\n void scope.load()\n const parts: string[] = []\n if (summary.added.length > 0) {\n setStagedFromAuto(true)\n setCandidates(catalog.models)\n parts.push(`目录里有 ${summary.added.length} 个还没加入的模型,已勾选,点「保存模型」即可加入`)\n }\n if (summary.missing.length > 0) {\n parts.push(`有 ${summary.missing.length} 个已保存模型在目录里找不到了(快照保留着,是否移除由你决定)`)\n }\n if (summary.adopted.length > 0) parts.push(`已刷新 ${summary.adopted.length} 个已保存模型的能力`)\n if (parts.length > 0) setNotice({ tone: 'warn', text: `${parts.join(';')}。` })\n }).catch(() => {})\n return () => { live = false }\n // `status` gates the first run; `scope` and `api` are stable for the card's life.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status, api, scope])\n\n /** Probe the catalog with the on-screen addresses and key. */\n const detect = useCallback(async (): Promise<void> => {\n setBusy(true)\n setNotice(undefined)\n setStagedFromAuto(false)\n const result = await api.catalog({ apiUrl: apiUrl.trim(), serviceUrl: serviceUrl.trim(), apiKey: draftApiKey.trim() })\n setBusy(false)\n if (!result.ok) {\n setCandidates(undefined)\n // The host reports every address it tried; show them so a wrong address\n // is a five-second fix rather than a mystery.\n setNotice({ tone: 'error', text: `检测失败:${result.message}` })\n return\n }\n setCandidates(result.value.models)\n const tried = result.value.attempts\n .map(attempt => `${attempt.url}(${attempt.outcome})`)\n .join(';')\n if (result.value.models.length === 0) {\n setNotice({\n tone: 'warn',\n text: `扫到 ${result.value.scanned} 个模型,其中没有图片模型。来源:${tried}`,\n })\n return\n }\n setNotice({\n tone: 'ok',\n text: `发现 ${result.value.models.length} 个图片模型(共扫描 ${result.value.scanned} 个),来自 ${result.value.catalogUrl}。勾选后点「保存模型」。`,\n })\n }, [api, apiUrl, draftApiKey, serviceUrl])\n\n /** Persist one catalog selection (a whole-array write, secrets untouched). */\n const saveModels = useCallback(async (selection: ModelConfig[]): Promise<void> => {\n setBusy(true)\n // Discovery already answers with the stored shape (#669), so the selection is\n // written as it stands: the field list and the rules that make a degenerate\n // value safe live in `capability.ts` (the schema, the read-back and the\n // ceiling), not here. The discovery time travels with it too — a dated surface\n // reports it, and a failed re-detection leaves the old value (and its honest\n // \"may be stale\" reading) in place.\n const models: ModelConfig[] = selection\n // One atomic write: `models` and `defaultModel` must not be able to land\n // apart (a revision conflict between two writes would leave them disagreeing).\n const ops: Parameters<SeeWorkScope['mutateOps']>[0] = [{ op: 'set', path: ['models'], value: models }]\n if (models.length > 0 && (config?.defaultModel ?? '') === '') {\n ops.push({ op: 'set', path: ['defaultModel'], value: models[0]!.id })\n }\n await scope.mutateOps(ops)\n await scope.load()\n setBusy(false)\n setNotice({ tone: 'ok', text: `已保存 ${models.length} 个模型。` })\n }, [config?.defaultModel, scope])\n\n if (status === 'loading') {\n return <div className={css.card}><p className={css.muted}>正在读取设置…</p></div>\n }\n if (status === 'unavailable') {\n return (\n <div className={css.card}>\n <p className={css.error}>\n 设置接口不可达。请确认浏览器打开的就是本机 DSH 宿主(该接口只监听本机回环地址)。\n </p>\n </div>\n )\n }\n\n return (\n <div className={css.card}>\n <section className={css.section}>\n <h3 className={css.sectionTitle}>SeeAI Hub 连接</h3>\n <label className={css.field}>\n <span className={css.label}>API 地址</span>\n <TextInput\n value={apiUrl}\n placeholder=\"http://127.0.0.1:8080/v1\"\n spellCheck={false}\n onChange={event => { setDraftApiUrl(event.target.value) }}\n />\n </label>\n <p className={css.hint}>\n 生成接口的地址,填到 <code>/v1</code> 为止。开发环境默认 <code>http://127.0.0.1:8080/v1</code>。\n </p>\n <label className={css.field}>\n <span className={css.label}>模型目录地址</span>\n <TextInput\n value={serviceUrl}\n placeholder=\"http://127.0.0.1:8081\"\n spellCheck={false}\n onChange={event => { setDraftServiceUrl(event.target.value) }}\n />\n </label>\n <p className={css.hint}>\n SeeAI Hub 的「模型目录」在 service 上(默认 <code>127.0.0.1:8081</code>),不在生成网关上;\n 两个地址各管各的。如果你的部署把两者挂在同一个域名下,填同一个域名即可——插件两个地址都会试,并把结果告诉你。\n </p>\n <label className={css.field}>\n <span className={css.label}>\n 用户 API Key\n {keySet ? <Pill>已保存</Pill> : null}\n </span>\n <TextInput\n value={draftApiKey}\n type=\"password\"\n autoComplete=\"off\"\n spellCheck={false}\n placeholder={keySet ? '留空表示不修改已保存的 Key' : 'sk-…'}\n onChange={event => { setDraftApiKey(event.target.value) }}\n />\n </label>\n <p className={css.hint}>\n 在 SeeAI Hub 后台「API Keys」里创建的设备 Key。密钥只写在本机设置文档里,不会存到浏览器。\n </p>\n <div className={css.row}>\n <Button variant=\"outline\" disabled={busy} onClick={() => { void detect() }}>\n {busy ? '处理中…' : '检测可用模型'}\n </Button>\n <Button variant=\"primary\" disabled={busy} onClick={() => { void saveConnection() }}>\n 保存连接\n </Button>\n </div>\n </section>\n\n <ModelSection\n persisted={persisted}\n candidates={candidates}\n stagedFromAuto={stagedFromAuto}\n busy={busy}\n onSave={selection => { void saveModels(selection) }}\n />\n\n <section className={css.section}>\n <h3 className={css.sectionTitle}>生成默认值</h3>\n <DefaultsFields config={config} models={persisted} onWrite={write} />\n </section>\n\n <section className={css.section}>\n <h3 className={css.sectionTitle}>本地与行为</h3>\n <div className={css.field}>\n <span className={css.label}>素材目录</span>\n <div className={css.dirRow}>\n <span className={css.fixedValue} data-dsh-seework-datadir=\"\" title={resolvedRoot}>\n {resolvedRoot !== '' ? resolvedRoot : '~/.dsh/dsh-seework(默认)'}\n </span>\n {picker?.kind === 'native'\n ? (\n <Button\n disabled={busy}\n data-dsh-seework-pick-dir=\"\"\n title=\"打开系统的文件夹选择窗口\"\n onClick={() => { void chooseDirectory() }}\n >\n 选择目录…\n </Button>\n )\n : null}\n {customRoot\n ? (\n <Button\n disabled={busy}\n data-dsh-seework-reset-dir=\"\"\n title=\"回到插件自己的默认位置\"\n onClick={() => { void restoreDefaultDirectory() }}\n >\n 恢复默认\n </Button>\n )\n : null}\n </div>\n </div>\n <p className={css.hint}>\n {picker === undefined || picker.kind === 'native'\n ? '对话里生成的图片、画布上的合成图都存在这里;换目录只是换个地方读写,旧目录里的文件不会被删。'\n : picker.message}\n </p>\n <BehaviorSwitches config={config} onWrite={write} />\n <div className={css.row}>\n <Button variant=\"primary\" disabled={busy} onClick={() => { void saveConnection() }}>\n 保存\n </Button>\n </div>\n </section>\n\n <section className={css.section}>\n <h3 className={css.sectionTitle}>版本</h3>\n <VersionRow api={api} />\n </section>\n\n {notice !== undefined\n ? <p className={notice.tone === 'ok' ? css.ok : notice.tone === 'warn' ? css.warn : css.error}>{notice.text}</p>\n : null}\n </div>\n )\n}\n\n/** The model catalog section: detection results plus the persisted selection. */\nfunction ModelSection({\n persisted,\n candidates,\n stagedFromAuto,\n busy,\n onSave,\n}: {\n persisted: ModelConfig[]\n candidates: ModelConfig[] | undefined\n /**\n * True when `candidates` came from the automatic round, whose rows pre-tick the\n * models the catalog gained; a manual detection stages the saved choices as-is.\n */\n stagedFromAuto: boolean\n busy: boolean\n onSave: (selection: ModelConfig[]) => void\n}): JSX.Element {\n // Until a detection runs, the table shows what is already saved; afterwards a\n // detection result the user has not accepted yet is what they see. Discovery\n // answers with the stored shape since #669, so a row is a model — only the\n // display label needs filling in.\n const rows = (candidates ?? persisted).map(model => ({ ...model, label: model.label ?? '' }))\n\n const [selected, setSelected] = useState<string[] | undefined>(undefined)\n // The staged selection is seeded when a detection result arrives, and only\n // then: re-seeding on every settings write would throw away boxes the user just\n // ticked (they type a default, the section re-reads, the ticks vanish).\n const persistedIds = persisted.map(model => model.id).join('\\n')\n useEffect(() => {\n if (candidates === undefined) {\n setSelected(undefined)\n return\n }\n const stored = persistedIds === '' ? [] : persistedIds.split('\\n')\n // A manual detection stages the saved choices as they are; the automatic\n // round additionally pre-ticks what the catalog gained (#652), so adding a\n // model is one 「保存模型」 per new row rather than a hunt for it.\n setSelected(stagedFromAuto\n ? [...new Set([...stored, ...candidates.map(model => model.id).filter(id => !stored.includes(id))])]\n : stored.filter(id => candidates.some(model => model.id === id)))\n // eslint-disable-next-line react-hooks/exhaustive-deps -- persistedIds and the source flag are read, not triggers\n }, [candidates])\n\n const checked = new Set(selected ?? persisted.map(model => model.id))\n const pending = rows.filter(model => checked.has(model.id))\n\n return (\n <section className={css.section}>\n <h3 className={css.sectionTitle}>图片模型</h3>\n <p className={css.hint}>\n 只勾选你的网关确实支持生图的模型。没有目录信息的网关按「仅文生图、参数由上游决定」处理。\n </p>\n {rows.length === 0\n ? <p className={css.muted}>还没有模型:先填好地址与 Key,点「检测可用模型」。</p>\n : (\n <div className={css.modelList}>\n {rows.map(model => (\n <label key={model.id} className={css.modelRow}>\n <input\n type=\"checkbox\"\n checked={checked.has(model.id)}\n onChange={event => {\n const next = new Set(checked)\n if (event.target.checked) next.add(model.id)\n else next.delete(model.id)\n setSelected(rows.filter(candidate => next.has(candidate.id)).map(candidate => candidate.id))\n }}\n />\n <span className={css.modelMain}>\n <span className={css.modelName}>{model.label !== '' ? model.label : model.id}</span>\n {model.label !== '' && model.label !== model.id ? <code className={css.modelId}>{model.id}</code> : null}\n <span className={css.modelCapability}>\n {[\n model.resolutions.length > 0 ? model.resolutions.join(' / ') : '档位由上游决定',\n model.aspectRatios.length > 0 ? `${model.aspectRatios.length} 种比例` : '比例由上游决定',\n model.maxReferenceImages > 0 ? `可带参考图 ≤${model.maxReferenceImages}` : '仅文生图',\n ].filter(Boolean).join(' · ')}\n </span>\n </span>\n </label>\n ))}\n </div>\n )}\n <div className={css.row}>\n <Button\n variant=\"primary\"\n \n disabled={busy || rows.length === 0}\n onClick={() => { onSave(pending) }}\n >\n 保存模型\n </Button>\n {selected !== undefined\n ? <Button variant=\"ghost\" onClick={() => { setSelected([]) }}>全不选</Button>\n : null}\n </div>\n </section>\n )\n}\n\n/**\n * Output formats to offer: what the saved models declare, or the three we know\n * (#658/D-16); the stored value is kept selectable so the control never shows\n * something other than what is stored.\n */\nfunction outputFormatOptions(models: ModelConfig[], current: string): string[] {\n const seen: string[] = []\n for (const model of models) {\n for (const format of model.outputFormats ?? []) {\n if (!includesIgnoringCase(seen, format)) seen.push(format)\n }\n }\n return withCurrent(seen.length > 0 ? seen : [...OUTPUT_FORMAT_FALLBACKS], current)\n}\n\n/** The gateway-wide ratios (#658/D-16), keeping a stored value that is not one. */\nfunction aspectRatioOptions(current: string): string[] {\n return withCurrent([...UNIFIED_ASPECT_RATIOS], current)\n}\n\n/** Keep a stored value selectable even when the list does not offer it (old documents). */\nfunction withCurrent(options: string[], current: string): string[] {\n const wanted = current.trim()\n if (wanted !== '' && !includesIgnoringCase(options, wanted)) options.push(wanted)\n return options\n}\n\n/** Generation defaults; each control writes its own scalar field immediately. */\nfunction DefaultsFields({\n config,\n models,\n onWrite,\n}: {\n config: SeeWorkConfig | undefined\n models: ModelConfig[]\n onWrite: (field: string, value: unknown) => void\n}): JSX.Element {\n // The two defaulted values a user may override here. Resolution is deliberately\n // absent: it is derived per model (#658/D-17), and \"images per request\" is gone\n // too (#658/D-15) — `n` defaults to 1 in code.\n const ratio = config?.defaultAspectRatio?.trim() || DEFAULT_ASPECT_RATIO\n const format = config?.outputFormat?.trim() || DEFAULT_OUTPUT_FORMAT\n return (\n <div className={css.grid}>\n <label className={css.field}>\n <span className={css.label}>默认模型</span>\n <select\n className={css.select}\n value={config?.defaultModel ?? ''}\n onChange={event => { onWrite('defaultModel', event.target.value) }}\n >\n <option value=\"\">(目录里的第一个)</option>\n {models.map(model => (\n <option key={model.id} value={model.id}>{model.label !== undefined && model.label !== '' ? model.label : model.id}</option>\n ))}\n </select>\n </label>\n <label className={css.field}>\n <span className={css.label}>默认比例</span>\n <select\n className={css.select}\n value={ratio}\n onChange={event => { onWrite('defaultAspectRatio', event.target.value) }}\n >\n {aspectRatioOptions(ratio).map(option => <option key={option} value={option}>{option}</option>)}\n </select>\n </label>\n <label className={css.field}>\n <span className={css.label}>输出格式</span>\n <select\n className={css.select}\n value={format}\n onChange={event => { onWrite('outputFormat', event.target.value) }}\n >\n {outputFormatOptions(models, format).map(option => <option key={option} value={option}>{option}</option>)}\n </select>\n </label>\n </div>\n )\n}\n\n/** Plugin switches. */\nfunction BehaviorSwitches({\n config,\n onWrite,\n}: {\n config: SeeWorkConfig | undefined\n onWrite: (field: string, value: unknown) => void\n}): JSX.Element {\n return (\n <div className={css.switches}>\n <Toggle\n checked={config?.enabled ?? true}\n label=\"启用插件\"\n onChange={next => { onWrite('enabled', next) }}\n />\n <Toggle\n checked={config?.allowAgentGeneration ?? true}\n label=\"允许 Agent 生图\"\n onChange={next => { onWrite('allowAgentGeneration', next) }}\n />\n <Toggle\n checked={config?.announceToAgent ?? true}\n label=\"把插件与模型告知 Agent\"\n onChange={next => { onWrite('announceToAgent', next) }}\n />\n </div>\n )\n}\n","/**\n * Canvas limits shared by both halves.\n *\n * These live in their own module — with no imports at all — because the client\n * bundle and the host store must agree on them, while the host store is full of\n * Node built-ins. Importing a constant from `canvas-store.ts` into browser code\n * drags `node:crypto` and friends into the bundle, where the module loader\n * cannot resolve them and the whole client half fails to load. Keeping the\n * constants dependency-free is what makes the shared use safe.\n */\n\n/** Zoom range for a board. */\nexport const CANVAS_ZOOM_MIN = 0.1\nexport const CANVAS_ZOOM_MAX = 5\n\n/** Cards allowed on one board. */\nexport const CANVAS_MAX_CARDS = 500\n\n/** Boards kept before the oldest is dropped (with its file). */\nexport const CANVAS_MAX_BOARDS = 200\n\n/** Default size of a card placed on a board, in board pixels. */\nexport const CANVAS_NEW_IMAGE_SIZE = 320\n","/**\n * Canvas viewport math (pure functions, shared by the board and its tests).\n *\n * The board is spatial, not a node graph: the viewport is an ordinary\n * translate+scale pair, so every question about it (\"what is under the cursor\",\n * \"where does a card land if I click here\") is one linear map. Keeping that map\n * in one module means the pointer handling never re-derives it.\n *\n * Coordinate convention: a board card at `(x, y)` renders at\n * `(x * k + vx, y * k + vy)` in screen pixels.\n */\n\nimport type { CanvasCard, CanvasViewport } from './protocol.ts'\nimport { CANVAS_ZOOM_MAX, CANVAS_ZOOM_MIN } from './canvas-limits.ts'\n\n/** Zoom applied per wheel notch and per toolbar button press. */\nexport const ZOOM_STEP = 1.25\n\n/** Padding kept around the content by {@link fitViewport}. */\nexport const FIT_PADDING = 48\n\n/** Clamp a zoom factor into the supported range. */\nexport function clampZoom(k: number): number {\n if (!Number.isFinite(k)) return 1\n return Math.round(Math.min(CANVAS_ZOOM_MAX, Math.max(CANVAS_ZOOM_MIN, k)) * 1000) / 1000\n}\n\n/** Convert a screen point to board coordinates. */\nexport function screenToBoard(viewport: CanvasViewport, screenX: number, screenY: number): { x: number; y: number } {\n return {\n x: (screenX - viewport.x) / viewport.k,\n y: (screenY - viewport.y) / viewport.k,\n }\n}\n\n/** Convert a board point to screen coordinates. */\nexport function boardToScreen(viewport: CanvasViewport, boardX: number, boardY: number): { x: number; y: number } {\n return {\n x: boardX * viewport.k + viewport.x,\n y: boardY * viewport.k + viewport.y,\n }\n}\n\n/**\n * Zoom one step around a fixed screen anchor (the cursor, or the viewport\n * centre for a button press), so the board point under the anchor stays put.\n * @param direction - 1 zooms in, -1 zooms out.\n */\nexport function zoomAt(\n viewport: CanvasViewport,\n direction: 1 | -1,\n anchorX: number,\n anchorY: number,\n): CanvasViewport {\n const next = clampZoom(direction === 1 ? viewport.k * ZOOM_STEP : viewport.k / ZOOM_STEP)\n return zoomTo(viewport, next, anchorX, anchorY)\n}\n\n/** Zoom to an exact factor around a fixed screen anchor. */\nexport function zoomTo(\n viewport: CanvasViewport,\n targetK: number,\n anchorX: number,\n anchorY: number,\n): CanvasViewport {\n const k = clampZoom(targetK)\n if (k === viewport.k) return viewport\n const board = screenToBoard(viewport, anchorX, anchorY)\n return {\n x: anchorX - board.x * k,\n y: anchorY - board.y * k,\n k,\n }\n}\n\n/** Translate the viewport by a screen-space delta (dragging the board). */\nexport function panBy(viewport: CanvasViewport, deltaX: number, deltaY: number): CanvasViewport {\n return { x: viewport.x + deltaX, y: viewport.y + deltaY, k: viewport.k }\n}\n\n/** Bounding box of a set of cards, or undefined for an empty board. */\nexport function cardBounds(cards: CanvasCard[]): { x: number; y: number; width: number; height: number } | undefined {\n if (cards.length === 0) return undefined\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const card of cards) {\n minX = Math.min(minX, card.x)\n minY = Math.min(minY, card.y)\n maxX = Math.max(maxX, card.x + card.width)\n maxY = Math.max(maxY, card.y + card.height)\n }\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }\n}\n\n/**\n * Compute the viewport that shows every card with a little padding.\n * @param stageWidth - visible area width in screen pixels.\n * @param stageHeight - visible area height in screen pixels.\n * @returns the fitted viewport; the identity viewport for an empty board or a\n * degenerate stage.\n */\nexport function fitViewport(\n cards: CanvasCard[],\n stageWidth: number,\n stageHeight: number,\n padding = FIT_PADDING,\n): CanvasViewport {\n if (cards.length === 0 || stageWidth <= 0 || stageHeight <= 0) return { x: 0, y: 0, k: 1 }\n const bounds = cardBounds(cards)\n if (bounds === undefined || bounds.width <= 0 || bounds.height <= 0) return { x: 0, y: 0, k: 1 }\n const availableWidth = Math.max(1, stageWidth - 2 * padding)\n const availableHeight = Math.max(1, stageHeight - 2 * padding)\n const k = clampZoom(Math.min(availableWidth / bounds.width, availableHeight / bounds.height))\n return {\n x: Math.round((-bounds.x * k + (stageWidth - bounds.width * k) / 2) * 100) / 100,\n y: Math.round((-bounds.y * k + (stageHeight - bounds.height * k) / 2) * 100) / 100,\n k,\n }\n}\n\n/**\n * Grid cell size in screen pixels at the current zoom, chosen so the dot grid\n * always reads as roughly the same density however far the user zoomed.\n */\nexport function gridStep(k: number, base = 32): number {\n if (!Number.isFinite(k) || k <= 0) return base\n let step = base * k\n while (step < 12) step *= 4\n while (step > 160) step /= 4\n return step\n}\n","/**\n * Where a picked picture lands on a board.\n *\n * Kept outside the board component so the maths has one home: the picture picker\n * inside the board and the conversation's 「加到画布」 both ask where the visible\n * centre is, and both build the same card there. (Automatic placement of a fresh\n * generation is gone — a background generation no longer touches the board.)\n */\n\nimport { CANVAS_NEW_IMAGE_SIZE } from './canvas-limits.ts'\nimport { screenToBoard } from './canvas-viewport.ts'\nimport type { CanvasCard, CanvasCardOrigin, CanvasViewport } from './protocol.ts'\n\n/** One picture to place, as the library stores it. */\nexport interface PlacedImage {\n /** Library file name the card points at. */\n file: string\n width?: number | undefined\n height?: number | undefined\n /** Model that produced it, shown on the card. */\n model?: string | undefined\n /** Prompt snapshot, shown on the card. */\n prompt?: string | undefined\n /** Where it came from, shown as the card badge. */\n origin?: CanvasCardOrigin | undefined\n}\n\n/**\n * Build the card for one picture, centred on the given board point.\n * @param image - the picture to place.\n * @param centre - board coordinates of the stage centre.\n * @param id - the card id to use.\n * @returns the card, sized to the picture's own aspect ratio.\n */\nexport function centeredImageCard(image: PlacedImage, centre: { x: number; y: number }, id: string): CanvasCard {\n const aspect = image.width !== undefined && image.height !== undefined && image.height > 0\n ? image.width / image.height\n : 1\n const height = Math.round(CANVAS_NEW_IMAGE_SIZE / aspect)\n return {\n id,\n kind: 'image',\n x: Math.round(centre.x - CANVAS_NEW_IMAGE_SIZE / 2),\n y: Math.round(centre.y - height / 2),\n width: CANVAS_NEW_IMAGE_SIZE,\n height,\n z: 0,\n file: image.file,\n model: image.model ?? '',\n prompt: image.prompt ?? '',\n ...image.origin === undefined ? {} : { origin: image.origin },\n }\n}\n\n/**\n * Board coordinates of the visible centre.\n * @param viewport - the board's viewport.\n * @param stage - visible stage size in screen pixels.\n * @returns the board point at the middle of the stage.\n */\nexport function stageCentre(viewport: CanvasViewport, stage: { width: number; height: number }): { x: number; y: number } {\n return screenToBoard(viewport, stage.width / 2, stage.height / 2)\n}\n\n/**\n * Stage size assumed when nothing measurable is on screen yet.\n *\n * Halving a real stage puts a picture in the middle of what the user sees;\n * halving a ZERO stage puts it exactly on the viewport's top-left corner, which\n * is the \"why did it land in the corner\" bug. Assuming a plausible panel size\n * keeps it near the middle even before the canvas has been laid out.\n */\nexport const ASSUMED_STAGE: { readonly width: number; readonly height: number } = { width: 960, height: 640 }\n\n/**\n * The stage size to place against, falling back when nothing is measured.\n * @param measured - the last measurement (may be zero before layout).\n * @returns a usable size.\n */\nexport function usableStage(measured: { width: number; height: number }): { width: number; height: number } {\n return measured.width > 0 && measured.height > 0 ? measured : { ...ASSUMED_STAGE }\n}\n","/**\n * Annotation objects (the board's own marking layer).\n *\n * Three kinds share one session — brush strokes, rectangles, text — and the user\n * may mix them freely, which is why they are one list of objects rather than one\n * active tool's output (ADR-0016 in the reference client made the same call).\n * Everything is stored in the **picture's own pixel coordinates**, so burning is\n * a straight 1:1 replay onto a canvas of the natural size and a different display\n * scale can never move a mark.\n *\n * Geometry here is pure data + arithmetic (no DOM), so it is unit-tested; the\n * actual painting lives in `burn-image.ts`.\n */\n\n/** Which tool is drawing right now. */\nexport type AnnotationTool = 'brush' | 'rect' | 'arrow' | 'text' | 'eraser'\n\n/** One point, in the picture's own pixels. */\nexport interface AnnotationPoint {\n x: number\n y: number\n}\n\n/** A freehand stroke. */\nexport interface BrushAnnotation {\n kind: 'brush'\n color: string\n /** Stroke width in picture pixels. */\n sizePx: number\n points: AnnotationPoint[]\n}\n\n/** A rectangle outline: the \"circle this for the model\" mark. */\nexport interface RectAnnotation {\n kind: 'rect'\n color: string\n /** Outline width in picture pixels. */\n sizePx: number\n x: number\n y: number\n width: number\n height: number\n}\n\n/** A straight arrow: \"this one, in this direction\". */\nexport interface ArrowAnnotation {\n kind: 'arrow'\n color: string\n /** Shaft width in picture pixels (the head is sized from it). */\n sizePx: number\n from: AnnotationPoint\n to: AnnotationPoint\n}\n\n/** A text label anchored at its top-left corner. */\nexport interface TextAnnotation {\n kind: 'text'\n color: string\n /** Font size in picture pixels. */\n sizePx: number\n x: number\n y: number\n text: string\n}\n\n/** One annotation object. */\nexport type Annotation = BrushAnnotation | RectAnnotation | ArrowAnnotation | TextAnnotation\n\n/** The palette (three colours keeps the toolbar a row, not a picker). */\nexport const ANNOTATION_COLORS: readonly string[] = ['#ff3b30', '#ffcc00', '#0a84ff']\n\n/** The three widths, in editor pixels. */\nexport const ANNOTATION_WIDTHS: readonly number[] = [4, 8, 16]\n\n/** Smallest rectangle the user can draw (picture pixels). */\nexport const MIN_RECT_SIZE = 4\n\n/** How far from an object's edge still counts as a hit (picture pixels). */\nexport const HIT_TOLERANCE = 6\n\n/** Font family for text marks; must match what the burn uses. */\nexport const TEXT_FONT_FAMILY = 'system-ui, sans-serif'\n\n/** The bounding box of one object. */\nexport function annotationBounds(annotation: Annotation): { x: number; y: number; width: number; height: number } {\n if (annotation.kind === 'rect') return { x: annotation.x, y: annotation.y, width: annotation.width, height: annotation.height }\n if (annotation.kind === 'text') {\n // Height follows the font; width is left generous because the burn measures\n // the real text and the editor only needs the box for hit-testing.\n return { x: annotation.x, y: annotation.y, width: annotation.text.length * annotation.sizePx, height: annotation.sizePx * 1.25 }\n }\n if (annotation.kind === 'arrow') {\n const minX = Math.min(annotation.from.x, annotation.to.x)\n const minY = Math.min(annotation.from.y, annotation.to.y)\n const pad = annotation.sizePx * 2\n return {\n x: minX - pad,\n y: minY - pad,\n width: Math.abs(annotation.to.x - annotation.from.x) + pad * 2,\n height: Math.abs(annotation.to.y - annotation.from.y) + pad * 2,\n }\n }\n const xs = annotation.points.map(point => point.x)\n const ys = annotation.points.map(point => point.y)\n const minX = Math.min(...xs)\n const minY = Math.min(...ys)\n return {\n x: minX - annotation.sizePx / 2,\n y: minY - annotation.sizePx / 2,\n width: Math.max(...xs) - minX + annotation.sizePx,\n height: Math.max(...ys) - minY + annotation.sizePx,\n }\n}\n\n/** Move one object by a picture-pixel delta. */\nexport function moveAnnotation(annotation: Annotation, dx: number, dy: number): Annotation {\n if (annotation.kind === 'brush') {\n return { ...annotation, points: annotation.points.map(point => ({ x: point.x + dx, y: point.y + dy })) }\n }\n if (annotation.kind === 'arrow') {\n return {\n ...annotation,\n from: { x: annotation.from.x + dx, y: annotation.from.y + dy },\n to: { x: annotation.to.x + dx, y: annotation.to.y + dy },\n }\n }\n return { ...annotation, x: annotation.x + dx, y: annotation.y + dy }\n}\n\n/** Distance from a point to a segment, in picture pixels. */\nexport function distanceToSegment(point: AnnotationPoint, from: AnnotationPoint, to: AnnotationPoint): number {\n const dx = to.x - from.x\n const dy = to.y - from.y\n const lengthSquared = dx * dx + dy * dy\n if (lengthSquared === 0) return Math.hypot(point.x - from.x, point.y - from.y)\n // Projection of the point onto the segment, clamped to its ends.\n const t = Math.max(0, Math.min(1, ((point.x - from.x) * dx + (point.y - from.y) * dy) / lengthSquared))\n return Math.hypot(point.x - (from.x + t * dx), point.y - (from.y + t * dy))\n}\n\n/** Whether a point (picture pixels) lands on one object. */\nexport function hitAnnotation(annotation: Annotation, point: AnnotationPoint): boolean {\n if (annotation.kind === 'arrow') {\n // An arrow is mostly empty space: hit the shaft, not its bounding box.\n return distanceToSegment(point, annotation.from, annotation.to) <= annotation.sizePx * 1.5 + HIT_TOLERANCE\n }\n const bounds = annotationBounds(annotation)\n return point.x >= bounds.x - HIT_TOLERANCE\n && point.x <= bounds.x + bounds.width + HIT_TOLERANCE\n && point.y >= bounds.y - HIT_TOLERANCE\n && point.y <= bounds.y + bounds.height + HIT_TOLERANCE\n}\n\n/** The topmost object under a point, if any (later objects are on top). */\nexport function annotationAt(annotations: readonly Annotation[], point: AnnotationPoint): number | undefined {\n for (let index = annotations.length - 1; index >= 0; index -= 1) {\n if (hitAnnotation(annotations[index]!, point)) return index\n }\n return undefined\n}\n\n/** Every object under a point, topmost first (the eraser takes them all). */\nexport function annotationsAt(annotations: readonly Annotation[], point: AnnotationPoint): number[] {\n const hits: number[] = []\n for (let index = annotations.length - 1; index >= 0; index -= 1) {\n if (hitAnnotation(annotations[index]!, point)) hits.push(index)\n }\n return hits\n}\n\n/**\n * The head of an arrow, as a filled triangle in picture pixels.\n *\n * Shared by the editor's SVG and the burn so the mark the user sees is the mark\n * that gets painted.\n *\n * @param from - the tail.\n * @param to - the tip.\n * @param sizePx - the shaft width (the head scales from it).\n * @returns the three corners of the head, tip last.\n */\nexport function arrowHeadPoints(from: AnnotationPoint, to: AnnotationPoint, sizePx: number): [AnnotationPoint, AnnotationPoint, AnnotationPoint] {\n const angle = Math.atan2(to.y - from.y, to.x - from.x)\n const length = Math.max(sizePx * 3, 10)\n const half = Math.max(sizePx * 1.6, 6)\n const baseX = to.x - Math.cos(angle) * length\n const baseY = to.y - Math.sin(angle) * length\n const normalX = -Math.sin(angle) * half\n const normalY = Math.cos(angle) * half\n return [\n { x: baseX + normalX, y: baseY + normalY },\n { x: baseX - normalX, y: baseY - normalY },\n { x: to.x, y: to.y },\n ]\n}\n\n/**\n * Convert the editor's stroke widths into picture pixels.\n *\n * The user picks a width that LOOKS right on screen, so it has to be scaled by\n * how much the editor shrinks the picture; otherwise the same choice produces a\n * hairline on a 4K image and a slab on a thumbnail.\n *\n * @param sizePx - the width chosen in the editor.\n * @param scale - picture pixels per editor pixel (natural / displayed).\n * @returns the width to store on the object.\n */\nexport function scaleWidth(sizePx: number, scale: number): number {\n return Math.max(1, sizePx * (Number.isFinite(scale) && scale > 0 ? scale : 1))\n}\n\n/** Whether anything was drawn (an empty session must not produce a copy). */\nexport function hasAnnotations(annotations: readonly Annotation[]): boolean {\n return annotations.some(annotation => annotation.kind !== 'brush' || annotation.points.length > 0)\n && annotations.some(annotation => annotation.kind !== 'text' || annotation.text.trim() !== '')\n}","/**\n * Burning annotations and crops into a new picture (browser half).\n *\n * Both operations are the same shape: load the picture, replay something onto an\n * offscreen canvas of its natural size, hand back a PNG data URL. That URL is\n * what the host stores as a canvas asset — the original file is never touched and\n * nothing here goes near the material library.\n *\n * This is the one part of the feature that cannot be unit-tested in jsdom (no 2D\n * context), so it stays deliberately thin and arithmetic-free: all coordinates\n * arrive already in picture pixels from `annotations.ts` / `crop-rect.ts`.\n */\n\nimport { TEXT_FONT_FAMILY, arrowHeadPoints, type Annotation } from './annotations.ts'\nimport type { CropRect } from './crop-rect.ts'\n\n/** One burned picture: the PNG and the size it was burned at. */\nexport interface BurnedImage {\n dataUrl: string\n width: number\n height: number\n}\n\n/** Load one picture for the canvas to draw. */\nasync function loadImage(url: string): Promise<HTMLImageElement | undefined> {\n return new Promise(resolve => {\n const image = new Image()\n image.onload = () => { resolve(image) }\n image.onerror = () => { resolve(undefined) }\n image.src = url\n })\n}\n\n/**\n * Draw a picture onto an offscreen canvas and export it as a PNG data URL.\n *\n * @param url - where the picture is served from.\n * @param paint - draws on top of the picture (receives the 2D context and size).\n * @param source - sub-rectangle to take instead of the whole picture (a crop).\n * @returns the burned picture, or undefined when it could not be loaded.\n */\nasync function burn(\n url: string,\n paint: (context: CanvasRenderingContext2D, width: number, height: number) => void,\n source?: CropRect,\n): Promise<BurnedImage | undefined> {\n const image = await loadImage(url)\n if (image === undefined) return undefined\n const naturalWidth = image.naturalWidth === 0 ? image.width : image.naturalWidth\n const naturalHeight = image.naturalHeight === 0 ? image.height : image.naturalHeight\n const rect = source ?? { x: 0, y: 0, width: naturalWidth, height: naturalHeight }\n const canvas = document.createElement('canvas')\n canvas.width = Math.max(1, Math.round(rect.width))\n canvas.height = Math.max(1, Math.round(rect.height))\n const context = canvas.getContext('2d')\n if (context === null) return undefined\n context.drawImage(\n image,\n rect.x, rect.y, rect.width, rect.height,\n 0, 0, canvas.width, canvas.height,\n )\n paint(context, canvas.width, canvas.height)\n return { dataUrl: canvas.toDataURL('image/png'), width: canvas.width, height: canvas.height }\n}\n\n/**\n * Burn every annotation onto a copy of the picture, in list order.\n *\n * @param url - where the picture is served from.\n * @param annotations - the marks, in picture pixels.\n * @returns the composite, or undefined when the picture could not be loaded.\n */\nexport async function burnAnnotations(url: string, annotations: readonly Annotation[]): Promise<BurnedImage | undefined> {\n return burn(url, (context, width, height) => {\n for (const annotation of annotations) {\n if (annotation.kind === 'rect') {\n // Inset by half the line so the outline lands fully inside the picture.\n const inset = annotation.sizePx / 2\n context.strokeStyle = annotation.color\n context.lineWidth = annotation.sizePx\n context.strokeRect(\n annotation.x + inset,\n annotation.y + inset,\n Math.max(1, annotation.width - annotation.sizePx),\n Math.max(1, annotation.height - annotation.sizePx),\n )\n continue\n }\n if (annotation.kind === 'brush') {\n if (annotation.points.length === 0) continue\n context.strokeStyle = annotation.color\n context.fillStyle = annotation.color\n context.lineWidth = annotation.sizePx\n context.lineCap = 'round'\n context.lineJoin = 'round'\n const [first, ...rest] = annotation.points\n if (rest.length === 0) {\n // A tap with no drag is a dot, not an invisible stroke.\n context.beginPath()\n context.arc(first!.x, first!.y, annotation.sizePx / 2, 0, Math.PI * 2)\n context.fill()\n continue\n }\n context.beginPath()\n context.moveTo(first!.x, first!.y)\n for (const point of rest) context.lineTo(point.x, point.y)\n context.stroke()\n continue\n }\n if (annotation.kind === 'arrow') {\n // Shaft plus a filled head, from the same helper the editor draws with.\n const [left, right, tip] = arrowHeadPoints(annotation.from, annotation.to, annotation.sizePx)\n context.strokeStyle = annotation.color\n context.fillStyle = annotation.color\n context.lineWidth = annotation.sizePx\n context.lineCap = 'round'\n context.beginPath()\n context.moveTo(annotation.from.x, annotation.from.y)\n context.lineTo(annotation.to.x, annotation.to.y)\n context.stroke()\n context.beginPath()\n context.moveTo(left.x, left.y)\n context.lineTo(right.x, right.y)\n context.lineTo(tip.x, tip.y)\n context.closePath()\n context.fill()\n continue\n }\n if (annotation.text.trim() === '') continue\n context.fillStyle = annotation.color\n // `top` baseline matches the editor's top-left anchored label, so what the\n // user placed is where it lands.\n context.textBaseline = 'top'\n context.font = `${annotation.sizePx}px ${TEXT_FONT_FAMILY}`\n context.fillText(annotation.text, annotation.x, annotation.y)\n }\n void width\n void height\n })\n}\n\n/**\n * Cut a sub-rectangle out of the picture.\n *\n * @param url - where the picture is served from.\n * @param rect - the crop box in picture pixels.\n * @returns the cropped picture, or undefined when it could not be loaded.\n */\nexport async function burnCrop(url: string, rect: CropRect): Promise<BurnedImage | undefined> {\n return burn(url, () => {}, rect)\n}\n","/**\r\n * The annotation editor: mark the picture, then save the marks as one composite.\r\n *\r\n * Opened from the board's floating command bar. The picture is shown fitted to the\r\n * panel and the marks are drawn as an SVG overlay with the picture's own coordinate\r\n * system as its viewBox — so the display scale is the browser's problem, and every\r\n * object is stored in natural pixels. Saving burns them onto a copy (see\r\n * `burn-image.ts`) and hands the board a NEW node: the original picture, and the\r\n * material library, are untouched.\r\n *\r\n * Three tools mix in one session (`annotations.ts`): brush strokes, rectangles\r\n * (drag to draw), and text (click, type, Enter). Undo/redo is object-level —\r\n * see the reference client's ADR-0016, which this follows.\r\n */\r\n\r\nimport { useEffect, useMemo, useRef, useState, type PointerEvent as ReactPointerEvent } from 'react'\r\nimport {\r\n ANNOTATION_COLORS,\r\n ANNOTATION_WIDTHS,\r\n annotationAt,\r\n annotationsAt,\r\n arrowHeadPoints,\r\n hasAnnotations,\r\n moveAnnotation,\r\n scaleWidth,\r\n type Annotation,\r\n type AnnotationPoint,\r\n type AnnotationTool,\r\n} from './annotations.ts'\r\nimport { burnAnnotations } from './burn-image.ts'\r\nimport { Button, TextInput } from './controls.tsx'\r\nimport css from './annotation-editor.module.css'\r\n\r\n/** What the editor needs to know about the picture it is marking. */\r\nexport interface AnnotationEditorProps {\r\n /** Where the picture is served from. */\r\n url: string\r\n /** Its natural size, when known (the editor reads it from the image otherwise). */\r\n natural?: { width: number; height: number } | undefined\r\n /** Called with the burned composite when the user saves. */\r\n onSave: (dataUrl: string) => void | Promise<void>\r\n /** Leave without saving. */\r\n onCancel: () => void\r\n}\r\n\r\n/** One drag in progress (brush stroke, rectangle, arrow, eraser, or moving an object). */\r\ntype Drag =\r\n | { kind: 'brush'; points: AnnotationPoint[] }\r\n | { kind: 'rect'; start: AnnotationPoint; current: AnnotationPoint }\r\n | { kind: 'arrow'; start: AnnotationPoint; current: AnnotationPoint }\r\n | { kind: 'erase' }\r\n | { kind: 'move'; index: number; last: AnnotationPoint; moved: boolean }\r\n\r\n/**\r\n * The editor overlay.\r\n * @param props - the picture and what to do when the user saves or cancels.\r\n * @returns the editor.\r\n */\r\nexport function AnnotationEditor(props: AnnotationEditorProps): JSX.Element {\r\n const [tool, setTool] = useState<AnnotationTool>('brush')\r\n const [color, setColor] = useState(ANNOTATION_COLORS[0]!)\r\n const [width, setWidth] = useState(ANNOTATION_WIDTHS[1]!)\r\n const [objects, setObjects] = useState<Annotation[]>([])\r\n const [selected, setSelected] = useState<number | undefined>(undefined)\r\n const [draft, setDraft] = useState<Annotation | undefined>(undefined)\r\n const [text, setText] = useState<{ point: AnnotationPoint; value: string } | undefined>(undefined)\r\n const [natural, setNatural] = useState(props.natural ?? { width: 1024, height: 1024 })\r\n const [saving, setSaving] = useState(false)\r\n // Object-level history: every committed change pushes the previous list.\r\n const [past, setPast] = useState<Annotation[][]>([])\r\n const [future, setFuture] = useState<Annotation[][]>([])\r\n const surfaceRef = useRef<SVGSVGElement | null>(null)\r\n const dragRef = useRef<Drag | undefined>(undefined)\r\n /**\r\n * The in-progress object, kept in a ref as well as in state.\r\n *\r\n * The pointer-up handler has to see what the moves produced, and a gesture can\r\n * deliver down/move/up inside one task (a fast drag, or a scripted one) — a\r\n * state closure would still hold the pre-gesture value and drop the object.\r\n */\r\n const draftRef = useRef<Annotation | undefined>(undefined)\r\n /**\r\n * Where the text tool was pressed, waiting for the release.\r\n *\r\n * The text box is deliberately created on pointer UP, not on pointer down: the\r\n * browser moves focus on `mousedown`, which lands right after `pointerdown`. A\r\n * box created on the press is therefore mounted and then blurred inside the very\r\n * same click — the input's `autoFocus` never survives and nobody can type. By\r\n * release time that focus shift is over and the box keeps the caret.\r\n */\r\n const textPointRef = useRef<AnnotationPoint | undefined>(undefined)\r\n /**\r\n * Whether the press in flight is the one that confirmed an open text box.\r\n *\r\n * Confirming and arming happen in two different React listeners — capture, then\r\n * bubble — and React re-renders in between, so the bubble handler sees a fresh\r\n * world where no box is open. Without this flag the same press would confirm one\r\n * label and immediately open the next box.\r\n */\r\n const textPressUsedRef = useRef(false)\r\n const commitDraft = (next: Annotation | undefined): void => {\r\n draftRef.current = next\r\n setDraft(next)\r\n }\r\n\r\n // The picture's real size decides the coordinate system, so read it from the\r\n // element rather than trusting whatever the caller happened to know.\r\n useEffect(() => {\r\n const image = new Image()\r\n image.onload = () => {\r\n if (image.naturalWidth > 0 && image.naturalHeight > 0) {\r\n setNatural({ width: image.naturalWidth, height: image.naturalHeight })\r\n }\r\n }\r\n image.src = props.url\r\n }, [props.url])\r\n\r\n const commit = (next: Annotation[]): void => {\r\n setPast(history => [...history, objects])\r\n setFuture([])\r\n setObjects(next)\r\n setSelected(undefined)\r\n }\r\n\r\n const undo = (): void => {\r\n if (past.length === 0) return\r\n const previous = past[past.length - 1]!\r\n setPast(history => history.slice(0, -1))\r\n setFuture(stack => [objects, ...stack])\r\n setObjects(previous)\r\n setSelected(undefined)\r\n }\r\n\r\n const redo = (): void => {\r\n if (future.length === 0) return\r\n const next = future[0]!\r\n setFuture(stack => stack.slice(1))\r\n setPast(history => [...history, objects])\r\n setObjects(next)\r\n setSelected(undefined)\r\n }\r\n\r\n useEffect(() => {\r\n const onKey = (event: KeyboardEvent): void => {\r\n // While the text box is open it owns the keyboard: Esc there cancels the\r\n // typing (handled by the input itself), NOT the whole editor. Closing the\r\n // editor on Esc is exactly the trap this guard exists to avoid. The branch\r\n // below also covers the box being open but unfocused (the user tabbed away,\r\n // or the window lost focus), where the input never sees the key at all.\r\n if (text !== undefined) {\r\n if (event.key === 'Escape') setText(undefined)\r\n return\r\n }\r\n if (event.key === 'Escape') props.onCancel()\r\n if (event.key === 'z' && (event.ctrlKey || event.metaKey)) {\r\n event.preventDefault()\r\n if (event.shiftKey) redo()\r\n else undo()\r\n }\r\n if ((event.key === 'Delete' || event.key === 'Backspace') && selected !== undefined) {\r\n event.preventDefault()\r\n commit(objects.filter((_object, index) => index !== selected))\r\n }\r\n }\r\n document.addEventListener('keydown', onKey)\r\n return () => { document.removeEventListener('keydown', onKey) }\r\n })\r\n\r\n /** Pointer position in picture pixels. */\r\n const toPicture = (event: ReactPointerEvent): AnnotationPoint => {\r\n const surface = surfaceRef.current\r\n if (surface === null) return { x: 0, y: 0 }\r\n const rect = surface.getBoundingClientRect()\r\n const scaleX = rect.width === 0 ? 1 : natural.width / rect.width\r\n const scaleY = rect.height === 0 ? 1 : natural.height / rect.height\r\n return {\r\n x: Math.min(natural.width, Math.max(0, (event.clientX - rect.left) * scaleX)),\r\n y: Math.min(natural.height, Math.max(0, (event.clientY - rect.top) * scaleY)),\r\n }\r\n }\r\n\r\n /** How much the editor shrinks the picture (for converting stroke widths). */\r\n const editorScale = (): number => {\r\n const surface = surfaceRef.current\r\n if (surface === null) return 1\r\n const rect = surface.getBoundingClientRect()\r\n return rect.width === 0 ? 1 : natural.width / rect.width\r\n }\r\n\r\n const onPointerDown = (event: ReactPointerEvent): void => {\r\n const point = toPicture(event)\r\n if (textPressUsedRef.current) {\r\n // This press only confirmed the previous label; it must not arm another one.\r\n textPressUsedRef.current = false\r\n return\r\n }\r\n if (text !== undefined) return\r\n if (tool === 'text') {\r\n // Only remembered here; the box is opened on release (see textPointRef).\r\n textPointRef.current = point\r\n return\r\n }\r\n if (tool === 'eraser') {\r\n // Object eraser: whatever the pointer touches disappears, and undo brings\r\n // it back. (An object list has no pixels to rub out.)\r\n dragRef.current = { kind: 'erase' }\r\n eraseAt(point)\r\n return\r\n }\r\n if (tool === 'brush') {\r\n dragRef.current = { kind: 'brush', points: [point] }\r\n commitDraft({ kind: 'brush', color, sizePx: scaleWidth(width, editorScale()), points: [point] })\r\n return\r\n }\r\n if (tool === 'arrow') {\r\n dragRef.current = { kind: 'arrow', start: point, current: point }\r\n setSelected(undefined)\r\n commitDraft({ kind: 'arrow', color, sizePx: scaleWidth(width, editorScale()), from: point, to: point })\r\n return\r\n }\r\n // Rect tool: a drag on an existing object moves it instead of drawing a new\r\n // one, which is what \"click the object and drag it\" means to a user.\r\n const hit = annotationAt(objects, point)\r\n if (hit !== undefined) {\r\n dragRef.current = { kind: 'move', index: hit, last: point, moved: false }\r\n setSelected(hit)\r\n return\r\n }\r\n dragRef.current = { kind: 'rect', start: point, current: point }\r\n setSelected(undefined)\r\n commitDraft({ kind: 'rect', color, sizePx: scaleWidth(width, editorScale()), x: point.x, y: point.y, width: 0, height: 0 })\r\n }\r\n\r\n /** Remove every object under one point, as one undoable step. */\r\n const eraseAt = (point: AnnotationPoint): void => {\r\n const hits = annotationsAt(objects, point)\r\n if (hits.length === 0) return\r\n commit(objects.filter((_object, index) => !hits.includes(index)))\r\n }\r\n\r\n const onPointerMove = (event: ReactPointerEvent): void => {\r\n const drag = dragRef.current\r\n if (drag === undefined) return\r\n const point = toPicture(event)\r\n if (drag.kind === 'erase') {\r\n eraseAt(point)\r\n return\r\n }\r\n if (drag.kind === 'brush') {\r\n drag.points.push(point)\r\n const current = draftRef.current\r\n if (current?.kind === 'brush') commitDraft({ ...current, points: [...drag.points] })\r\n return\r\n }\r\n if (drag.kind === 'rect') {\r\n drag.current = point\r\n commitDraft({\r\n kind: 'rect',\r\n color,\r\n sizePx: scaleWidth(width, editorScale()),\r\n x: Math.min(drag.start.x, point.x),\r\n y: Math.min(drag.start.y, point.y),\r\n width: Math.abs(point.x - drag.start.x),\r\n height: Math.abs(point.y - drag.start.y),\r\n })\r\n return\r\n }\r\n if (drag.kind === 'arrow') {\r\n drag.current = point\r\n commitDraft({ kind: 'arrow', color, sizePx: scaleWidth(width, editorScale()), from: drag.start, to: point })\r\n return\r\n }\r\n const dx = point.x - drag.last.x\r\n const dy = point.y - drag.last.y\r\n if (dx === 0 && dy === 0) return\r\n drag.last = point\r\n drag.moved = true\r\n setObjects(current => current.map((object, index) => index === drag.index ? moveAnnotation(object, dx, dy) : object))\r\n }\r\n\r\n const onPointerUp = (): void => {\r\n if (tool === 'text') {\r\n const point = textPointRef.current\r\n textPointRef.current = undefined\r\n if (point !== undefined) setText({ point, value: '' })\r\n return\r\n }\r\n const drag = dragRef.current\r\n dragRef.current = undefined\r\n if (drag === undefined) return\r\n if (drag.kind === 'move') {\r\n // A move is already applied to the list; only a real move is undoable.\r\n if (drag.moved) {\r\n setFuture([])\r\n setPast(history => [...history, objects])\r\n }\r\n return\r\n }\r\n const pending = draftRef.current\r\n commitDraft(undefined)\r\n if (pending === undefined) return\r\n // A rectangle or arrow that was never dragged is not a mark.\r\n if (pending.kind === 'rect' && (pending.width < 4 || pending.height < 4)) return\r\n if (pending.kind === 'arrow' && Math.hypot(pending.to.x - pending.from.x, pending.to.y - pending.from.y) < 6) return\r\n commit([...objects, pending])\r\n }\r\n\r\n const commitText = (): void => {\r\n const pending = text\r\n setText(undefined)\r\n if (pending === undefined || pending.value.trim() === '') return\r\n commit([...objects, {\r\n kind: 'text',\r\n color,\r\n sizePx: scaleWidth(width, editorScale()),\r\n x: pending.point.x,\r\n y: pending.point.y,\r\n text: pending.value.trim(),\r\n }])\r\n }\r\n\r\n const visible = useMemo(() => (draft === undefined ? objects : [...objects, draft]), [draft, objects])\r\n\r\n /**\r\n * Pressing anywhere outside the text box finishes the label.\r\n *\r\n * This replaces an `onBlur` commit, which could not survive a real click: the\r\n * browser's own `mousedown` focus handling blurred a freshly mounted box and the\r\n * handler threw the (still empty) typing away. Capture phase, so the press that\r\n * closes the box cannot also start a new one.\r\n */\r\n const onOverlayPointerDown = (event: ReactPointerEvent): void => {\r\n textPressUsedRef.current = false\r\n if (text === undefined) return\r\n const target = event.target\r\n if (target instanceof Element && target.closest('[data-dsh-seework-annotation-text]') !== null) return\r\n textPressUsedRef.current = true\r\n commitText()\r\n }\r\n\r\n return (\r\n <div\r\n className={css.overlay}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-label=\"标注编辑器\"\r\n onPointerDownCapture={onOverlayPointerDown}\r\n >\r\n <div className={css.panel}>\r\n <header className={css.toolbar} data-dsh-seework-annotation-toolbar=\"\">\r\n {([\r\n ['brush', '画笔'],\r\n ['rect', '矩形'],\r\n ['arrow', '箭头'],\r\n ['text', '文字'],\r\n ['eraser', '橡皮擦'],\r\n ] as Array<[AnnotationTool, string]>).map(([value, label]) => (\r\n <button\r\n key={value}\r\n type=\"button\"\r\n className={css.tool}\r\n data-active={tool === value ? 'true' : undefined}\r\n data-tool={value}\r\n onClick={() => { setTool(value) }}\r\n >\r\n {label}\r\n </button>\r\n ))}\r\n <span className={css.gap} />\r\n {ANNOTATION_COLORS.map(swatch => (\r\n <button\r\n key={swatch}\r\n type=\"button\"\r\n className={css.swatch}\r\n style={{ background: swatch }}\r\n title={`颜色 ${swatch}`}\r\n aria-label={`颜色 ${swatch}`}\r\n data-active={color === swatch ? 'true' : undefined}\r\n onClick={() => { setColor(swatch) }}\r\n />\r\n ))}\r\n <span className={css.gap} />\r\n {ANNOTATION_WIDTHS.map(size => (\r\n <button\r\n key={size}\r\n type=\"button\"\r\n className={css.tool}\r\n data-active={width === size ? 'true' : undefined}\r\n title={`粗细 ${size}`}\r\n onClick={() => { setWidth(size) }}\r\n >\r\n {size}\r\n </button>\r\n ))}\r\n <span className={css.gap} />\r\n <Button onClick={undo} disabled={past.length === 0} title=\"撤销(Ctrl+Z)\">撤销</Button>\r\n <Button onClick={redo} disabled={future.length === 0} title=\"重做(Ctrl+Shift+Z)\">重做</Button>\r\n <Button onClick={props.onCancel}>取消</Button>\r\n <Button\r\n variant=\"primary\"\r\n disabled={saving || !hasAnnotations(objects)}\r\n data-dsh-seework-annotation-save=\"\"\r\n onClick={() => {\r\n setSaving(true)\r\n void burnAnnotations(props.url, objects)\r\n .then(burned => burned === undefined ? undefined : props.onSave(burned.dataUrl))\r\n .finally(() => { setSaving(false) })\r\n }}\r\n >\r\n {saving ? '保存中…' : '保存为合成图'}\r\n </Button>\r\n </header>\r\n\r\n <div className={css.stage}>\r\n <div className={css.frame}>\r\n <img className={css.picture} src={props.url} alt=\"待标注的图片\" draggable={false} />\r\n <svg\r\n ref={surfaceRef}\r\n className={css.surface}\r\n viewBox={`0 0 ${natural.width} ${natural.height}`}\r\n preserveAspectRatio=\"none\"\r\n onPointerDown={onPointerDown}\r\n onPointerMove={onPointerMove}\r\n onPointerUp={onPointerUp}\r\n onPointerLeave={onPointerUp}\r\n >\r\n {visible.map((object, index) => <AnnotationShape key={index} annotation={object} />)}\r\n </svg>\r\n {text === undefined\r\n ? null\r\n : (\r\n <div\r\n className={css.textEntry}\r\n data-dsh-seework-annotation-text=\"\"\r\n style={{\r\n left: `${(text.point.x / natural.width) * 100}%`,\r\n top: `${(text.point.y / natural.height) * 100}%`,\r\n }}\r\n >\r\n <TextInput\r\n autoFocus\r\n value={text.value}\r\n placeholder=\"输入文字,回车确认,Esc 取消\"\r\n onChange={event => { setText(current => current === undefined ? current : { ...current, value: event.target.value }) }}\r\n onKeyDown={event => {\r\n if (event.key === 'Enter') commitText()\r\n if (event.key === 'Escape') {\r\n // Cancel the typing only: the editor stays open, and the\r\n // document-level Escape handler is told to stand down.\r\n event.stopPropagation()\r\n event.preventDefault()\r\n setText(undefined)\r\n }\r\n }}\r\n />\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n <p className={css.hint}>\r\n 五种标注可以混用:画笔涂抹、矩形框选、箭头指向、文字说明、橡皮擦按对象擦除(都可撤销)。保存会生成一张合成图作为**新**卡片,原图与素材库都不动。\r\n 文字框里回车确认、Esc 取消输入(不会关掉编辑器)、点框外即确认。\r\n {selected === undefined ? '' : ' 已选中一个标注:拖动可移动,Delete 删除。'}\r\n </p>\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\n/** One annotation drawn into the SVG overlay. */\r\nfunction AnnotationShape({ annotation }: { annotation: Annotation }): JSX.Element {\r\n if (annotation.kind === 'rect') {\r\n return (\r\n <rect\r\n x={annotation.x}\r\n y={annotation.y}\r\n width={annotation.width}\r\n height={annotation.height}\r\n fill=\"none\"\r\n stroke={annotation.color}\r\n strokeWidth={annotation.sizePx}\r\n />\r\n )\r\n }\r\n if (annotation.kind === 'text') {\r\n return (\r\n <text x={annotation.x} y={annotation.y} fill={annotation.color} fontSize={annotation.sizePx} dominantBaseline=\"hanging\">\r\n {annotation.text}\r\n </text>\r\n )\r\n }\r\n if (annotation.kind === 'arrow') {\r\n const [left, right, tip] = arrowHeadPoints(annotation.from, annotation.to, annotation.sizePx)\r\n return (\r\n <g>\r\n <line\r\n x1={annotation.from.x}\r\n y1={annotation.from.y}\r\n x2={annotation.to.x}\r\n y2={annotation.to.y}\r\n stroke={annotation.color}\r\n strokeWidth={annotation.sizePx}\r\n strokeLinecap=\"round\"\r\n />\r\n <polygon\r\n points={`${left.x},${left.y} ${right.x},${right.y} ${tip.x},${tip.y}`}\r\n fill={annotation.color}\r\n />\r\n </g>\r\n )\r\n }\r\n return (\r\n <polyline\r\n points={annotation.points.map(point => `${point.x},${point.y}`).join(' ')}\r\n fill=\"none\"\r\n stroke={annotation.color}\r\n strokeWidth={annotation.sizePx}\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n />\r\n )\r\n}\r\n","/**\n * Crop geometry, in the picture's own pixels.\n *\n * The crop box lives in natural coordinates and is rendered by scaling it to the\n * node's displayed size, so zooming the board never changes what will be cut —\n * the same three-layer mapping (natural ↔ node ↔ screen) the reference client\n * uses. Pure arithmetic, unit-tested; the actual cutting lives in `burn-image.ts`.\n */\n\n/** One crop box, in picture pixels. */\nexport interface CropRect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** One length/ratio choice in the crop toolbar. */\nexport interface CropAspectOption {\n label: string\n /** width / height, or null for a free box. */\n value: number | null\n}\n\n/** The ratio menu, mirroring the reference client's list. */\nexport const CROP_ASPECTS: readonly CropAspectOption[] = [\n { label: '自定义', value: null },\n { label: '1:1', value: 1 },\n { label: '4:3', value: 4 / 3 },\n { label: '3:4', value: 3 / 4 },\n { label: '16:9', value: 16 / 9 },\n { label: '9:16', value: 9 / 16 },\n]\n\n/** Smallest crop box (picture pixels), so a stray drag cannot produce nothing. */\nexport const MIN_CROP_SIZE = 16\n\n/** The eight resize handles. */\nexport type CropHandle = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w'\n\n/** All handles, in outline order. */\nexport const CROP_HANDLES: readonly CropHandle[] = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']\n\n/** Which directions a handle moves. */\nconst HANDLE_AXES: Record<CropHandle, { x: -1 | 0 | 1; y: -1 | 0 | 1 }> = {\n nw: { x: -1, y: -1 },\n n: { x: 0, y: -1 },\n ne: { x: 1, y: -1 },\n e: { x: 1, y: 0 },\n se: { x: 1, y: 1 },\n s: { x: 0, y: 1 },\n sw: { x: -1, y: 1 },\n w: { x: -1, y: 0 },\n}\n\n/** Keep a box inside the picture. */\nfunction clampRect(rect: CropRect, natural: { width: number; height: number }): CropRect {\n const width = Math.min(Math.max(MIN_CROP_SIZE, rect.width), natural.width)\n const height = Math.min(Math.max(MIN_CROP_SIZE, rect.height), natural.height)\n return {\n width,\n height,\n x: Math.min(Math.max(0, rect.x), Math.max(0, natural.width - width)),\n y: Math.min(Math.max(0, rect.y), Math.max(0, natural.height - height)),\n }\n}\n\n/**\n * The box a crop mode starts with: the whole picture, or the largest box of the\n * requested ratio that fits in it.\n *\n * @param natural - the picture's natural size.\n * @param aspect - width / height, or null for the whole picture.\n * @returns the starting box.\n */\nexport function defaultCropRect(natural: { width: number; height: number }, aspect: number | null): CropRect {\n if (aspect === null) return { x: 0, y: 0, width: natural.width, height: natural.height }\n const byWidth = { width: natural.width, height: natural.width / aspect }\n const size = byWidth.height <= natural.height ? byWidth : { width: natural.height * aspect, height: natural.height }\n return clampRect({\n x: (natural.width - size.width) / 2,\n y: (natural.height - size.height) / 2,\n width: size.width,\n height: size.height,\n }, natural)\n}\n\n/**\n * Move the box, keeping it inside the picture.\n * @param rect - the current box.\n * @param dx - pointer delta in picture pixels.\n * @param dy - pointer delta in picture pixels.\n * @param natural - the picture's natural size.\n * @returns the moved box.\n */\nexport function moveCropRect(rect: CropRect, dx: number, dy: number, natural: { width: number; height: number }): CropRect {\n return clampRect({ ...rect, x: rect.x + dx, y: rect.y + dy }, natural)\n}\n\n/**\n * Resize the box by one handle.\n *\n * The anchor (the opposite edge/corner) stays put, which is what makes dragging a\n * handle feel like resizing rather than moving; with a fixed ratio the box grows\n * along the dominant axis and stays inside the picture.\n *\n * @param rect - the current box.\n * @param handle - which handle is being dragged.\n * @param dx - pointer delta in picture pixels.\n * @param dy - pointer delta in picture pixels.\n * @param natural - the picture's natural size.\n * @param aspect - width / height, or null for a free box.\n * @returns the resized box.\n */\nexport function resizeCropRect(\n rect: CropRect,\n handle: CropHandle,\n dx: number,\n dy: number,\n natural: { width: number; height: number },\n aspect: number | null,\n): CropRect {\n const axes = HANDLE_AXES[handle]\n let width = axes.x === 0 ? rect.width : rect.width + dx * axes.x\n let height = axes.y === 0 ? rect.height : rect.height + dy * axes.y\n if (aspect !== null) {\n // Grow along whichever axis moved more, then derive the other from the ratio.\n const byWidth = { width, height: width / aspect }\n const byHeight = { width: height * aspect, height }\n const chosen = Math.abs(dx) >= Math.abs(dy) ? byWidth : byHeight\n width = chosen.width\n height = chosen.height\n }\n width = Math.max(MIN_CROP_SIZE, width)\n height = Math.max(MIN_CROP_SIZE, height)\n // The anchor is the edge the dragged handle is not touching.\n const x = axes.x === 0 ? rect.x : axes.x < 0 ? rect.x + rect.width - width : rect.x\n const y = axes.y === 0 ? rect.y : axes.y < 0 ? rect.y + rect.height - height : rect.y\n return clampRect({ x, y, width, height }, natural)\n}\n\n/**\n * The displayed position of the box inside a node.\n *\n * @param rect - the box in picture pixels.\n * @param node - the node's displayed size on screen.\n * @param natural - the picture's natural size.\n * @returns screen-space offsets relative to the node's top-left corner.\n */\nexport function cropRectToNode(\n rect: CropRect,\n node: { width: number; height: number },\n natural: { width: number; height: number },\n): CropRect {\n const scaleX = natural.width === 0 ? 1 : node.width / natural.width\n const scaleY = natural.height === 0 ? 1 : node.height / natural.height\n return {\n x: rect.x * scaleX,\n y: rect.y * scaleY,\n width: rect.width * scaleX,\n height: rect.height * scaleY,\n }\n}\n\n/**\n * Where a crop's new node goes: to the right of the original, same top edge.\n *\n * Non-destructive by construction — the crop is a NEW node next to the picture it\n * came from, so the user can compare them.\n *\n * @param source - the original card's board rectangle.\n * @param size - the new node's size.\n * @returns the new node's top-left corner.\n */\nexport function cropNodePosition(\n source: { x: number; y: number; width: number },\n size: { width: number },\n): { x: number; y: number } {\n return { x: source.x + source.width + 24, y: source.y }\n}\n\n/**\n * The displayed size of a crop's node: the crop's own aspect ratio, at a size\n * comparable to the picture it came from.\n *\n * @param rect - the crop box in picture pixels.\n * @param maxWidth - the widest the new node may be on the board.\n * @returns the node's board size.\n */\nexport function croppedNodeSize(rect: CropRect, maxWidth = 320): { width: number; height: number } {\n const aspect = rect.height === 0 ? 1 : rect.width / rect.height\n const width = Math.max(48, Math.min(maxWidth, rect.width))\n return { width: Math.round(width), height: Math.round(width / aspect) }\n}\n","/**\n * In-place crop mode for one board node.\n *\n * The crop box is drawn over the node itself (not in a dialog), because cropping\n * is a local framing decision and a modal would break the board context — the\n * reference client's ADR-0016 moved it here for exactly that reason. The box is\n * stored in the picture's own pixels and rendered through `crop-rect`'s mapping,\n * so nothing here depends on the board's zoom.\n *\n * Confirming produces a NEW node (`onConfirm` receives the box); the original card\n * and the material library are untouched.\n */\n\nimport { useRef, useState } from 'react'\nimport {\n CROP_ASPECTS,\n CROP_HANDLES,\n cropRectToNode,\n defaultCropRect,\n moveCropRect,\n resizeCropRect,\n type CropHandle,\n type CropRect,\n} from './crop-rect.ts'\nimport { Button } from './controls.tsx'\nimport css from './crop-overlay.module.css'\n\n/** What the crop overlay needs to know about the node it sits on. */\nexport interface CropOverlayProps {\n /** The node's rectangle on the board (board coordinates). */\n node: { x: number; y: number; width: number; height: number }\n /** The picture's natural size, for the three-layer mapping. */\n natural: { width: number; height: number }\n /** Crop the picture with this box (picture pixels). */\n onConfirm: (rect: CropRect) => void\n /** Leave crop mode without producing anything. */\n onCancel: () => void\n}\n\n/** Which direction each handle drags, for the cursor only. */\nconst HANDLE_CURSORS: Record<CropHandle, string> = {\n nw: 'nwse-resize',\n n: 'ns-resize',\n ne: 'nesw-resize',\n e: 'ew-resize',\n se: 'nwse-resize',\n s: 'ns-resize',\n sw: 'nesw-resize',\n w: 'ew-resize',\n}\n\n/**\n * The crop box and its toolbar for one node.\n * @param props - the node, the picture size, and what to do on confirm/cancel.\n * @returns the overlay.\n */\nexport function CropOverlay(props: CropOverlayProps): JSX.Element {\n const [aspect, setAspect] = useState<number | null>(null)\n const [rect, setRect] = useState<CropRect>(() => defaultCropRect(props.natural, null))\n const dragRef = useRef<{ mode: 'move' | CropHandle; startX: number; startY: number; startRect: CropRect } | undefined>(undefined)\n\n /** Picture pixels per screen pixel at the current board scale. */\n const scale = props.node.width === 0 ? 1 : props.natural.width / props.node.width\n const displayed = cropRectToNode(rect, props.node, props.natural)\n\n const beginDrag = (event: React.PointerEvent, mode: 'move' | CropHandle): void => {\n event.preventDefault()\n event.stopPropagation()\n dragRef.current = { mode, startX: event.clientX, startY: event.clientY, startRect: rect }\n const onMove = (moveEvent: PointerEvent): void => {\n const drag = dragRef.current\n if (drag === undefined) return\n const dx = (moveEvent.clientX - drag.startX) * scale\n const dy = (moveEvent.clientY - drag.startY) * scale\n setRect(drag.mode === 'move'\n ? moveCropRect(drag.startRect, dx, dy, props.natural)\n : resizeCropRect(drag.startRect, drag.mode, dx, dy, props.natural, aspect))\n }\n const onUp = (): void => {\n dragRef.current = undefined\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onUp)\n }\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n window.addEventListener('pointercancel', onUp)\n }\n\n return (\n <>\n {/* The box lives over the node: three-thirds guides, a dimmed outside is\n deliberately omitted so the user still sees what they are cutting. */}\n <div\n className={css.box}\n data-dsh-seework-crop=\"\"\n style={{\n left: `${props.node.x + displayed.x}px`,\n top: `${props.node.y + displayed.y}px`,\n width: `${displayed.width}px`,\n height: `${displayed.height}px`,\n }}\n onPointerDown={event => { beginDrag(event, 'move') }}\n >\n <span className={css.thirdV} />\n <span className={css.thirdV} data-second=\"\" />\n <span className={css.thirdH} />\n <span className={css.thirdH} data-second=\"\" />\n {CROP_HANDLES.map(handle => (\n <span\n key={handle}\n className={css.handle}\n data-handle={handle}\n style={{ cursor: HANDLE_CURSORS[handle] }}\n onPointerDown={event => { beginDrag(event, handle) }}\n />\n ))}\n </div>\n\n <div\n className={css.toolbar}\n data-dsh-seework-crop-toolbar=\"\"\n style={{ left: `${props.node.x}px`, top: `${props.node.y + props.node.height + 12}px` }}\n onPointerDown={event => { event.stopPropagation() }}\n >\n <select\n className={css.select}\n value={String(aspect)}\n title=\"裁剪比例\"\n onChange={event => {\n const value = event.target.value === 'null' ? null : Number(event.target.value)\n setAspect(value)\n setRect(defaultCropRect(props.natural, value))\n }}\n >\n {CROP_ASPECTS.map(option => (\n <option key={option.label} value={String(option.value)}>{option.label}</option>\n ))}\n </select>\n <span className={css.size}>{Math.round(rect.width)}×{Math.round(rect.height)}</span>\n <Button onClick={props.onCancel}>取消</Button>\n <Button variant=\"primary\" data-dsh-seework-crop-confirm=\"\" onClick={() => { props.onConfirm(rect) }}>裁剪</Button>\n </div>\n </>\n )\n}\n","/**\n * 「加入到对话框」: put a picture into the conversation's composer as a draft\n * attachment.\n *\n * The composer's attachments are browser-owned drafts: the shell registers the\n * file, hands back a draft id, and the input machine shows it in the rail with\n * its bytes sent along with the prompt (`createDrafts` + `addAttachments` on the\n * conversation service / the session's input actions). So this needs no host\n * route and no library write: the bytes come from the picture's own URL.\n *\n * Where the face comes from: the shell hands `sessionId` and `inputActions` to\n * every session-scope slot component, and our library/canvas tab bodies are\n * exactly that — so they publish the face while they are mounted, and the\n * conversation service is probed once from the plugin root (`ctx.get`, never a\n * declared `inject`: a missing provider must not park the whole plugin).\n *\n * Without a mounted tab there is no session context, so the buttons hide rather\n * than pretend: the home-screen floating panels have no conversation to add to.\n */\n\n/** The slice of the conversation service this module uses. */\nexport interface ComposerDrafts {\n /**\n * Register browser files as runtime-only draft attachments.\n * @param sessionId - target session.\n * @param files - browser files to register.\n * @returns ordered draft descriptors.\n */\n createDrafts: (sessionId: string, files: readonly File[]) => readonly { id: string }[]\n /** Release drafts that never made it into the composer. */\n releaseDraftAttachments: (attachments: readonly unknown[]) => void\n}\n\n/** The slice of the session input actions this module uses. */\nexport interface ComposerInput {\n /** Append ordered browser-owned attachment ids; a busy composer refuses. */\n addAttachments: (ids: readonly string[]) => boolean\n}\n\n/** A session-scope tab body publishes this while it is mounted. */\nexport interface ComposerFace {\n sessionId: string\n input: ComposerInput\n}\n\nlet conversationProbe: (() => ComposerDrafts | undefined) | undefined\nlet face: ComposerFace | undefined\n\n/**\n * Install the probe that resolves the conversation service (plugin root, once).\n *\n * A probe rather than the service itself: plugins attach in boot order, and this\n * one applies before the conversation plugin may have registered — resolving at\n * call time costs nothing and cannot go stale.\n *\n * @param next - the probe, or undefined when the page has no conversation.\n * @returns disposer restoring the previous probe.\n */\nexport function setComposerProbe(next: (() => ComposerDrafts | undefined) | undefined): () => void {\n const previous = conversationProbe\n conversationProbe = next\n return () => { conversationProbe = previous }\n}\n\n/**\n * Publish (or clear) the session-scope face a tab body owns.\n * @param next - the face, or undefined when no tab body is mounted.\n * @returns disposer clearing this exact face.\n */\nexport function setComposerFace(next: ComposerFace | undefined): () => void {\n const previous = face\n face = next\n return () => {\n if (face === next) face = previous\n }\n}\n\n/** Whether 「加入到对话框」 can do anything right now. */\nexport function composerAvailable(): boolean {\n return conversationProbe?.() !== undefined && face !== undefined\n}\n\n/**\n * Send one picture to the composer as a draft attachment.\n *\n * The picture is fetched from its own URL (a library image or a canvas asset),\n * turned into a browser `File`, and registered as a draft; the draft id then\n * goes into the session's input rail. A composer that refuses the id (a busy\n * submission) leaves nothing behind — the draft is released again.\n *\n * @param image - the picture's URL and the name its attachment should carry.\n * @returns true when the draft reached the composer.\n */\nexport async function sendImageToConversation(image: { url: string; name?: string | undefined }): Promise<boolean> {\n const service = conversationProbe?.()\n const current = face\n if (service === undefined || current === undefined) return false\n const response = await fetch(image.url)\n if (!response.ok) return false\n const blob = await response.blob()\n const name = image.name ?? 'seework-image.png'\n const file = new File([blob], name, { type: blob.type === '' ? 'image/png' : blob.type })\n const drafts = service.createDrafts(current.sessionId, [file])\n const ids = drafts.map(draft => draft.id)\n if (ids.length === 0) return false\n if (!current.input.addAttachments(ids)) {\n // A busy composer refuses the ids; hand the drafts back rather than leaking\n // object URLs the composer will never show.\n service.releaseDraftAttachments(drafts)\n console.warn('[dsh-seework] the composer refused the draft attachment.')\n return false\n }\n return true\n}\n\n/**\n * The attachment name for one library file: it keeps the extension the route\n * serves, so the composer's preview and the eventual prompt agree on the type.\n * @param file - the library or asset file name.\n * @returns a browser-safe file name.\n */\nexport function attachmentNameFor(file: string): string {\n const base = file.split(/[\\\\/]/).pop() ?? 'seework-image.png'\n return base === '' ? 'seework-image.png' : base\n}\n","/**\n * Where a node's floating command bar sits.\n *\n * Pure maths (no React, no DOM) so it can be tested without a browser: the bar\n * hugs the node's top edge, flips below it when there is not enough room above,\n * and clamps horizontally inside the visible stage so it never runs off an edge.\n * Mirrors the reference client's own helper (seeaitv `utils/floatingBarPosition`),\n * because the interaction it serves is the same one: click a node, get a bar\n * right there.\n */\n\nimport { boardToScreen } from '../canvas-viewport.ts'\nimport type { CanvasViewport } from '../protocol.ts'\n\n/** Vertical gap between the bar and the node it belongs to (screen px). */\nexport const FLOATING_BAR_GAP = 8\n\n/** Minimum horizontal distance from the stage's edges (screen px). */\nexport const FLOATING_BAR_EDGE_MARGIN = 8\n\n/** Which side of the node the bar ended up on (a caller may style the arrow). */\nexport type FloatingBarPlacement = 'above' | 'below'\n\n/** The node rectangle, in board coordinates. */\nexport interface FloatingBarNode {\n x: number\n y: number\n width: number\n height: number\n}\n\n/**\n * Place the bar for one node.\n *\n * @param node - the node's rectangle in board coordinates.\n * @param viewport - the board's viewport.\n * @param barSize - the bar's measured size in screen pixels.\n * @param stageSize - the visible stage size in screen pixels.\n * @returns absolute left/top inside the stage, and which side it landed on.\n */\nexport function floatingBarPosition(\n node: FloatingBarNode,\n viewport: CanvasViewport,\n barSize: { width: number; height: number },\n stageSize: { width: number; height: number },\n): { left: number; top: number; placement: FloatingBarPlacement } {\n const topLeft = boardToScreen(viewport, node.x, node.y)\n const bottomRight = boardToScreen(viewport, node.x + node.width, node.y + node.height)\n const centreX = (topLeft.x + bottomRight.x) / 2\n\n // Above by default; below when the bar would cross the stage's top edge.\n const placement: FloatingBarPlacement =\n topLeft.y - FLOATING_BAR_GAP - barSize.height < 0 ? 'below' : 'above'\n const top = placement === 'above'\n ? topLeft.y - FLOATING_BAR_GAP - barSize.height\n : bottomRight.y + FLOATING_BAR_GAP\n\n const minLeft = FLOATING_BAR_EDGE_MARGIN\n const maxLeft = Math.max(minLeft, stageSize.width - FLOATING_BAR_EDGE_MARGIN - barSize.width)\n const left = Math.min(Math.max(centreX - barSize.width / 2, minLeft), maxLeft)\n\n return { left, top, placement }\n}\n","/**\n * The floating command bar that appears when a board node is selected.\n *\n * Clicking an image on the canvas is the moment a user wants to act on THAT\n * picture, so the actions live right next to it instead of in a distant toolbar.\n * The bar is HTML over the board (not part of the scaled world layer), measured\n * after every render, and positioned by {@link floatingBarPosition} — the same\n * interaction the reference client uses.\n *\n * Batch 1 carries the one action that exists so far, 「加入到对话框」 (put the\n * picture back into the composer as a draft attachment); the 标注 / 裁剪 tools\n * join the same bar in the next batch.\n */\n\nimport { useLayoutEffect, useRef, useState } from 'react'\nimport type { CanvasViewport } from '../protocol.ts'\nimport { floatingBarPosition, type FloatingBarNode } from './floating-bar.ts'\nimport css from './node-floating-bar.module.css'\n\n/** What the bar offers for the node it belongs to. */\nexport interface NodeFloatingBarProps {\n /** The selected node, in board coordinates. */\n node: FloatingBarNode\n /** The board's viewport. */\n viewport: CanvasViewport\n /** The visible stage size, measured live by the board. */\n stageSize: { width: number; height: number }\n /** Put this picture into the conversation's composer. */\n onAddToConversation?: (() => boolean | Promise<boolean>) | undefined\n /** Mark up this picture (开门标注编辑器). */\n onAnnotate?: (() => void) | undefined\n /** Crop this picture in place (produces a new node). */\n onCrop?: (() => void) | undefined\n}\n\n/** One button in the bar. */\nfunction BarButton({ label, title, onClick, disabled = false }: {\n label: string\n title: string\n onClick?: (() => void) | undefined\n disabled?: boolean\n}): JSX.Element {\n return (\n <button\n type=\"button\"\n className={css.button}\n title={title}\n aria-label={title}\n disabled={disabled}\n onClick={event => {\n event.stopPropagation()\n onClick?.()\n }}\n >\n {label}\n </button>\n )\n}\n\n/**\n * The bar for one selected node.\n * @param props - the node, the viewport, the stage size, and the actions.\n * @returns the bar, positioned inside the stage.\n */\nexport function NodeFloatingBar(props: NodeFloatingBarProps): JSX.Element {\n const barRef = useRef<HTMLDivElement | null>(null)\n const [size, setSize] = useState({ width: 0, height: 0 })\n const [outcome, setOutcome] = useState<'idle' | 'failed'>('idle')\n\n // Measure after every render: the labels are localized text, so the width is\n // not knowable up front, and a wrong width would clamp against the wrong edge.\n useLayoutEffect(() => {\n const element = barRef.current\n if (element === null) return\n const rect = element.getBoundingClientRect()\n setSize(previous => previous.width === rect.width && previous.height === rect.height\n ? previous\n : { width: rect.width, height: rect.height })\n })\n\n const position = floatingBarPosition(props.node, props.viewport, size, props.stageSize)\n return (\n <div\n ref={barRef}\n className={css.bar}\n data-dsh-seework-node-bar=\"\"\n data-placement={position.placement}\n style={{\n left: `${position.left}px`,\n top: `${position.top}px`,\n // Hidden until measured: an unmeasured bar would flash at the corner.\n visibility: size.width > 0 ? 'visible' : 'hidden',\n }}\n onPointerDown={event => { event.stopPropagation() }}\n onMouseDown={event => { event.stopPropagation() }}\n >\n <BarButton\n label={outcome === 'failed' ? '放不进输入框' : '加入到对话框'}\n title={outcome === 'failed'\n ? '没能放进输入框:请确认当前会话的输入框可用'\n : '把这张图放进对话输入框当草稿附件'}\n disabled={props.onAddToConversation === undefined}\n onClick={() => {\n const run = props.onAddToConversation\n if (run === undefined) return\n void Promise.resolve(run()).then(ok => {\n // The bar is the only feedback here: the composer may be off screen.\n if (ok !== false) return\n setOutcome('failed')\n setTimeout(() => { setOutcome('idle') }, 2500)\n })\n }}\n />\n <BarButton\n label=\"标注\"\n title=\"在图上画矩形 / 涂抹 / 写字,保存成一张新图(原图不变)\"\n onClick={props.onAnnotate}\n disabled={props.onAnnotate === undefined}\n />\n <BarButton\n label=\"裁剪\"\n title=\"就地裁剪这张图(生成新卡片,原图保留)\"\n onClick={props.onCrop}\n disabled={props.onCrop === undefined}\n />\n </div>\n )\n}\n","/**\n * A minimal observable snapshot store.\n *\n * The shell's `dsh-client-store` package is not in the shared module baseline,\n * so depending on it would either duplicate the store machinery inside this\n * bundle or drag in its zustand/immer tree. The settings bridge needs only\n * \"read the current value, subscribe, replace or patch it\", which is this file.\n */\n\n/** One observable value with React-friendly snapshot semantics. */\nexport class SnapshotStore<T> {\n private value: T\n private readonly listeners = new Set<() => void>()\n\n constructor(initial: T) {\n this.value = initial\n }\n\n /** @returns the current value; the reference changes on every update. */\n getSnapshot(): T {\n return this.value\n }\n\n /** @returns the disposer removing this listener. */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n /** Replace the value and notify listeners. */\n set(next: T): void {\n this.value = next\n this.notify()\n }\n\n /** Patch the value with a mutator that receives a detached copy. */\n update(mutator: (draft: T) => void): void {\n const draft = { ...(this.value as unknown as Record<string, unknown>) } as unknown as T\n mutator(draft)\n this.value = draft\n this.notify()\n }\n\n private notify(): void {\n for (const listener of [...this.listeners]) listener()\n }\n}\n","/**\n * Browser-side canvas access: board listing, the open board, and autosave.\n *\n * The board is a spatial document (see `docs/architecture.md`), so the\n * interesting part is not the model but the save protocol:\n *\n * - edits touch the in-memory document and mark it dirty;\n * - a debounced save sends the document with the revision it was read at;\n * - a conflict (another window saved first) re-reads the board, adopts that\n * revision, and retries — the user's board is the working copy, so it is\n * never replaced by the remote document;\n * - a failed save keeps the document dirty so the next tick retries.\n *\n * Nothing here draws anything: the board component renders whatever this store\n * holds, so the same document can be saved, reloaded, or thrown away without\n * the view knowing.\n */\n\nimport type { CanvasCard, CanvasDocument, CanvasSummary, CanvasViewport } from '../protocol.ts'\nimport { SeeWorkApi } from './api.ts'\nimport { SnapshotStore } from './snapshot-store.ts'\n\n/** How long edits settle before a save is attempted. */\nexport const AUTOSAVE_DELAY_MS = 800\n\n/** Canvas state as the workspace renders it. */\nexport interface CanvasState {\n status: 'loading' | 'ready' | 'error'\n boards: CanvasSummary[]\n /** The open board, when one is open. */\n board?: CanvasDocument\n /** Whether the open board has edits that are not stored yet. */\n dirty: boolean\n /** `saving` is in flight; `conflict` needs the user to look; `error` is a failure. */\n save: 'idle' | 'saving' | 'saved' | 'conflict' | 'error'\n /** Human-readable reason for the current save state. */\n message?: string\n}\n/** Create a card id (board-local, only needs to be unique within one board). */\nexport function newCardId(): string {\n const cryptoObj: Crypto | undefined = globalThis.crypto\n if (cryptoObj?.randomUUID !== undefined) return cryptoObj.randomUUID()\n return `card-${Date.now().toString(36)}-${Math.round(Math.random() * 1e6).toString(36)}`\n}\n\n/** The board store. */\nexport class CanvasStore {\n private readonly store = new SnapshotStore<CanvasState>({ status: 'loading', boards: [], dirty: false, save: 'idle' })\n private readonly api: SeeWorkApi\n private saveTimer: ReturnType<typeof setTimeout> | undefined\n private saveInFlight = false\n /**\n * Monotonic local-edit counter, and the fence a save response is judged by.\n *\n * The host echoes the document it stored; that echo is a snapshot of what was\n * sent. Any edit made AFTER the request went out is newer than the echo, and\n * comparing this counter is how the response knows not to overwrite it.\n */\n private editSeq = 0\n private disposed = false\n\n constructor(api: SeeWorkApi) {\n this.api = api\n }\n\n /**\n * The route client this store writes through.\n *\n * Exposed for the board's own picture work (a burned annotation, a crop): those\n * write to the canvas asset store, and threading a second client through every\n * mount point just to upload one image would be worse.\n */\n get client(): SeeWorkApi {\n return this.api\n }\n\n getSnapshot(): CanvasState {\n return this.store.getSnapshot()\n }\n\n subscribe(listener: () => void): () => void {\n return this.store.subscribe(listener)\n }\n\n /** Stop the autosave timer (the surface is going away). */\n dispose(): void {\n this.disposed = true\n if (this.saveTimer !== undefined) clearTimeout(this.saveTimer)\n this.saveTimer = undefined\n }\n\n /** Re-read the board list; the open board is left alone. */\n async refreshList(): Promise<void> {\n const result = await this.api.canvases()\n if (this.disposed) return\n if (!result.ok) {\n this.store.update(draft => {\n draft.status = 'error'\n draft.message = result.message\n })\n return\n }\n this.store.update(draft => {\n draft.status = 'ready'\n draft.boards = result.value.canvases\n delete draft.message\n })\n }\n\n /**\n * Open a board.\n * @param id - board id, or '' to create a fresh one.\n * @returns the board that is now open, or undefined when it could not be opened.\n */\n async open(id = ''): Promise<CanvasDocument | undefined> {\n const result = id === '' ? await this.api.createCanvas() : await this.api.readCanvas(id)\n if (this.disposed) return undefined\n if (!result.ok) {\n this.store.update(draft => {\n draft.status = 'error'\n draft.message = result.message\n })\n return undefined\n }\n const board = result.value.canvas\n this.store.update(draft => {\n draft.board = board\n draft.dirty = false\n draft.save = 'idle'\n delete draft.message\n })\n void this.refreshList()\n return board\n }\n\n /** Close the open board, saving first when it has unsaved edits. */\n async close(): Promise<void> {\n if (this.store.getSnapshot().dirty) await this.saveNow()\n this.store.update(draft => {\n delete draft.board\n draft.dirty = false\n draft.save = 'idle'\n delete draft.message\n })\n }\n\n /** Delete a board (closing it first when it is the open one). */\n async deleteBoard(id: string): Promise<void> {\n if (this.store.getSnapshot().board?.id === id) await this.close()\n const result = await this.api.removeCanvas(id)\n if (this.disposed) return\n if (!result.ok) {\n this.store.update(draft => {\n draft.save = 'error'\n draft.message = `删除失败:${result.message}`\n })\n return\n }\n this.store.update(draft => { draft.boards = result.value.canvases })\n }\n\n /** Replace the board document as part of an edit. */\n private edit(mutate: (board: CanvasDocument) => void): void {\n const current = this.store.getSnapshot().board\n if (current === undefined) return\n // A shallow copy keeps React's identity checks meaningful while the nested\n // arrays are replaced by the callers that touch them.\n const next: CanvasDocument = { ...current, cards: [...current.cards] }\n mutate(next)\n this.editSeq += 1\n this.store.update(draft => {\n draft.board = next\n draft.dirty = true\n if (draft.save !== 'conflict') draft.save = 'idle'\n })\n this.scheduleSave()\n }\n\n /** Move, resize or restack one card. */\n updateCard(id: string, patch: Partial<CanvasCard>): void {\n this.edit(board => {\n board.cards = board.cards.map(card => card.id === id ? { ...card, ...patch } : card)\n })\n }\n\n /** Remove one card. */\n removeCard(id: string): void {\n this.edit(board => {\n board.cards = board.cards.filter(card => card.id !== id)\n })\n }\n\n /** Place new cards on the board, on top of everything already there. */\n addCards(cards: CanvasCard[]): void {\n if (cards.length === 0) return\n this.edit(board => {\n const top = board.cards.reduce((highest, card) => Math.max(highest, card.z), 0)\n board.cards = [...board.cards, ...cards.map((card, index) => ({ ...card, z: top + index + 1 }))]\n })\n }\n\n /** Move the viewport (and remember it). */\n setViewport(viewport: CanvasViewport): void {\n this.edit(board => { board.viewport = viewport })\n }\n\n /** Rename the board. */\n setTitle(title: string): void {\n this.edit(board => { board.title = title })\n }\n\n /** Save immediately, cancelling the debounce. */\n async saveNow(): Promise<void> {\n if (this.saveTimer !== undefined) {\n clearTimeout(this.saveTimer)\n this.saveTimer = undefined\n }\n await this.save()\n }\n\n /** Queue a save after the edits settle. */\n private scheduleSave(): void {\n if (this.saveTimer !== undefined) clearTimeout(this.saveTimer)\n this.saveTimer = setTimeout(() => {\n this.saveTimer = undefined\n void this.save()\n }, AUTOSAVE_DELAY_MS)\n }\n\n /**\n * Write the open board.\n *\n * A save that fails or conflicts must never lose the local edit: the document\n * stays in memory and dirty, and the user is told what happened.\n */\n private async save(): Promise<void> {\n const snapshot = this.store.getSnapshot()\n const board = snapshot.board\n if (board === undefined || !snapshot.dirty) return\n if (this.saveInFlight) {\n // Coalesce: the in-flight response re-schedules when it sees that edits\n // arrived after its request went out (see `changedSince` below).\n return\n }\n this.saveInFlight = true\n const sentAt = this.editSeq\n this.store.update(draft => { draft.save = 'saving' })\n const reading = board.revision\n const result = await this.api.saveCanvas(board, reading)\n this.saveInFlight = false\n if (this.disposed) return\n if (result.ok) {\n const changedSince = this.editSeq !== sentAt\n this.store.update(draft => {\n // The host's reply is the document it STORED, i.e. a snapshot of what\n // was sent. The local document is the working copy, so only its\n // revision comes back from that reply: adopting the echo wholesale\n // would silently drop every edit made after the request left — a\n // viewport change (\"适应内容\") or a card move included — and marking the\n // board clean would then stop it from ever being saved.\n if (draft.board !== undefined) {\n draft.board = { ...draft.board, revision: result.value.canvas.revision }\n }\n draft.dirty = changedSince\n draft.save = changedSince ? 'idle' : 'saved'\n })\n if (changedSince) this.scheduleSave()\n void this.refreshList()\n return\n }\n if (result.code === 'canvas_conflict') {\n // Another window saved first. The user's board stays exactly as they left\n // it — this is their working copy, and replacing it with the remote\n // document would throw their layout away. What changes is the revision we\n // fence on, which the re-read supplies; the retry then persists the\n // user's whole board, which is what \"last writer wins\" means for a\n // deliberately single-document board.\n const reread = await this.api.readCanvas(board.id)\n if (this.disposed) return\n const current = this.store.getSnapshot().board\n this.store.update(draft => {\n draft.save = 'conflict'\n draft.dirty = true\n draft.message = reread.ok\n ? '这块画布在别处也被改过:正在把你的版本保存为最新版本。'\n : '这块画布在别处也被改过,且重新读取失败;改动还在这里,稍后会自动重试。'\n if (reread.ok && current !== undefined) {\n draft.board = { ...current, revision: reread.value.canvas.revision }\n }\n })\n if (reread.ok) {\n // Retry immediately on the fresh revision so the user is not left\n // staring at a conflict they cannot act on.\n await this.save()\n }\n return\n }\n this.store.update(draft => {\n draft.save = 'error'\n draft.message = `保存失败:${result.message}`\n draft.dirty = true\n })\n }\n}\n\n/** A card id that does not collide with what is already on the board. */\nexport function unusedCardId(board: CanvasDocument | undefined): string {\n const existing = new Set((board?.cards ?? []).map(card => card.id))\n for (let attempt = 0; attempt < 10; attempt++) {\n const id = newCardId()\n if (!existing.has(id)) return id\n }\n return `card-${Date.now().toString(36)}`\n}\n","/**\n * The SeeWork canvas: a free spatial board.\n *\n * Deliberately not a node graph — there are no ports and no connections. A card\n * is a rectangle at a coordinate; the user arranges pictures and notes by hand,\n * which is what SeeAI TV's board does and what the user asked for. Cards come\n * from the material library (the images already generated) or are plain notes.\n *\n * All pointer handling goes through `canvas-viewport`'s linear map, so dragging\n * and zooming stay correct at any zoom level instead of drifting.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { CANVAS_NEW_IMAGE_SIZE } from '../canvas-limits.ts'\nimport { centeredImageCard, stageCentre } from '../canvas-placement.ts'\nimport { boardToScreen, fitViewport, panBy, zoomAt, zoomTo } from '../canvas-viewport.ts'\nimport { canvasImageUrl, canvasOriginLabel, type CanvasCard, type CanvasCardOrigin, type CanvasViewport, type LibraryEntry } from '../protocol.ts'\nimport { AnnotationEditor } from './AnnotationEditor.tsx'\nimport { CropOverlay } from './CropOverlay.tsx'\nimport { burnAnnotations, burnCrop } from './burn-image.ts'\nimport { attachmentNameFor, sendImageToConversation } from './composer-draft.ts'\nimport { cropNodePosition, croppedNodeSize, type CropRect } from './crop-rect.ts'\nimport { NodeFloatingBar } from './NodeFloatingBar.tsx'\nimport { Button, TextInput } from './controls.tsx'\nimport { unusedCardId, type CanvasState, type CanvasStore } from './canvas-store.ts'\nimport type { CanvasStore as CanvasStoreType } from './canvas-store.ts'\nimport css from './canvas.module.css'\n\n/** How far a click must travel before it counts as a drag. */\nconst DRAG_THRESHOLD_PX = 3\n\n/** How much of a card must be on screen before the layer list leaves the view alone. */\nconst LAYER_REVEAL_MARGIN = 24\n\n/**\n * How long a housekeeping notice stays up.\n *\n * It reports a finished action (\"已清理 4 张…\"), not a state the user has to keep\n * reading — a notice bar that never goes away just eats the board (user report:\n * 「一直存在提示条,不消失」).\n */\nexport const NOTICE_MS = 4000\n\n/** A byte count for a notice, e.g. `12.4 MB`. */\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\n/**\n * What the ✕ on one card promises.\n *\n * Only the board's own pictures are deleted with the card; a library picture is\n * the record of a generation, and saying otherwise would be a lie the user acts on.\n *\n * @param card - the card about to be removed.\n * @returns the tooltip text.\n */\nfunction removeHint(card: CanvasCard): string {\n if (card.source === 'canvas') return '从画布移除(这张合成图的文件也会一起删掉)'\n if (card.kind === 'image') return '从画布移除(不会删除素材库里的文件)'\n return '从画布移除'\n}\n\n/**\n * One line of the layer list.\n * @param card - the card.\n * @returns a short description (its prompt, its note, or its file name).\n */\nfunction layerName(card: CanvasCard): string {\n const text = (card.kind === 'text' ? card.text : card.prompt) ?? ''\n const trimmed = text.trim()\n if (trimmed !== '') return trimmed\n return card.kind === 'text' ? '(空备注)' : card.file ?? '图片'\n}\n\n/**\n * Visible stage size per board store, as the board last measured it.\n *\n * The conversation's 「加到画布」 runs outside any board render, so the size is\n * recorded here instead of being asked of the DOM from there.\n */\nconst stageSizes = new WeakMap<object, { width: number; height: number }>()\n\n/**\n * Record the stage size the board just measured.\n * @param store - the board's store.\n * @param size - the stage size in screen pixels.\n */\nexport function publishStageSize(store: CanvasStoreType, size: { width: number; height: number }): void {\n stageSizes.set(store, size)\n}\n\n/**\n * The last stage size a board measured.\n * @param store - the board's store.\n * @returns the size, or zeros before the board has rendered.\n */\nexport function stageSizeFor(store: CanvasStoreType): { width: number; height: number } {\n return stageSizes.get(store) ?? { width: 0, height: 0 }\n}\n\n/** Pointer gesture currently in progress. */\ntype Gesture =\n | { kind: 'pan'; lastX: number; lastY: number }\n | { kind: 'move'; cardId: string; startX: number; startY: number; cardX: number; cardY: number; moved: boolean }\n | { kind: 'resize'; cardId: string; startX: number; startY: number; width: number; height: number; moved: boolean }\n\n/** Props for the canvas workspace. */\nexport interface CanvasBoardProps {\n store: CanvasStore\n /** The material library, for picking pictures to place. */\n library: { entries: LibraryEntry[] }\n /** Load the library (the picker needs it even if the drawer was never opened). */\n onNeedLibrary: () => void\n}\n\n/** One node's rectangle in stage (screen) coordinates. */\nfunction nodeScreenRect(\n card: CanvasCard,\n viewport: CanvasViewport,\n): { x: number; y: number; width: number; height: number } {\n const topLeft = boardToScreen(viewport, card.x, card.y)\n return { x: topLeft.x, y: topLeft.y, width: card.width * viewport.k, height: card.height * viewport.k }\n}\n\n/** A new node's board size for one burned picture: its own aspect, comparable width. */\nfunction sizeKeepingAspect(\n referenceWidth: number,\n width: number,\n height: number,\n): { width: number; height: number } {\n const aspect = height === 0 ? 1 : width / height\n const nodeWidth = Math.max(64, Math.min(480, referenceWidth))\n return { width: Math.round(nodeWidth), height: Math.round(nodeWidth / aspect) }\n}\n\n/** Read a burned data URL's real pixel size (the new node is sized from it). */\nasync function loadBurnedSize(dataUrl: string): Promise<{ dataUrl: string; width: number; height: number }> {\n return new Promise(resolve => {\n const image = new Image()\n image.onload = () => { resolve({ dataUrl, width: image.naturalWidth, height: image.naturalHeight }) }\n image.onerror = () => { resolve({ dataUrl, width: 0, height: 0 }) }\n image.src = dataUrl\n })\n}\n\n/** The canvas workspace: toolbar + board + library picker. */export function CanvasBoard({ store, library, onNeedLibrary }: CanvasBoardProps): JSX.Element {\n const [tick, setTick] = useState(0)\n useEffect(() => store.subscribe(() => { setTick(value => value + 1) }), [store])\n void tick\n const state = store.getSnapshot()\n const board = state.board\n const api = store.client\n\n const surfaceRef = useRef<HTMLDivElement | null>(null)\n const gestureRef = useRef<Gesture | null>(null)\n const [gestureTick, setGestureTick] = useState(0)\n const [pickerOpen, setPickerOpen] = useState(false)\n const [selected, setSelected] = useState<string | undefined>(undefined)\n /** The card whose marks are being edited (modal), or undefined. */\n const [annotating, setAnnotating] = useState<string | undefined>(undefined)\n /** The card in crop mode (in place), or undefined. */\n const [cropping, setCropping] = useState<string | undefined>(undefined)\n /** The picture behind the active edit mode, with the size it really is. */\n const [modePicture, setModePicture] = useState<{ url: string; natural: { width: number; height: number } } | undefined>(undefined)\n const [busy, setBusy] = useState(false)\n /** What the last housekeeping action did (or why it failed). */\n const [assetNotice, setAssetNotice] = useState<string | undefined>(undefined)\n const noticeTimer = useRef<number | undefined>(undefined)\n /** Show a housekeeping notice, replacing any previous one (and any pending fade). */\n const showAssetNotice = useCallback((text: string): void => {\n if (noticeTimer.current !== undefined) window.clearTimeout(noticeTimer.current)\n setAssetNotice(text)\n noticeTimer.current = window.setTimeout(() => {\n noticeTimer.current = undefined\n setAssetNotice(undefined)\n }, NOTICE_MS)\n }, [])\n useEffect(() => () => {\n if (noticeTimer.current !== undefined) window.clearTimeout(noticeTimer.current)\n }, [])\n /** Whether the compact layer list is open. */\n const [layersOpen, setLayersOpen] = useState(false)\n\n /**\n * Open one edit mode for a card.\n *\n * The picture's natural size decides the coordinate system both modes work in\n * (marks and the crop box are stored in picture pixels), and only the loaded\n * image knows it — a card's board size is whatever the user dragged it to.\n */\n const openMode = (kind: 'annotate' | 'crop', card: CanvasCard): void => {\n const file = card.file ?? ''\n if (file === '') return\n const url = canvasImageUrl(file, card.source)\n const enter = (natural: { width: number; height: number }): void => {\n setModePicture({ url, natural })\n if (kind === 'annotate') setAnnotating(card.id)\n else setCropping(card.id)\n }\n const image = new Image()\n image.onload = () => { enter({ width: image.naturalWidth || card.width, height: image.naturalHeight || card.height }) }\n image.onerror = () => { enter({ width: card.width, height: card.height }) }\n image.src = url\n }\n\n /** Leave whichever edit mode is open. */\n const closeMode = (): void => {\n setAnnotating(undefined)\n setCropping(undefined)\n setModePicture(undefined)\n }\n\n /**\n * Turn one burned picture into a new card on the board.\n *\n * The composite goes to the canvas asset store — deliberately not the material\n * library — and the card says so (`source: 'canvas'` + its `origin`), so a\n * marked-up copy can never be mistaken for a generation.\n */\n const addBurnedCard = async (burned: { dataUrl: string; width: number; height: number }, placement: { x: number; y: number; width: number; height: number }, origin: 'annotation' | 'crop'): Promise<void> => {\n const current = store.getSnapshot().board\n if (current === undefined) return\n const label = canvasOriginLabel(origin) ?? '合成图'\n setBusy(true)\n try {\n const result = await api.writeCanvasAsset(burned.dataUrl)\n if (!result.ok) {\n console.warn('[dsh-seework] storing the marked-up picture failed:', result.message)\n return\n }\n store.addCards([{\n id: unusedCardId(current),\n kind: 'image',\n x: Math.round(placement.x),\n y: Math.round(placement.y),\n width: Math.round(placement.width),\n height: Math.round(placement.height),\n z: 0,\n file: result.value.image.file,\n source: 'canvas',\n origin,\n model: label,\n prompt: label,\n }])\n await store.saveNow()\n } finally {\n setBusy(false)\n }\n }\n\n const viewport = board?.viewport ?? { x: 0, y: 0, k: 1 }\n // A gesture mutates the viewport through the store, so the render below is\n // already up to date; gestureTick only exists to re-render the cursor state.\n void gestureTick\n\n // Stage size, used by \"fit\", by placement maths, and by the automatic\n // placement the plugin runs when a generation lands while the canvas is open\n // (that path has no DOM access of its own, so the size is recorded here).\n const stageRef = useRef<{ width: number; height: number }>({ width: 0, height: 0 })\n /**\n * The visible stage size, measured NOW.\n *\n * Measuring on demand matters: the recorded value can be a stale zero (this\n * board has not been laid out yet, or a hidden sibling — the floating overlay\n * and the sidebar tab share one store — measured 0×0 and overwrote it). A\n * zero here used to mean \"fit\" silently reset the view to 100% at the origin,\n * which reads to the user as \"my pictures disappeared\".\n */\n const stageSize = useCallback((): { width: number; height: number } => {\n const surface = surfaceRef.current\n if (surface === null) return stageRef.current\n const width = surface.offsetWidth\n const height = surface.offsetHeight\n return width > 0 && height > 0 ? { width, height } : stageRef.current\n }, [])\n\n // Measure the stage without ever measuring the transformed world layer: its\n // client rect already includes the zoom, and fitting against that feeds the\n // zoom back in on every press.\n useEffect(() => {\n const surface = surfaceRef.current\n if (surface === null) return\n const measure = (): void => {\n const size = { width: surface.offsetWidth, height: surface.offsetHeight }\n stageRef.current = size\n // Never publish a zero: a hidden sibling board would otherwise wipe the\n // size a visible one reported, and a placement that reads it would aim at\n // the viewport's corner instead of its middle.\n if (size.width > 0 && size.height > 0) publishStageSize(store, size)\n }\n measure()\n if (typeof ResizeObserver === 'undefined') return\n const observer = new ResizeObserver(measure)\n observer.observe(surface)\n return () => { observer.disconnect() }\n }, [store])\n\n /** Zoom one step around the stage centre (a toolbar press, not a cursor). */\n const zoomCentre = useCallback((direction: 1 | -1): void => {\n if (board === undefined) return\n const { width, height } = stageSize()\n if (width <= 0 || height <= 0) return\n store.setViewport(zoomAt(board.viewport, direction, width / 2, height / 2))\n }, [board, stageSize, store])\n\n /** Zoom to an exact factor around the stage centre. */\n const zoomExact = useCallback((k: number): void => {\n if (board === undefined) return\n const { width, height } = stageSize()\n if (width <= 0 || height <= 0) return\n store.setViewport(zoomTo(board.viewport, k, width / 2, height / 2))\n }, [board, stageSize, store])\n\n /**\n * Show everything on the board.\n *\n * A board with nothing to fit against is left alone rather than reset: the\n * degenerate fit returns the identity viewport, which throws away wherever\n * the user had scrolled to.\n */\n const fit = useCallback((): void => {\n if (board === undefined) return\n const { width, height } = stageSize()\n if (width <= 0 || height <= 0 || board.cards.length === 0) return\n store.setViewport(fitViewport(board.cards, width, height))\n }, [board, stageSize, store])\n\n // Wheel zoom: the board point under the cursor stays under the cursor.\n useEffect(() => {\n const surface = surfaceRef.current\n if (surface === null) return\n const onWheel = (event: WheelEvent): void => {\n if (board === undefined) return\n event.preventDefault()\n const bounds = surface.getBoundingClientRect()\n const anchorX = event.clientX - bounds.left\n const anchorY = event.clientY - bounds.top\n const factor = event.deltaY < 0 ? 1.08 : 1 / 1.08\n const current = store.getSnapshot().board?.viewport\n if (current === undefined) return\n store.setViewport(zoomTo(current, current.k * factor, anchorX, anchorY))\n }\n surface.addEventListener('wheel', onWheel, { passive: false })\n return () => { surface.removeEventListener('wheel', onWheel) }\n }, [board, store])\n\n // Global pointer handlers for the active gesture: the pointer can leave the\n // card (or the stage) mid-drag and must keep working.\n useEffect(() => {\n const onMove = (event: PointerEvent): void => {\n const gesture = gestureRef.current\n const current = store.getSnapshot().board\n if (gesture === null || current === undefined) return\n if (gesture.kind === 'pan') {\n // Pan by the step between this event and the previous one, never by the\n // total distance from the gesture's start. The current viewport is read\n // fresh each time, so a second handler (or a replayed event) can only\n // re-apply the same step to an already-advanced viewport: the board\n // races away under the pointer instead of following it.\n store.setViewport(panBy(current.viewport, event.clientX - gesture.lastX, event.clientY - gesture.lastY))\n gesture.lastX = event.clientX\n gesture.lastY = event.clientY\n return\n }\n const dx = (event.clientX - gesture.startX) / current.viewport.k\n const dy = (event.clientY - gesture.startY) / current.viewport.k\n if (gesture.kind === 'move') {\n if (!gesture.moved && Math.hypot(event.clientX - gesture.startX, event.clientY - gesture.startY) < DRAG_THRESHOLD_PX) return\n gesture.moved = true\n store.updateCard(gesture.cardId, { x: Math.round(gesture.cardX + dx), y: Math.round(gesture.cardY + dy) })\n return\n }\n gesture.moved = true\n store.updateCard(gesture.cardId, {\n width: Math.max(24, Math.round(gesture.width + dx)),\n height: Math.max(24, Math.round(gesture.height + dy)),\n })\n }\n const onUp = (): void => {\n const gesture = gestureRef.current\n gestureRef.current = null\n setGestureTick(value => value + 1)\n if (gesture !== null && gesture.kind !== 'pan' && gesture.moved) void store.saveNow()\n }\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n window.addEventListener('pointercancel', onUp)\n return () => {\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onUp)\n }\n }, [store])\n\n // Delete removes the selected card; Escape drops the selection.\n useEffect(() => {\n const onKey = (event: KeyboardEvent): void => {\n if (selected === undefined) return\n const target = event.target as HTMLElement | null\n const typing = target !== null && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)\n if (typing) return\n if (event.key === 'Delete' || event.key === 'Backspace') {\n event.preventDefault()\n store.removeCard(selected)\n setSelected(undefined)\n void store.saveNow()\n }\n if (event.key === 'Escape') setSelected(undefined)\n }\n document.addEventListener('keydown', onKey)\n return () => { document.removeEventListener('keydown', onKey) }\n }, [selected, store])\n\n const beginPan = (event: React.PointerEvent<HTMLDivElement>): void => {\n if (event.button !== 0) return\n // Pressing the board puts the work back on the board: the layer list is a\n // look-up aid, not a panel that stays in the way (user report: 「图层展开后,\n // 再画布点击后应该消失,现在一直占着」). Presses inside the list itself stop\n // propagation, so reading it does not close it.\n setLayersOpen(false)\n // Only the empty surface pans; a card's own handler stops propagation.\n const target = event.target as HTMLElement\n if (target.closest('[data-seework-card]') !== null) return\n setSelected(undefined)\n gestureRef.current = {\n kind: 'pan',\n lastX: event.clientX,\n lastY: event.clientY,\n }\n setGestureTick(value => value + 1)\n }\n\n const beginMove = (event: React.PointerEvent<HTMLElement>, card: CanvasCard): void => {\n if (event.button !== 0) return\n event.stopPropagation()\n setSelected(card.id)\n gestureRef.current = {\n kind: 'move',\n cardId: card.id,\n startX: event.clientX,\n startY: event.clientY,\n cardX: card.x,\n cardY: card.y,\n moved: false,\n }\n setGestureTick(value => value + 1)\n }\n\n const beginResize = (event: React.PointerEvent<HTMLElement>, card: CanvasCard): void => {\n if (event.button !== 0) return\n event.stopPropagation()\n event.preventDefault()\n gestureRef.current = {\n kind: 'resize',\n cardId: card.id,\n startX: event.clientX,\n startY: event.clientY,\n width: card.width,\n height: card.height,\n moved: false,\n }\n setGestureTick(value => value + 1)\n }\n\n /** Place a library picture in the middle of the current view. */\n const placeImage = useCallback((entry: LibraryEntry, imageIndex = 0): void => {\n if (board === undefined) return\n const image = entry.images[imageIndex]\n if (image === undefined) return\n const centre = stageCentre(board.viewport, stageSize())\n // Keep the picture's own aspect ratio; the card is sized to the picture,\n // not the picture squashed into a default card. The geometry comes from\n // `canvas-placement`, which the automatic placement shares.\n store.addCards([centeredImageCard({\n file: image.file,\n width: image.width,\n height: image.height,\n model: entry.model,\n prompt: entry.prompt,\n origin: entry.source === 'panel' ? 'panel' : 'chat',\n }, centre, unusedCardId(board))])\n setSelected(undefined)\n setPickerOpen(false)\n }, [board, stageSize, store])\n\n /** Add a note in the middle of the current view. */\n const addNote = useCallback((): void => {\n if (board === undefined) return\n const centre = stageCentre(board.viewport, stageSize())\n const id = unusedCardId(board)\n store.addCards([{\n id,\n kind: 'text',\n x: Math.round(centre.x - 120),\n y: Math.round(centre.y - 50),\n width: 240,\n height: 100,\n z: 0,\n text: '',\n fontSize: 16,\n }])\n setSelected(id)\n }, [board, stageSize, store])\n\n const boardCards = useMemo(() => [...(board?.cards ?? [])].sort((left, right) => left.z - right.z), [board?.cards])\n\n /**\n * Selecting a card also lifts it to the front.\n *\n * New cards land on top of everything (`addCards`), so without this a card you\n * click in order to move it can sit under pictures that were added later and be\n * impossible to drag out from underneath them. Only a card that is not already\n * on top is restacked, so clicking around does not rewrite the document.\n */\n const selectCard = useCallback((card: CanvasCard): void => {\n setSelected(card.id)\n const cards = store.getSnapshot().board?.cards ?? []\n const top = cards.reduce((highest, item) => Math.max(highest, item.z), card.z)\n if (card.z < top) store.updateCard(card.id, { z: top + 1 })\n }, [store])\n\n /**\n * Where one card's picture came from, for its badge.\n *\n * Cards written before the `origin` field existed have none, so it is inferred:\n * a board's own picture is a composite (its label says which kind), and a\n * library picture is looked up in the library the panel already holds.\n */\n const originOf = useCallback((card: CanvasCard): CanvasCardOrigin | undefined => {\n if (card.origin !== undefined) return card.origin\n if (card.source === 'canvas') return card.model === '裁剪' || card.model === '裁剪合成' ? 'crop' : 'annotation'\n for (const entry of library.entries) {\n if (entry.images.some(image => image.file === card.file)) return entry.source === 'panel' ? 'panel' : 'chat'\n }\n return undefined\n }, [library.entries])\n\n /**\n * Remove one card, and the file behind it when the board owns that file.\n *\n * The order matters: the board has to stop referencing the picture — and say so\n * on disk — before the delete route will agree to drop it, which is also what\n * keeps a second board's copy safe.\n */\n const removeCard = useCallback((card: CanvasCard): void => {\n store.removeCard(card.id)\n setSelected(undefined)\n void (async () => {\n await store.saveNow()\n if (card.source !== 'canvas' || card.file === undefined) return\n const result = await api.removeCanvasAsset(card.file)\n if (!result.ok) {\n showAssetNotice(`卡片已移除,但它的合成图文件没删掉:${result.message}`)\n return\n }\n showAssetNotice(result.value.removed === 0 ? '卡片已移除(合成图文件之前就不在了)。' : '卡片和它的合成图文件都已删除。')\n })()\n }, [api, store])\n\n /** Drop every board picture no card shows any more. */\n const pruneAssets = useCallback((): void => {\n void (async () => {\n const result = await api.pruneCanvasAssets()\n if (!result.ok) {\n showAssetNotice(`清理失败:${result.message}`)\n return\n }\n const { removed, bytes } = result.value\n showAssetNotice(removed === 0 ? '没有可清理的图片。' : `已清理 ${removed} 张没有用到的图片,释放 ${formatBytes(bytes)}。`)\n })()\n }, [api])\n\n /**\n * Pick one card from the layer list: select it, raise it, and bring it into view.\n *\n * A card that is completely covered by others cannot be clicked on the board at\n * all, so the list is the way back to it — and a card that is off-screen would be\n * selected invisibly, so the view follows it there too.\n */\n const pickFromLayers = useCallback((card: CanvasCard): void => {\n selectCard(card)\n const stage = stageSize()\n if (stage.width === 0 || stage.height === 0) return\n const centreX = card.x + card.width / 2\n const centreY = card.y + card.height / 2\n const screen = boardToScreen(viewport, centreX, centreY)\n const inside = screen.x > LAYER_REVEAL_MARGIN\n && screen.x < stage.width - LAYER_REVEAL_MARGIN\n && screen.y > LAYER_REVEAL_MARGIN\n && screen.y < stage.height - LAYER_REVEAL_MARGIN\n if (inside) return\n store.setViewport({\n ...viewport,\n x: Math.round(stage.width / 2 - centreX * viewport.k),\n y: Math.round(stage.height / 2 - centreY * viewport.k),\n })\n }, [selectCard, stageSize, store, viewport])\n\n /**\n * The selected card, when it is a picture the bar can act on. A note card has\n * no picture to send anywhere, so it gets no bar.\n */\n const selectedImageCard = useMemo(() => {\n if (selected === undefined) return undefined\n const card = board?.cards.find(candidate => candidate.id === selected)\n return card === undefined || card.kind !== 'image' || (card.file ?? '') === '' ? undefined : card\n }, [board?.cards, selected])\n /** The cards the two edit modes belong to, if they still exist. */\n const cropCard = board?.cards.find(candidate => candidate.id === cropping)\n const annotateCard = board?.cards.find(candidate => candidate.id === annotating)\n\n if (state.status === 'loading' && board === undefined) {\n return <p className={css.muted}>正在读取画布…</p>\n }\n if (board === undefined) {\n return <p className={css.muted}>{state.message ?? '没有打开的画布。'}</p>\n }\n\n const gridSize = 32 * viewport.k\n\n return (\n <div className={css.workspace}>\n <div className={css.toolbar}>\n <strong className={css.boardTitle} title=\"更换画布请用左侧列表\">{board.title}</strong>\n <Button onClick={() => { zoomCentre(-1) }} title=\"缩小\">−</Button>\n <span className={css.zoomLabel}>{Math.round(viewport.k * 100)}%</span>\n <Button onClick={() => { zoomCentre(1) }} title=\"放大\">+</Button>\n <Button onClick={() => { zoomExact(1) }} title=\"回到 100%\">100%</Button>\n <Button onClick={fit} title=\"让全部内容可见\">适应内容</Button>\n <span className={css.toolbarGap} />\n <Button onClick={addNote}>加备注</Button>\n <Button\n onClick={() => { setLayersOpen(open => !open) }}\n title=\"按图层从下到上列出板上的卡片;点一行就选中它并提到最上层\"\n data-dsh-seework-layers-toggle=\"\"\n >\n 图层\n </Button>\n <Button onClick={pruneAssets} title=\"删掉板上没有任何卡片引用的画布图片(标注/裁剪合成图)\">清理无用图片</Button>\n <Button\n variant=\"primary\"\n onClick={() => {\n onNeedLibrary()\n setPickerOpen(open => !open)\n }}\n >\n 从素材库添加\n </Button>\n <SaveState save={state.save} dirty={state.dirty} />\n </div>\n\n {assetNotice === undefined ? null : <p className={css.notice}>{assetNotice}</p>}\n\n {state.message !== undefined && state.save !== 'error'\n ? <p className={css.notice}>{state.message}</p>\n : null}\n\n <div\n className={css.surface}\n ref={surfaceRef}\n onPointerDown={beginPan}\n style={{ cursor: gestureRef.current?.kind === 'pan' ? 'grabbing' : 'default' }}\n >\n <div\n className={css.grid}\n style={{ backgroundSize: `${gridSize}px ${gridSize}px`, backgroundPosition: `${viewport.x}px ${viewport.y}px` }}\n />\n <div className={css.world} style={{ transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.k})` }}>\n {boardCards.map(card => (\n <CardView\n key={card.id}\n card={card}\n selected={selected === card.id}\n onSelect={() => { selectCard(card) }}\n onBeginMove={event => { beginMove(event, card) }}\n onBeginResize={event => { beginResize(event, card) }}\n onText={text => { store.updateCard(card.id, { text }) }}\n onRemove={() => { removeCard(card) }}\n badge={canvasOriginLabel(originOf(card))}\n removeHint={removeHint(card)}\n />\n ))}\n </div>\n {/* The layer list. A card buried under others cannot be clicked on the board\n at all, so this is the way back to it — and the only place the whole\n board's stacking order is visible at once. Top of the stack first. */}\n {layersOpen\n ? (\n <div\n className={css.layers}\n data-dsh-seework-layers=\"\"\n role=\"list\"\n aria-label=\"图层\"\n onPointerDown={event => { event.stopPropagation() }}\n >\n <p className={css.layersHead}>图层(上面的是最上层)</p>\n {[...boardCards].reverse().map((card, index) => (\n <button\n key={card.id}\n type=\"button\"\n role=\"listitem\"\n className={[css.layerRow, selected === card.id ? css.layerRowActive : ''].filter(Boolean).join(' ')}\n data-seework-layer-row={card.id}\n data-seework-layer-active={selected === card.id ? 'true' : undefined}\n onPointerDown={event => { event.stopPropagation() }}\n onClick={() => { pickFromLayers(card) }}\n >\n <span className={css.layerIndex}>{index + 1}</span>\n <span className={css.layerBadge}>{card.kind === 'image' ? canvasOriginLabel(originOf(card)) ?? '图片' : '备注'}</span>\n <span className={css.layerName}>{layerName(card)}</span>\n </button>\n ))}\n </div>\n )\n : null}\n\n {/* The command bar for the selected picture. It is HTML over the board,\n so it keeps its size at every zoom and never scales with the world.\n Hidden while an edit mode is open: those modes own the picture. */}\n {selectedImageCard === undefined || annotating !== undefined || cropping !== undefined\n ? null\n : (\n <NodeFloatingBar\n node={selectedImageCard}\n viewport={viewport}\n stageSize={stageSize()}\n onAddToConversation={() => {\n const file = selectedImageCard.file ?? ''\n if (file === '') return false\n return sendImageToConversation({\n url: canvasImageUrl(file, selectedImageCard.source),\n name: attachmentNameFor(file),\n })\n }}\n onAnnotate={() => { openMode('annotate', selectedImageCard) }}\n onCrop={() => { openMode('crop', selectedImageCard) }}\n />\n )}\n\n {/* Crop mode draws over the node in SCREEN coordinates, so its box and\n handles keep a constant size while the board is zoomed. */}\n {cropCard === undefined || modePicture === undefined\n ? null\n : (\n <CropOverlay\n node={nodeScreenRect(cropCard, viewport)}\n natural={modePicture.natural}\n onCancel={closeMode}\n onConfirm={rect => {\n void (async () => {\n const burned = await burnCrop(modePicture.url, rect)\n if (burned === undefined) return\n const size = croppedNodeSize(rect)\n const position = cropNodePosition(cropCard, size)\n await addBurnedCard(burned, { ...position, ...size }, 'crop')\n closeMode()\n })()\n }}\n />\n )}\n </div>\n\n {/* The annotation editor takes the whole panel: the picture is the work\n surface, and the board behind it would only compete for attention. */}\n {annotateCard === undefined || modePicture === undefined\n ? null\n : (\n <AnnotationEditor\n url={modePicture.url}\n natural={modePicture.natural}\n onCancel={closeMode}\n onSave={async dataUrl => {\n const image = await loadBurnedSize(dataUrl)\n await addBurnedCard(image, {\n x: annotateCard.x + annotateCard.width + 24,\n y: annotateCard.y,\n ...sizeKeepingAspect(annotateCard.width, image.width, image.height),\n }, 'annotation')\n closeMode()\n }}\n />\n )}\n {busy ? <p className={css.notice}>正在保存这张图…</p> : null}\n\n {pickerOpen\n ? (\n <LibraryPicker\n entries={library.entries}\n onPick={placeImage}\n onClose={() => { setPickerOpen(false) }}\n />\n )\n : null}\n </div>\n )\n}\n\n/** The save indicator. */\nfunction SaveState({ save, dirty }: { save: CanvasState['save']; dirty: boolean }): JSX.Element {\n const text = save === 'saving'\n ? '保存中…'\n : save === 'conflict'\n ? '已在别处被改动'\n : save === 'error'\n ? '保存失败'\n : dirty\n ? '待保存'\n : '已保存'\n const tone = save === 'conflict' ? css.saveWarn : save === 'error' ? css.saveError : dirty ? css.saveIdle : css.saveOk\n return <span className={`${css.saveState} ${tone}`}>{text}</span>\n}\n\n/** One card on the board. */\nfunction CardView({\n card,\n selected,\n badge,\n removeHint,\n onSelect,\n onBeginMove,\n onBeginResize,\n onText,\n onRemove,\n}: {\n card: CanvasCard\n selected: boolean\n /** Origin badge text (「标注合成」…), when the picture's source is known. */\n badge?: string | undefined\n /** What the ✕ promises to delete. */\n removeHint: string\n onSelect: () => void\n onBeginMove: (event: React.PointerEvent<HTMLElement>) => void\n onBeginResize: (event: React.PointerEvent<HTMLElement>) => void\n onText: (text: string) => void\n onRemove: () => void\n}): JSX.Element {\n return (\n <div\n data-seework-card={card.id}\n className={[css.card, selected ? css.cardSelected : ''].filter(Boolean).join(' ')}\n style={{ left: card.x, top: card.y, width: card.width, height: card.height, zIndex: Math.round(card.z) }}\n onPointerDown={onSelect}\n >\n <header className={css.cardBar} onPointerDown={onBeginMove} title=\"拖动移动\">\n <span className={css.cardKind} data-dsh-seework-card-origin={badge ?? ''}>\n {card.kind === 'image' ? badge ?? '图片' : '备注'}\n </span>\n <button\n type=\"button\"\n className={css.cardClose}\n title={removeHint}\n onPointerDown={event => { event.stopPropagation() }}\n onClick={onRemove}\n >\n ✕\n </button>\n </header>\n <div className={css.cardBody}>\n {card.kind === 'image' && card.file !== undefined\n ? <img className={css.cardImage} src={canvasImageUrl(card.file, card.source)} alt={card.prompt ?? ''} draggable={false} />\n : (\n <textarea\n className={css.cardText}\n value={card.text ?? ''}\n placeholder=\"写点什么…\"\n style={{ fontSize: card.fontSize ?? 16 }}\n onPointerDown={event => { event.stopPropagation() }}\n onChange={event => { onText(event.target.value) }}\n />\n )}\n </div>\n <span className={css.resizeHandle} onPointerDown={onBeginResize} title=\"拖动调整大小\" />\n </div>\n )\n}\n\n/** The picture picker, shown as a panel over the board. */\nfunction LibraryPicker({\n entries,\n onPick,\n onClose,\n}: {\n entries: LibraryEntry[]\n onPick: (entry: LibraryEntry, imageIndex: number) => void\n onClose: () => void\n}): JSX.Element {\n return (\n <>\n <div className={css.pickerBackdrop} onClick={onClose} />\n <div className={css.picker} role=\"dialog\" aria-label=\"从素材库添加\">\n <header className={css.pickerHeader}>\n <strong>从素材库添加</strong>\n <Button onClick={onClose}>关闭</Button>\n </header>\n {entries.length === 0\n ? <p className={css.muted}>素材库还是空的:先在对话里让 Agent 画一张。</p>\n : (\n <div className={css.pickerGrid}>\n {entries.flatMap(entry => entry.images.map((image, index) => (\n <button\n key={`${entry.id}-${index}`}\n type=\"button\"\n className={css.pickerItem}\n title={entry.prompt}\n onClick={() => { onPick(entry, index) }}\n >\n <img src={image.url} alt=\"\" loading=\"lazy\" />\n </button>\n )))}\n </div>\n )}\n </div>\n </>\n )\n}\n\n/** Board list + open/create/delete, shown beside the board. */\nexport function CanvasBoardList({ store }: { store: CanvasStore }): JSX.Element {\n const [tick, setTick] = useState(0)\n useEffect(() => store.subscribe(() => { setTick(value => value + 1) }), [store])\n void tick\n const state = store.getSnapshot()\n const [renaming, setRenaming] = useState(false)\n return ( <aside className={css.list}>\n <header className={css.listHeader}>\n <strong>画布</strong>\n <Button variant=\"primary\" onClick={() => { void store.open('') }}>新建</Button>\n </header>\n {state.boards.length === 0\n ? <p className={css.muted}>还没有画布。</p>\n : (\n <ul className={css.boardList}>\n {state.boards.map(summary => (\n <li key={summary.id}>\n <button\n type=\"button\"\n className={[css.boardRow, state.board?.id === summary.id ? css.boardRowActive : ''].filter(Boolean).join(' ')}\n onClick={() => { void store.open(summary.id) }}\n >\n <span className={css.boardName}>{summary.title}</span>\n <span className={css.boardMeta}>{summary.cardCount} 个卡片</span>\n </button>\n <button\n type=\"button\"\n className={css.boardDelete}\n title=\"删除这块画布\"\n onClick={() => {\n void store.deleteBoard(summary.id)\n }}\n >\n ✕\n </button>\n </li>\n ))}\n </ul>\n )}\n {state.board === undefined\n ? null\n : (\n <div className={css.listFooter}>\n {renaming\n ? (\n <TextInput\n defaultValue={state.board.title}\n autoFocus\n onBlur={event => {\n store.setTitle(event.target.value.trim() === '' ? state.board!.title : event.target.value.trim())\n setRenaming(false)\n void store.saveNow()\n }}\n onKeyDown={event => {\n if (event.key === 'Enter') (event.target as HTMLInputElement).blur()\n }}\n />\n )\n : <Button onClick={() => { setRenaming(true) }}>重命名当前画布</Button>}\n <span className={css.muted}>{state.boards.length} 块画布</span>\n </div>\n )}\n </aside>\n )\n}\n","/**\n * One dock for every SeeWork launcher.\n *\n * The surfaces are mounted independently (settings panel, library drawer, canvas\n * overlay), so their buttons cannot be laid out by a shared parent component.\n * Instead each surface renders its button into the dock element this module\n * owns, which is why a single owner exists at all: three buttons at fixed\n * coordinates would overlap, and each new surface would need another offset.\n */\n\nimport css from './surfaces.module.css'\n\n/** Marks the dock element. */\nexport const SURFACE_DOCK_ATTR = 'data-dsh-seework-dock'\n\n/**\n * The dock element, created on first use.\n *\n * Reuse is by attribute lookup, so a hot reload that mounts the surfaces again\n * does not leave a second dock behind.\n * @returns the dock element, or undefined when there is no document.\n */\nexport function surfaceDock(): HTMLElement | undefined {\n if (typeof document === 'undefined') return undefined\n // The attribute selector must pin the empty value: a bare `[attr]` matches\n // descendants too (the launcher buttons carry no such attribute, but the\n // selector reads as \"any element that has it\", which is how a nested lookup\n // could hand back the wrong node).\n const existing = document.querySelector<HTMLElement>(`[${SURFACE_DOCK_ATTR}=\"\"]`)\n if (existing !== null) return existing\n const dock = document.createElement('div')\n dock.setAttribute(SURFACE_DOCK_ATTR, '')\n dock.className = css.dock\n document.body.appendChild(dock)\n return dock\n}\n\n/**\n * Put one launcher button in the dock.\n * @param button - the button element (its classes come from the surface).\n * @returns disposer removing the button and the dock once it is empty.\n */\nexport function attachLauncher(button: HTMLElement): () => void {\n const dock = surfaceDock()\n if (dock === undefined) return () => {}\n dock.appendChild(button)\n return () => {\n button.remove()\n if (dock.childElementCount === 0) dock.remove()\n }\n}\n","/**\n * The DOM-mounted settings surface: the guaranteed way into the plugin's\n * settings when the shell does not expose a plugin-configuration slot to\n * third-party packages.\n *\n * It renders a quiet floating button in the lower-left corner of the viewport;\n * clicking it opens the same card the slot registration would render, inside a\n * local overlay. Nothing here depends on the shell's internal layout classes,\n * so it cannot break when the shell layout changes.\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport { SeeWorkApi } from './api.ts'\nimport { SeeWorkSettingsCard } from './SettingsCard.tsx'\nimport type { SeeWorkScope } from './settings-scope.ts'\nimport { attachLauncher } from './surfaces.ts'\nimport launcherCss from './surfaces.module.css'\nimport css from './settings-panel.module.css'\n\n/** Marks the mounted launcher (a stable hook for tests and DOM inspection). */\nexport const SETTINGS_LAUNCHER_ATTR = 'data-dsh-seework-settings-launcher'\n\n/** Cross-surface event: detail is the name of the surface that just opened. */\nexport const ACTIVATE_EVENT = 'dsh-seework-activate'\n\n/** The SeeWork surfaces subject to the single-open rule. */\nexport type SeeWorkSurface = 'settings' | 'library' | 'canvas'\n\n/**\n * Ask sibling SeeWork surfaces to step aside, and report which one is opening.\n * The DOM-mounted surfaces are siblings in `document.body`, so this is how they\n * avoid stacking on top of each other without sharing React state.\n * @param name - the surface that is opening.\n */\nexport function announceActivation(name: SeeWorkSurface): void {\n document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: name }))\n}\n\n/**\n * Ask one SeeWork surface to open, from anywhere.\n *\n * The header launcher lives in the shell's toolbar rather than in this plugin's\n * tree, so it cannot call a component's `setOpen` directly; announcing the\n * surface is the same path its own launcher takes, which means one code path\n * opens a surface however it was asked for.\n * @param surface - the surface to bring up.\n */\nexport function requestSurface(surface: SeeWorkSurface): void {\n if (typeof document === 'undefined') return\n announceActivation(surface)\n}\n\n/** The surface whose activation was announced last, for launcher highlighting. */\nlet activeSurface: SeeWorkSurface | undefined\n\n/**\n * The surface currently showing, as far as the shared activation event knows.\n * @returns the surface name, or undefined while none has been announced.\n */\nexport function activeSeeWorkSurface(): SeeWorkSurface | undefined {\n return activeSurface\n}\n\n/**\n * Follow the activation stream.\n * @param listener - called after each announcement with the surface it names.\n * @returns disposer removing the listener.\n */\nexport function onActivation(listener: (surface: SeeWorkSurface) => void): () => void {\n const handler = (event: Event): void => {\n const surface = (event as CustomEvent<SeeWorkSurface>).detail\n activeSurface = surface\n listener(surface)\n }\n document.addEventListener(ACTIVATE_EVENT, handler)\n return () => { document.removeEventListener(ACTIVATE_EVENT, handler) }\n}\n\n/** Mount the launcher + overlay.\n * @param scope - the bound settings scope.\n * @param api - the plugin route client.\n * @returns disposer unmounting everything this created.\n */\nexport function mountSettingsPanel(scope: SeeWorkScope, api: SeeWorkApi): () => void {\n const launcher = document.createElement('button')\n launcher.type = 'button'\n launcher.setAttribute(SETTINGS_LAUNCHER_ATTR, '')\n launcher.className = launcherCss.launcher\n launcher.textContent = 'SeeWork 设置'\n launcher.title = '设置 SeeAI Hub 地址、API Key 与图片模型'\n\n const overlay = document.createElement('div')\n overlay.className = css.overlay\n overlay.hidden = true\n overlay.setAttribute('role', 'dialog')\n overlay.setAttribute('aria-modal', 'true')\n overlay.setAttribute('aria-label', 'SeeWork 设置')\n\n const panel = document.createElement('div')\n panel.className = css.panel\n overlay.appendChild(panel)\n document.body.append(overlay)\n const detachLauncher = attachLauncher(launcher)\n\n const root: Root = createRoot(panel)\n root.render(<SeeWorkSettingsCard scope={scope} api={api} />)\n\n const close = (): void => { overlay.hidden = true }\n const onLauncher = (): void => {\n const next = overlay.hidden\n overlay.hidden = !next\n // Only one SeeWork surface should be open at a time; opening this one\n // retires the library drawer.\n if (next) announceActivation('settings')\n }\n const onOtherActivate = (event: Event): void => {\n if ((event as CustomEvent).detail === 'library') close()\n }\n const onOverlay = (event: MouseEvent): void => {\n if (event.target === overlay) close()\n }\n const onKey = (event: KeyboardEvent): void => {\n if (event.key === 'Escape' && !overlay.hidden) close()\n }\n launcher.addEventListener('click', onLauncher)\n overlay.addEventListener('click', onOverlay)\n document.addEventListener('keydown', onKey)\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n\n return () => {\n launcher.removeEventListener('click', onLauncher)\n overlay.removeEventListener('click', onOverlay)\n document.removeEventListener('keydown', onKey)\n document.removeEventListener(ACTIVATE_EVENT, onOtherActivate)\n root.unmount()\n detachLauncher()\n overlay.remove()\n }\n}\n","/**\n * SeeWork's entry points in the shell's conversation header (top-right).\n *\n * The shell owns that toolbar, so these are registered into its utilities slot\n * rather than mounted on `document.body`. That is deliberate: buttons floating\n * over the viewport cover whatever the shell puts in the same corner, and the\n * header is where a user looks for \"open the side panel\".\n *\n * Degradation: a shell without the slot simply never calls the injection\n * callback, in which case the floating dock from `surfaces.ts` stays in place —\n * a plugin must not become unreachable because a slot moved.\n */\n\nimport { useEffect, useState } from 'react'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { announceActivation, onActivation, requestSurface, type SeeWorkSurface } from './settings-panel.tsx'\nimport css from './header-launchers.module.css'\n\n/**\n * Who is visibly showing, as reported by the surfaces themselves.\n *\n * The launcher cannot infer this from the activation event alone: a surface can\n * be closed from its own chrome (the drawer's 收起), and only the surface knows\n * that happened. Registration is the surface's job; the launcher only asks.\n */\nconst openProbes = new Map<SeeWorkSurface, () => boolean>()\n\n/**\n * Report whether a surface is currently showing.\n * @param surface - the surface.\n * @param probe - reads the surface's own open state.\n * @returns disposer retiring the report.\n */\nexport function reportSurfaceOpen(surface: SeeWorkSurface, probe: () => boolean): () => void {\n openProbes.set(surface, probe)\n return () => {\n if (openProbes.get(surface) === probe) openProbes.delete(surface)\n }\n}\n\n/**\n * Is that surface up right now?\n * @param surface - the surface to ask about.\n * @returns true when the surface is showing.\n */\nexport function isSurfaceOpen(surface: SeeWorkSurface): boolean {\n return openProbes.get(surface)?.() === true\n}\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface SlotMap {\n /**\n * Right-aligned conversation-header utilities. The shell declares it as a\n * `list`, so this plugin's entry sits beside the shell's own controls\n * instead of taking the header's single far-right corner.\n */\n 'conversation.session.header.utilities': { kind: 'list'; scope: 'session'; owner: { children?: never } }\n }\n}\n\n/** One launcher button: the surface it opens and how it reads. */\ninterface LauncherTarget {\n surface: SeeWorkSurface\n label: string\n title: string\n}\n\n/** The two entries, in display order. */\nexport const LAUNCHER_TARGETS: readonly LauncherTarget[] = [\n { surface: 'library', label: '素材库', title: '查看用 SeeWork 生成过的图片' },\n { surface: 'canvas', label: '画布', title: '把生成过的图片摆到画布上' },\n]\n\n/** Props the shell hands a utilities entry (it supplies nothing of its own). */\nexport interface HeaderLaunchersProps {\n children?: never\n}\n\n/**\n * How a launcher changes the state of the surface behind it.\n *\n * Production passes the sidebar-backed controller; a shell without the right\n * column passes the floating-surface fallback instead, which is why this is an\n * injected face rather than a direct call.\n */\nexport interface LauncherControls {\n /** Show the surface (expanding the column when it is collapsed). */\n open: (surface: SeeWorkSurface) => boolean\n /** Put the surface away when it is the visible one. */\n close: (surface: SeeWorkSurface) => boolean\n /** Whether that surface is showing right now. */\n isOpen: (surface: SeeWorkSurface) => boolean\n}\n\n/** What the launchers module needs from the composition root. */\nexport interface HeaderLauncherOptions {\n controls?: LauncherControls\n}\n\n/** The controls in force; the floating-surface fallback until proven otherwise. */\nlet controls: LauncherControls | undefined\n\n/**\n * Whether the header buttons are on screen.\n *\n * The conversation header only exists inside a session, so its buttons come and\n * go: on the home screen there are none, which is why the floating dock must\n * come back rather than be torn down for good.\n */\nlet headerPresent = false\nconst presenceListeners = new Set<(present: boolean) => void>()\n\n/**\n * Report whether the header's SeeWork buttons are mounted (called by the\n * component itself, so the answer is what is actually rendered).\n * @param present - true while the buttons are on screen.\n */\nexport function reportHeaderPresence(present: boolean): void {\n if (headerPresent === present) return\n headerPresent = present\n for (const listener of [...presenceListeners]) listener(present)\n}\n\n/**\n * Watch the header buttons' presence.\n * @param listener - called now and on every change.\n * @returns disposer removing the listener.\n */\nexport function onHeaderPresence(listener: (present: boolean) => void): () => void {\n presenceListeners.add(listener)\n listener(headerPresent)\n return () => { presenceListeners.delete(listener) }\n}\n\n/**\n * Install the launcher controls.\n * @param next - the controls to use.\n * @returns disposer restoring the previous set.\n */\nexport function setLauncherControls(next: LauncherControls | undefined): () => void {\n const previous = controls\n controls = next\n return () => { controls = previous }\n}\n\n/**\n * The header buttons.\n *\n * The open surface is tracked from the shared activation event rather than from\n * the surfaces themselves, because those are mounted as siblings in\n * `document.body` and share no React state with the header. Pressing the button\n * of the surface that is already up puts it away again.\n */\nexport function HeaderLaunchers(_props: HeaderLaunchersProps): JSX.Element {\n const [active, setActive] = useState<SeeWorkSurface | undefined>(undefined)\n useEffect(() => onActivation(surface => { setActive(surface) }), [])\n // The buttons' own lifetime is the truthful \"the header is here\" signal: it\n // covers entering and leaving a session without guessing from the DOM.\n useEffect(() => {\n reportHeaderPresence(true)\n return () => { reportHeaderPresence(false) }\n }, [])\n const press = (surface: SeeWorkSurface): void => {\n const current = controls\n if (current !== undefined) {\n if (current.isOpen(surface)) current.close(surface)\n else current.open(surface)\n return\n }\n if (isSurfaceOpen(surface)) {\n // Floating fallback: closing is the same shape of message as opening — a\n // surface whose name is not announced stands down, and announcing a\n // neighbour keeps \"exactly one SeeWork surface is up\" true.\n const other = LAUNCHER_TARGETS.find(target => target.surface !== surface)\n announceActivation(other?.surface ?? 'settings')\n return\n }\n requestSurface(surface)\n }\n return (\n <div className={css.group} data-dsh-seework-header-launchers=\"\">\n {LAUNCHER_TARGETS.map(target => (\n <button\n key={target.surface}\n type=\"button\"\n className={css.button}\n data-surface={target.surface}\n data-active={active === target.surface ? 'true' : undefined}\n title={target.title}\n aria-expanded={active === target.surface}\n onClick={() => { press(target.surface) }}\n >\n {target.label}\n </button>\n ))}\n </div>\n )\n}\n","/**\n * Mount point for the canvas surface.\n *\n * The canvas opens as a full-surface overlay rather than a drawer: a board\n * needs room, and the user is arranging pictures rather than reading a list.\n * Its launcher sits in the shared SeeWork dock, and the shared activation event\n * keeps only one SeeWork surface open at a time.\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { SeeWorkApi } from './api.ts'\nimport { LibraryStore } from './library-store.ts'\nimport { CanvasStore } from './canvas-store.ts'\nimport { CanvasBoard, CanvasBoardList } from './CanvasBoard.tsx'\nimport { reportSurfaceOpen } from './header-launchers.tsx'\nimport { ACTIVATE_EVENT, announceActivation, onActivation } from './settings-panel.tsx'\nimport { attachLauncher } from './surfaces.ts'\nimport launcherCss from './surfaces.module.css'\nimport css from './canvas-shell.module.css'\n\n/** Marks the canvas launcher. */\nexport const CANVAS_LAUNCHER_ATTR = 'data-dsh-seework-canvas-launcher'\n\n/** Everything the canvas surface owns, mounted into the dock's host element. */\nfunction CanvasSurface({ library, store, launcher }: {\n library: LibraryStore\n store: CanvasStore\n launcher: HTMLElement\n}): JSX.Element | null {\n const [open, setOpen] = useState(false)\n const openRef = useRef(false)\n\n /** Open or close, doing whatever the transition needs. */\n const setSurfaceOpen = useCallback((next: boolean): void => {\n if (openRef.current === next) return\n openRef.current = next\n setOpen(next)\n if (!next) {\n // Leaving the surface saves what is unsaved; the store survives so\n // reopening shows the same board.\n void store.close()\n return\n }\n announceActivation('canvas')\n void library.refresh()\n if (store.getSnapshot().board !== undefined) {\n void store.refreshList()\n return\n }\n void (async () => {\n await store.refreshList()\n const first = store.getSnapshot().boards[0]\n await store.open(first?.id ?? '')\n })()\n }, [library, store])\n\n // The launcher lives in the dock, outside this tree, so its label and click\n // handler are wired imperatively.\n useEffect(() => {\n const render = (): void => {\n const count = store.getSnapshot().boards.length\n launcher.textContent = count === 0 ? '画布' : `画布 ${count}`\n launcher.title = '把生成过的图片摆到画布上'\n launcher.setAttribute('aria-expanded', String(openRef.current))\n }\n render()\n const stop = store.subscribe(render)\n const onClick = (): void => { setSurfaceOpen(!openRef.current) }\n launcher.addEventListener('click', onClick)\n return () => {\n stop()\n launcher.removeEventListener('click', onClick)\n }\n }, [launcher, store, setSurfaceOpen])\n\n // Only one SeeWork surface is open at a time, and announcing this surface is\n // how anything outside this tree (the conversation header) asks for it.\n useEffect(() => {\n const onOtherActivate = (event: Event): void => {\n if ((event as CustomEvent).detail !== 'canvas') setSurfaceOpen(false)\n }\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n return () => { document.removeEventListener(ACTIVATE_EVENT, onOtherActivate) }\n }, [setSurfaceOpen])\n\n useEffect(() => onActivation(surface => {\n if (surface === 'canvas') setSurfaceOpen(true)\n }), [setSurfaceOpen])\n\n // The header button toggles, so it needs the surface's real open state.\n useEffect(() => reportSurfaceOpen('canvas', () => openRef.current), [])\n\n // Mounted only while open. Hiding it with `display: none` instead would leave\n // a second live board over the same store: every card rendered twice, two sets\n // of pointer listeners, and a 0×0 \"stage\" whose measurement could overwrite the\n // size the visible board (the sidebar tab) just reported.\n if (!open) return null\n return (\n <div className={css.overlay} role=\"dialog\" aria-modal=\"true\" aria-label=\"SeeWork 画布\">\n <header className={css.header}>\n <strong>SeeWork 画布</strong>\n <span className={css.headerHint}>把素材库里的图片摆到板上:拖标题栏移动、右下角调整大小、滚轮缩放;不做连线。</span>\n <button type=\"button\" className={css.close} onClick={() => { setSurfaceOpen(false) }}>关闭</button>\n </header>\n <div className={css.body}>\n <CanvasBoardList store={store} />\n <CanvasBoard\n store={store}\n library={{ entries: library.getSnapshot().entries }}\n onNeedLibrary={() => { void library.refresh() }}\n />\n </div>\n </div>\n )\n}\n\n/**\n * Mount the canvas launcher and fallback overlay.\n * @param api - the plugin route client (unused here; the store is resolved by the caller).\n * @param library - the shared material-library store (the picture source).\n * @param store - the shared canvas store, also rendered by the right-sidebar tab.\n * @returns disposer unmounting everything this created.\n */\nexport function mountCanvasSurfaces(api: SeeWorkApi, library: LibraryStore, store: CanvasStore): () => void {\n void api\n const host = document.createElement('div')\n document.body.appendChild(host)\n const launcher = document.createElement('button')\n launcher.type = 'button'\n launcher.setAttribute(CANVAS_LAUNCHER_ATTR, '')\n launcher.className = launcherCss.launcher\n const detachLauncher = attachLauncher(launcher)\n\n const root: Root = createRoot(host)\n root.render(<CanvasSurface library={library} store={store} launcher={launcher} />)\n return () => {\n root.unmount()\n detachLauncher()\n host.remove()\n }\n}\n\n/** Re-exported so the composition root can measure the board without importing it. */\nexport { stageSizeFor } from './CanvasBoard.tsx'\n","/**\n * Noticing a finished generation from the browser half.\n *\n * The host gives the page no push channel: an agent-side `generate_image` writes\n * a library entry and nothing tells the tab. So the page polls the library's\n * *identity* (newest entry id + total, a few dozen bytes) and, when it changes,\n * refreshes the shared library store — which is what updates the library tab, the\n * canvas's picture picker, and the launcher's count.\n *\n * What it deliberately does NOT do is touch the canvas. Auto-opening the column\n * and dropping the new picture onto the open board was the earlier behaviour, and\n * it turned a background generation into the user's screen rearranging itself;\n * placement is the user's call now (the canvas's own 「从素材库选图」)。 The\n * watcher therefore does not even receive the canvas store.\n *\n * Polling is the honest option here: a websocket into the shell is private, and\n * an SSE route would still be a long-lived connection the console is happy to\n * burn. The interval is skipped while the page is hidden, and a failed poll\n * changes nothing — a transient 500 from the host must not disturb the page.\n */\n\nimport type { SeeWorkApi } from './api.ts'\nimport type { LibraryStore } from './library-store.ts'\n\n/** How often the page asks the host what the newest entry is. */\nexport const WATCH_INTERVAL_MS = 2000\n\n/** What the watcher needs from the composition root. */\nexport interface GenerationWatchDeps {\n api: SeeWorkApi\n library: LibraryStore\n /** Poll interval; tests shorten it. */\n intervalMs?: number\n /** Timer injectors, so tests can drive the loop without real time. */\n setTimer?: (handler: () => void, ms: number) => ReturnType<typeof setInterval>\n clearTimer?: (handle: ReturnType<typeof setInterval>) => void\n}\n\n/**\n * Start watching for generations that finished in the host.\n * @param deps - the api and the shared library store.\n * @returns disposer stopping the watch.\n */\nexport function watchGenerations(deps: GenerationWatchDeps): () => void {\n const setTimer = deps.setTimer ?? ((handler, ms) => setInterval(handler, ms))\n const clearTimer = deps.clearTimer ?? (handle => { clearInterval(handle) })\n /** Newest id already accounted for. */\n let newestId: string | undefined\n /** False until the first successful look, which only adopts. */\n let observed = false\n let stopped = false\n let polling = false\n\n const poll = async (): Promise<void> => {\n if (stopped || polling) return\n // A hidden page is not being looked at; skipping keeps an idle background tab\n // quiet. The next poll after the page becomes visible catches up.\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return\n polling = true\n try {\n const result = await deps.api.libraryHead()\n if (stopped) return\n if (!result.ok) return\n const head = result.value\n if (!observed) {\n // First look: adopt what is already there. A page load must never treat\n // the whole existing library as brand new.\n observed = true\n newestId = head.newestId\n return\n }\n // An empty library reports no id: the FIRST generation into it is a change\n // from `undefined`, not something to adopt away.\n if (head.newestId === undefined || head.newestId === newestId) return\n newestId = head.newestId\n await deps.library.refresh()\n } finally {\n polling = false\n }\n }\n\n const handle = setTimer(() => { void poll() }, deps.intervalMs ?? WATCH_INTERVAL_MS)\n return () => {\n stopped = true\n clearTimer(handle)\n }\n}\n","/**\n * Browser-side access to the local material library plus the pure view helpers\n * the sidebar filters with.\n *\n * The host serves every entry (metadata + image URLs) from one route, so the\n * sidebar filters locally: that keeps typing in the search box instant and\n * means a filter never costs a round trip.\n */\n\nimport type { LibraryEntry, LibraryListResult } from '../protocol.ts'\nimport { SeeWorkApi } from './api.ts'\nimport { SnapshotStore } from './snapshot-store.ts'\n\n/** Library state as the sidebar renders it. */\nexport interface LibraryState {\n status: 'loading' | 'ready' | 'error'\n entries: LibraryEntry[]\n imageCount: number\n dataRoot: string\n /** Human-readable reason when `status` is 'error'. */\n error?: string\n}\n\n/** What the sidebar needs from the library. */\nexport class LibraryStore {\n private readonly store = new SnapshotStore<LibraryState>({\n status: 'loading',\n entries: [],\n imageCount: 0,\n dataRoot: '',\n })\n private inFlight: Promise<void> | undefined\n\n constructor(private readonly api: SeeWorkApi) {}\n\n getSnapshot(): LibraryState {\n return this.store.getSnapshot()\n }\n\n subscribe(listener: () => void): () => void {\n return this.store.subscribe(listener)\n }\n\n /** Re-read the library; concurrent calls share one request. */\n refresh(): Promise<void> {\n if (this.inFlight !== undefined) return this.inFlight\n const pending = this.load().finally(() => {\n if (this.inFlight === pending) this.inFlight = undefined\n })\n this.inFlight = pending\n return pending\n }\n\n /**\n * Delete one entry and its files.\n * @returns true when the host accepted the deletion.\n */\n async remove(id: string): Promise<boolean> {\n const result = await this.api.removeEntry(id)\n if (!result.ok) {\n this.store.update(draft => {\n draft.status = 'error'\n draft.error = result.message\n })\n return false\n }\n await this.refresh()\n return true\n }\n\n /** Delete every entry. */\n async clear(): Promise<boolean> {\n const result = await this.api.clearLibrary()\n if (!result.ok) {\n this.store.update(draft => {\n draft.status = 'error'\n draft.error = result.message\n })\n return false\n }\n await this.refresh()\n return true\n }\n\n private async load(): Promise<void> {\n const result = await this.api.library()\n if (!result.ok) {\n // A failed refresh keeps the entries already on screen: a transient\n // bridge hiccup must not blank the sidebar.\n this.store.update(draft => {\n draft.status = 'error'\n draft.error = result.message\n })\n return\n }\n const value: LibraryListResult = result.value\n this.store.update(draft => {\n draft.status = 'ready'\n draft.entries = value.entries\n draft.imageCount = value.imageCount\n draft.dataRoot = value.dataRoot\n delete draft.error\n })\n }\n}\n\n/** What a generation was for, as the filter chips present it. */\nexport type SourceFilter = 'all' | LibraryEntry['source']\n\n/** One active filter set. */\nexport interface LibraryFilter {\n /** Free text matched against the prompt. */\n query: string\n /** Model id, or '' for every model. */\n model: string\n source: SourceFilter\n /** Only entries that day, as `YYYY-MM-DD`, or '' for every day. */\n day: string\n}\n\n/** The empty filter (everything). */\nexport const EMPTY_FILTER: LibraryFilter = { query: '', model: '', source: 'all', day: '' }\n\n/** Model ids present in the library, most used first, for the filter menu. */\nexport function libraryModels(entries: LibraryEntry[]): Array<{ id: string; count: number }> {\n const counts = new Map<string, number>()\n for (const entry of entries) counts.set(entry.model, (counts.get(entry.model) ?? 0) + 1)\n return [...counts.entries()]\n .map(([id, count]) => ({ id, count }))\n .sort((left, right) => right.count - left.count || left.id.localeCompare(right.id))\n}\n\n/** Apply the sidebar filter (case-insensitive substring match on the prompt). */\nexport function filterEntries(entries: LibraryEntry[], filter: LibraryFilter): LibraryEntry[] {\n const query = filter.query.trim().toLowerCase()\n return entries.filter(entry => {\n if (filter.model !== '' && entry.model !== filter.model) return false\n if (filter.source !== 'all' && entry.source !== filter.source) return false\n if (filter.day !== '' && dayOf(entry.createdAt) !== filter.day) return false\n if (query !== '' && !entry.prompt.toLowerCase().includes(query)) return false\n return true\n })\n}\n\n/** Local calendar day of a timestamp, as `YYYY-MM-DD`. */\nexport function dayOf(timestamp: number): string {\n const date = new Date(timestamp)\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n return `${date.getFullYear()}-${month}-${day}`\n}\n\n/** One day's entries, for the sidebar's date grouping. */\nexport interface LibraryDay {\n day: string\n /** Human label: 今天 / 昨天 / the date. */\n label: string\n entries: LibraryEntry[]\n}\n\n/** Group entries into day sections, newest first (input order is preserved). */\nexport function groupByDay(entries: LibraryEntry[], now = Date.now()): LibraryDay[] {\n const today = dayOf(now)\n const yesterday = dayOf(now - 24 * 60 * 60 * 1000)\n const groups = new Map<string, LibraryEntry[]>()\n for (const entry of entries) {\n const day = dayOf(entry.createdAt)\n const bucket = groups.get(day)\n if (bucket === undefined) groups.set(day, [entry])\n else bucket.push(entry)\n }\n return [...groups.entries()]\n .sort((left, right) => right[0].localeCompare(left[0]))\n .map(([day, bucket]) => ({\n day,\n label: day === today ? '今天' : day === yesterday ? '昨天' : day,\n entries: bucket,\n }))\n}\n\n/** Prompt text clipped to a preview length. */\nexport function promptPreview(prompt: string, max = 60): string {\n const flat = prompt.replace(/\\s+/g, ' ').trim()\n return flat.length <= max ? flat : `${flat.slice(0, max - 1)}…`\n}\n\n/** Total bytes the library occupies, when every image reports a size. */\nexport function entryCost(entry: LibraryEntry): string | undefined {\n return entry.cost === undefined ? undefined : `¥${entry.cost.toFixed(2)}`\n}\n","/**\n * 「加到画布」: put the picture the user is looking at onto a board.\n *\n * The automatic version of this (a finished generation opening the column and\n * dropping itself onto the board) was removed on request — a background\n * generation must not rearrange the screen. What remains is the deliberate,\n * one-click version: the user says \"this one\", and only then does the plugin\n * place the card and bring the canvas up so the result is visible.\n *\n * The button lives on surfaces this plugin owns (the enlarged-image lightbox and\n * the conversation card), and they reach the canvas through the injected\n * {@link CanvasAddFace}: those modules are mounted inside shell render trees, so\n * they cannot import the composition root — the same reason the header launchers\n * take their controls as a face.\n *\n * Placement reuses the picker's maths (centre of the visible stage, card sized\n * to the picture's own aspect ratio) and, like the old automatic path, waits\n * briefly for the revealed board to report a usable stage: a card placed against\n * a zero stage lands in the viewport's corner.\n */\n\nimport { centeredImageCard, stageCentre, usableStage } from '../canvas-placement.ts'\nimport type { CanvasCardOrigin } from '../protocol.ts'\nimport type { LibraryStore } from './library-store.ts'\nimport { unusedCardId, type CanvasStore } from './canvas-store.ts'\n\n/** How long one measurement step waits, and how many are taken before giving up. */\nexport const STAGE_WAIT_STEP_MS = 50\nexport const STAGE_WAIT_ATTEMPTS = 30\n\n/** One picture to put on a board, as the callers know it. */\nexport interface CanvasAddTarget {\n /** Library file name the board's card will reference. */\n file: string\n width?: number | undefined\n height?: number | undefined\n /** Model that produced it, shown on the card. */\n model?: string | undefined\n /** Prompt snapshot, shown on the card. */\n prompt?: string | undefined\n /** Where it came from, shown as the card badge (read off the library otherwise). */\n origin?: CanvasCardOrigin | undefined\n}\n\n/** What a surface needs to offer 「加到画布」. */\nexport interface CanvasAddFace {\n /**\n * Place one picture and bring the canvas into view.\n * @param target - the picture to place.\n * @returns true once a card was added.\n */\n add: (target: CanvasAddTarget) => Promise<boolean>\n}\n\n/** The face in force, or undefined while the composition root has not set one. */\nlet face: CanvasAddFace | undefined\n\n/**\n * Install the face the surfaces call.\n * @param next - the face, or undefined to remove it.\n * @returns disposer restoring the previous face.\n */\nexport function setCanvasAddFace(next: CanvasAddFace | undefined): () => void {\n const previous = face\n face = next\n return () => { face = previous }\n}\n\n/** Whether 「加到画布」 can do anything right now (surfaces hide the button when not). */\nexport function canvasAddAvailable(): boolean {\n return face !== undefined\n}\n\n/**\n * Ask for one picture to be placed.\n *\n * Fire-and-forget by design: a click on a button must not make the caller wait,\n * and a failure leaves the picture exactly where it already is — in the library,\n * which is where the user would look for it anyway.\n *\n * @param target - the picture to place.\n */\nexport function addToCanvas(target: CanvasAddTarget): void {\n const current = face\n if (current === undefined) return\n void current.add(target).catch(() => {})\n}\n\n/** What the controller needs from the composition root. */\nexport interface CanvasAddDeps {\n library: LibraryStore\n canvas: CanvasStore\n /** Bring the canvas into view (right-sidebar tab, or the floating overlay). */\n reveal: () => void\n /** Visible stage size, measured from the board that is actually on screen. */\n stageSize: () => { width: number; height: number }\n /** Sleep seam; tests pass a no-op. */\n sleep?: (ms: number) => Promise<void>\n}\n\n/**\n * Build the face the surfaces use.\n * @param deps - both stores, how to reveal the canvas, and the stage measurement.\n * @returns the face.\n */\nexport function createCanvasAddFace(deps: CanvasAddDeps): CanvasAddFace {\n const sleep = deps.sleep ?? (ms => new Promise<void>(resolve => { setTimeout(resolve, ms) }))\n\n /** An open board, creating one when the user has none yet. */\n const openBoard = async (): Promise<void> => {\n if (deps.canvas.getSnapshot().board !== undefined) return\n await deps.canvas.refreshList()\n const first = deps.canvas.getSnapshot().boards[0]\n // 'open' with an empty id creates a board; the canvas tab would do the same\n // a moment later, so an empty deployment behaves like a fresh canvas.\n await deps.canvas.open(first?.id ?? '')\n }\n\n /**\n * Wait briefly for the board on screen to report its size.\n *\n * Counted in attempts rather than wall-clock: the step is the same 50 ms either\n * way, and a caller that injects `sleep` (tests) drives it without real time.\n */\n const waitForStage = async (): Promise<void> => {\n for (let attempt = 0; attempt < STAGE_WAIT_ATTEMPTS; attempt += 1) {\n const size = deps.stageSize()\n if (size.width > 0 && size.height > 0) return\n await sleep(STAGE_WAIT_STEP_MS)\n }\n }\n\n /** Fill in anything the caller did not know, from the library record. */\n const complete = (target: CanvasAddTarget): CanvasAddTarget => {\n if (target.width !== undefined && target.height !== undefined && target.origin !== undefined) return target\n const entry = deps.library.getSnapshot().entries.find(candidate =>\n candidate.images.some(image => image.file === target.file))\n if (entry === undefined) return target\n const image = entry.images.find(candidate => candidate.file === target.file)\n return {\n ...target,\n width: target.width ?? image?.width,\n height: target.height ?? image?.height,\n model: target.model ?? entry.model,\n prompt: target.prompt ?? entry.prompt,\n origin: target.origin ?? (entry.source === 'panel' ? 'panel' : 'chat'),\n }\n }\n\n return {\n async add(target) {\n await openBoard()\n // Reveal BEFORE placing: the card goes to the middle of what is on screen,\n // and only a board that is up can say how big that is.\n deps.reveal()\n await waitForStage()\n const board = deps.canvas.getSnapshot().board\n if (board === undefined) return false\n const picture = complete(target)\n const centre = stageCentre(board.viewport, usableStage(deps.stageSize()))\n deps.canvas.addCards([centeredImageCard({\n file: picture.file,\n width: picture.width,\n height: picture.height,\n model: picture.model,\n prompt: picture.prompt,\n origin: picture.origin,\n }, centre, unusedCardId(board))])\n // Persist right away: the user is watching the board, and the next thing\n // they may do is close the panel.\n await deps.canvas.saveNow()\n return true\n },\n }\n}\n\n/**\n * The library file a plugin image URL serves, when it is one.\n * @param src - an image URL (relative or absolute).\n * @returns the file name, or undefined for another route.\n */\nexport function libraryFileFromUrl(src: string): string | undefined {\n const marker = '/api/dsh-seework/library/image/'\n const at = src.indexOf(marker)\n if (at < 0) return undefined\n const file = src.slice(at + marker.length).split(/[?#]/)[0] ?? ''\n try {\n return file === '' ? undefined : decodeURIComponent(file)\n } catch {\n return undefined\n }\n}\n","/**\n * The large preview for one material-library entry: full-size images with\n * paging, the complete prompt with a copy action, the generation parameters,\n * and the destructive actions.\n *\n * It reads the entry out of the live library state rather than taking a copy,\n * so a refresh or a deletion elsewhere is reflected here instead of showing a\n * stale record.\n */\n\nimport type { LibraryEntry } from '../protocol.ts'\nimport { useEffect, useState } from 'react'\nimport { addToCanvas, canvasAddAvailable } from './canvas-add.ts'\nimport { attachmentNameFor, composerAvailable, sendImageToConversation } from './composer-draft.ts'\nimport { entryCost } from './library-store.ts'\nimport type { LibraryStore } from './library-store.ts'\nimport { Button, copyText } from './controls.tsx'\nimport css from './library-detail.module.css'\n\n/** Human label for where a generation came from. */\nexport function SourceBadge({ source }: { source: LibraryEntry['source'] }): JSX.Element {\n const label = source === 'agent' ? '对话' : source === 'canvas' ? '画布' : '面板'\n return <span className={`${css.badge} ${source === 'agent' ? css.badgeAgent : source === 'canvas' ? css.badgeCanvas : css.badgePanel}`}>{label}</span>\n}\n\n/** Props for the detail overlay. */\nexport interface LibraryDetailProps {\n store: LibraryStore\n /** Entry to show; the overlay closes itself when the id disappears. */\n entryId: string\n onClose: () => void\n}\n\n/** The detail overlay. */\nexport function LibraryDetail({ store, entryId, onClose }: LibraryDetailProps): JSX.Element | null {\n const [tick, setTick] = useState(0)\n useEffect(() => store.subscribe(() => { setTick(value => value + 1) }), [store])\n void tick\n const [index, setIndex] = useState(0)\n const [copied, setCopied] = useState(false)\n const [confirming, setConfirming] = useState(false)\n\n const entry = store.getSnapshot().entries.find(candidate => candidate.id === entryId)\n /** Whether the plugin can place pictures right now (no canvas face, no action). */\n const addable = canvasAddAvailable()\n /** Whether a session-scope tab is mounted to hand the picture to (see composer-draft). */\n const toComposer = composerAvailable()\n const [composerState, setComposerState] = useState<'idle' | 'sent' | 'failed'>('idle')\n\n /** Hand the picture on screen to the conversation composer. */\n const sendCurrentToComposer = async (): Promise<void> => {\n if (image === undefined) return\n const ok = await sendImageToConversation({\n url: image.url,\n name: attachmentNameFor(image.file),\n }).catch(() => false)\n setComposerState(ok ? 'sent' : 'failed')\n setTimeout(() => { setComposerState('idle') }, 2500)\n }\n useEffect(() => {\n const onKey = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') onClose()\n if (entry === undefined) return\n if (event.key === 'ArrowRight') setIndex(current => Math.min(entry.images.length - 1, current + 1))\n if (event.key === 'ArrowLeft') setIndex(current => Math.max(0, current - 1))\n }\n document.addEventListener('keydown', onKey)\n return () => { document.removeEventListener('keydown', onKey) }\n }, [entry, onClose])\n\n if (entry === undefined) {\n // Deleted while open (here or in another window): nothing left to show.\n return null\n }\n const image = entry.images[Math.min(index, entry.images.length - 1)]\n\n const copyPrompt = (): void => {\n void copyText(entry.prompt).then(() => {\n setCopied(true)\n setTimeout(() => { setCopied(false) }, 1500)\n }).catch(() => { setCopied(false) })\n }\n\n return (\n <div className={css.overlay} role=\"dialog\" aria-modal=\"true\" aria-label=\"素材详情\">\n <div className={css.sheet}>\n <div className={css.stage}>\n {image !== undefined\n ? (\n <img\n className={css.image}\n src={image.url}\n alt={entry.prompt.slice(0, 40)}\n />\n )\n : <p className={css.muted}>这条记录没有图片。</p>}\n {entry.images.length > 1\n ? (\n <div className={css.pager}>\n <Button\n onClick={() => { setIndex(current => Math.max(0, current - 1)) }}\n disabled={index === 0}\n title=\"上一张\"\n >\n ←\n </Button>\n <span className={css.pagerText}>{Math.min(index, entry.images.length - 1) + 1} / {entry.images.length}</span>\n <Button\n onClick={() => { setIndex(current => Math.min(entry.images.length - 1, current + 1)) }}\n disabled={index >= entry.images.length - 1}\n title=\"下一张\"\n >\n →\n </Button>\n </div>\n )\n : null}\n </div>\n\n <div className={css.side}>\n <div className={css.sideHeader}>\n <SourceBadge source={entry.source} />\n <span className={css.muted}>{new Date(entry.createdAt).toLocaleString('zh-CN')}</span>\n <Button onClick={onClose} title=\"关闭(Esc)\">关闭</Button>\n </div>\n\n {image !== undefined\n ? (\n <div className={css.stageActions}>\n {addable\n ? (\n <Button\n variant=\"primary\"\n data-dsh-seework-add-to-canvas=\"\"\n title=\"把当前这张图放到画布上\"\n onClick={() => {\n addToCanvas({\n file: image.file,\n width: image.width,\n height: image.height,\n model: entry.model,\n prompt: entry.prompt,\n })\n }}\n >\n 加到画布\n </Button>\n )\n : null}\n {toComposer\n ? (\n <Button\n variant=\"outline\"\n data-dsh-seework-add-to-composer=\"\"\n title=\"把当前这张图放进对话输入框当草稿附件\"\n onClick={() => { void sendCurrentToComposer() }}\n >\n 加入到对话框\n </Button>\n )\n : null}\n <span className={css.muted}>\n 当前这张 {image.file}\n {composerState === 'sent' ? ' · 已放进输入框' : ''}\n {composerState === 'failed' ? ' · 放入输入框失败' : ''}\n </span>\n </div>\n )\n : null}\n\n <section className={css.block}>\n <div className={css.blockHead}>\n <h3 className={css.blockTitle}>提示词</h3>\n {/* A real button, on its own row: sitting under the text made it\n read as part of the prompt instead of as the copy control. */}\n <Button\n variant=\"outline\"\n className={css.copyButton}\n onClick={copyPrompt}\n title=\"复制这段提示词到剪贴板\"\n aria-label=\"复制提示词\"\n >\n {copied ? '已复制 ✓' : '复制提示词'}\n </Button>\n </div>\n <pre className={css.prompt}>{entry.prompt}</pre>\n </section>\n\n <section className={css.block}>\n <h3 className={css.blockTitle}>生成参数</h3>\n <dl className={css.facts}>\n <dt>模型</dt><dd>{entry.model}</dd>\n <dt>档位</dt><dd>{entry.resolution === '' ? '(未指定)' : entry.resolution}</dd>\n <dt>比例</dt><dd>{entry.aspectRatio === '' ? '(未指定)' : entry.aspectRatio}</dd>\n <dt>尺寸</dt><dd>{image === undefined || image.width === undefined || image.height === undefined ? '未知' : `${image.width} × ${image.height} 像素`}</dd>\n <dt>格式</dt><dd>{entry.outputFormat === '' ? '(网关默认)' : entry.outputFormat}</dd>\n <dt>张数</dt><dd>{entry.n}</dd>\n {entry.cost === undefined ? null : <><dt>扣费</dt><dd>{entryCost(entry)}</dd></>}\n {entry.refNames === undefined || entry.refNames.length === 0\n ? null\n : <><dt>参考图</dt><dd>{entry.refNames.join('、')}</dd></>}\n </dl>\n </section>\n\n <section className={css.block}>\n <h3 className={css.blockTitle}>文件</h3>\n <ul className={css.files}>\n {entry.images.map((item, position) => (\n <li key={item.file}>\n <a className={css.fileLink} href={item.url} target=\"_blank\" rel=\"noreferrer\">\n {item.file}\n </a>\n <span className={css.muted}>\n {item.width !== undefined && item.height !== undefined ? `${item.width}×${item.height}` : ''}\n {' '}\n {item.mime}\n {position === Math.min(index, entry.images.length - 1) ? '(当前)' : ''}\n </span>\n </li>\n ))}\n </ul>\n </section>\n\n <section className={css.block}>\n {confirming\n ? (\n <div className={css.confirm}>\n <span>删除后图片文件也会从磁盘移除,确定吗?</span>\n <div className={css.confirmActions}>\n <Button\n onClick={() => {\n void store.remove(entry.id).then(ok => {\n if (ok) onClose()\n else setConfirming(false)\n })\n }}\n >\n 确定删除\n </Button>\n <Button onClick={() => { setConfirming(false) }}>取消</Button>\n </div>\n </div>\n )\n : (\n <Button onClick={() => { setConfirming(true) }}>删除这条记录</Button>\n )}\n </section>\n </div>\n </div>\n </div>\n )\n}\n","/**\n * The SeeWork material library sidebar (phase 2).\n *\n * Everything the user ever generated through this plugin is already on disk and\n * indexed host-side, so this surface is a view over `library/list`: search,\n * filter by model / source / day, group by day, and open a large preview.\n *\n * Mounted at the DOM level (a launcher button plus a left drawer) rather than\n * through a shell slot, for the same reason the settings surface is: a\n * third-party plugin must not depend on shell-private layout.\n */\n\nimport { useEffect, useMemo, useState } from 'react'\nimport type { LibraryEntry } from '../protocol.ts'\nimport {\n EMPTY_FILTER,\n entryCost,\n filterEntries,\n groupByDay,\n libraryModels,\n promptPreview,\n type LibraryFilter,\n type LibraryState,\n type LibraryStore,\n} from './library-store.ts'\nimport { Button, TextInput } from './controls.tsx'\nimport { addToCanvas, canvasAddAvailable } from './canvas-add.ts'\nimport { SourceBadge } from './library-detail.tsx'\nimport css from './library-panel.module.css'\n\n/** Sidebar props (it owns no lifecycle of its own — the mount point does). */\nexport interface LibraryPanelProps {\n store: LibraryStore\n /** Close the drawer (the launcher toggles it). */\n onClose: () => void\n /** Open one entry in the large preview. */\n onOpen: (id: string) => void\n /**\n * Hide the 收起 button. In the right sidebar the tab strip's own ✕ closes the\n * surface, so a second close control would be redundant chrome.\n */\n hideClose?: boolean\n /**\n * Render as a right-sidebar tab body: fill the shell's panel instead of\n * floating over the viewport.\n */\n tab?: boolean\n}\n\n/** The library drawer body. */\nexport function LibraryPanel({ store, onClose, onOpen, hideClose = false, tab = false }: LibraryPanelProps): JSX.Element {\n const [tick, setTick] = useState(0)\n useEffect(() => store.subscribe(() => { setTick(value => value + 1) }), [store])\n void tick\n\n const state: LibraryState = store.getSnapshot()\n const [filter, setFilter] = useState<LibraryFilter>(EMPTY_FILTER)\n const [refreshing, setRefreshing] = useState(false)\n /** Whether the plugin can place pictures right now (no canvas face, no button). */\n const addable = canvasAddAvailable()\n\n const filtered = useMemo(() => filterEntries(state.entries, filter), [state.entries, filter])\n const days = useMemo(() => groupByDay(filtered), [filtered])\n const models = useMemo(() => libraryModels(state.entries), [state.entries])\n const active = filter.query !== '' || filter.model !== '' || filter.source !== 'all' || filter.day !== ''\n\n const refresh = (): void => {\n setRefreshing(true)\n void store.refresh().finally(() => { setRefreshing(false) })\n }\n\n return (\n <aside className={tab ? `${css.panel} ${css.tabPanel}` : css.panel} aria-label=\"SeeWork 素材库\">\n <header className={css.header}>\n <div className={css.titleRow}>\n <h2 className={css.title}>SeeWork 素材库</h2>\n <div className={css.headerActions}>\n <Button onClick={refresh} disabled={refreshing} title=\"重新读取素材库\">\n {refreshing ? '刷新中…' : '刷新'}\n </Button>\n {hideClose\n ? null\n : <Button onClick={onClose} title=\"收起素材库\">收起</Button>}\n </div>\n </div>\n <TextInput\n value={filter.query}\n placeholder=\"搜索提示词…\"\n spellCheck={false}\n onChange={event => { setFilter(current => ({ ...current, query: event.target.value })) }}\n />\n <div className={css.filters}>\n <label className={css.filter}>\n <span className={css.filterLabel}>模型</span>\n <select\n className={css.select}\n value={filter.model}\n onChange={event => { setFilter(current => ({ ...current, model: event.target.value })) }}\n >\n <option value=\"\">全部</option>\n {models.map(model => (\n <option key={model.id} value={model.id}>{model.id}({model.count})</option>\n ))}\n </select>\n </label>\n <label className={css.filter}>\n <span className={css.filterLabel}>来源</span>\n <select\n className={css.select}\n value={filter.source}\n onChange={event => { setFilter(current => ({ ...current, source: event.target.value as LibraryFilter['source'] })) }}\n >\n <option value=\"all\">全部</option>\n <option value=\"agent\">对话生成</option>\n <option value=\"panel\">面板生成</option>\n <option value=\"canvas\">画布生成</option>\n </select>\n </label>\n </div>\n {filter.day !== ''\n ? (\n <div className={css.activeFilters}>\n <button type=\"button\" className={css.chip} onClick={() => { setFilter(current => ({ ...current, day: '' })) }}>\n 只看 {filter.day} ✕\n </button>\n </div>\n )\n : null}\n </header>\n\n <div className={css.body}>\n {state.status === 'error'\n ? <p className={css.error}>{state.error ?? '素材库读取失败。'}{state.entries.length > 0 ? '(下面是上次读到的内容)' : ''}</p>\n : null}\n {state.status === 'loading' && state.entries.length === 0\n ? <p className={css.muted}>正在读取素材库…</p>\n : null}\n {state.status !== 'loading' && state.entries.length === 0\n ? (\n <div className={css.empty}>\n <p className={css.emptyTitle}>还没有生成过图片</p>\n <p className={css.muted}>在对话里让 Agent 画一张图,成品就会出现在这里。</p>\n </div>\n )\n : null}\n {state.entries.length > 0 && filtered.length === 0\n ? (\n <p className={css.muted}>\n 没有符合当前条件的图片。\n <button type=\"button\" className={css.linkButton} onClick={() => { setFilter(EMPTY_FILTER) }}>清除筛选</button>\n </p>\n )\n : null}\n\n {days.map(day => (\n <section key={day.day} className={css.day}>\n <button\n type=\"button\"\n className={css.dayHeader}\n title={`只看 ${day.day}`}\n onClick={() => { setFilter(current => ({ ...current, day: current.day === day.day ? '' : day.day })) }}\n >\n <span>{day.label}</span>\n <span className={css.dayCount}>{day.entries.length}</span>\n </button>\n <ul className={css.list}>\n {day.entries.map(entry => {\n const first = entry.images[0]\n return (\n <li key={entry.id}>\n <article className={css.card}>\n <button\n type=\"button\"\n className={css.thumbButton}\n onClick={() => { onOpen(entry.id) }}\n title=\"查看大图\"\n >\n <img\n className={css.thumb}\n src={first?.url ?? ''}\n alt={promptPreview(entry.prompt, 24)}\n loading=\"lazy\"\n />\n {entry.images.length > 1\n ? <span className={css.imageCount}>{entry.images.length}</span>\n : null}\n </button>\n <div className={css.cardBody}>\n <button type=\"button\" className={css.promptButton} onClick={() => { onOpen(entry.id) }}>\n {promptPreview(entry.prompt)}\n </button>\n <div className={css.meta}>\n <SourceBadge source={entry.source} />\n <span className={css.metaText}>{entry.model}</span>\n {entry.cost !== undefined\n ? <span className={css.metaText}>{entryCost(entry)}</span>\n : null}\n <time className={css.metaText} dateTime={new Date(entry.createdAt).toISOString()}>\n {new Date(entry.createdAt).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}\n </time>\n {/* Puts the picture the card shows onto the board, and brings\n the canvas up so the result is visible. */}\n {addable && first !== undefined\n ? (\n <button\n type=\"button\"\n className={css.addButton}\n data-dsh-seework-add-to-canvas=\"\"\n title=\"把这张图放到画布上\"\n onClick={() => {\n addToCanvas({\n file: first.file,\n width: first.width,\n height: first.height,\n model: entry.model,\n prompt: entry.prompt,\n })\n }}\n >\n 加到画布\n </button>\n )\n : null}\n </div>\n </div>\n </article>\n </li>\n )\n })}\n </ul>\n </section>\n ))}\n </div>\n\n <footer className={css.footer}>\n <span>\n {filtered.length === state.entries.length\n ? `${state.entries.length} 次生成 · ${state.imageCount} 张图`\n : `筛出 ${filtered.length} / ${state.entries.length} 次生成`}\n </span>\n {state.dataRoot !== ''\n ? <span className={css.footerPath} title={state.dataRoot}>{state.dataRoot}</span>\n : null}\n {active\n ? <button type=\"button\" className={css.linkButton} onClick={() => { setFilter(EMPTY_FILTER) }}>清除筛选</button>\n : null}\n </footer>\n </aside>\n )\n}\n","/**\n * Mount point for the material-library surfaces: a launcher button in the\n * shared SeeWork dock, the left drawer, and the detail overlay.\n *\n * The store is passed in rather than created here: the canvas also reads the\n * library (its picture picker), and two stores would mean two caches of the\n * same list — one of them stale.\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport type { SeeWorkApi } from './api.ts'\nimport type { LibraryStore } from './library-store.ts'\nimport { LibraryPanel } from './LibraryPanel.tsx'\nimport { LibraryDetail } from './library-detail.tsx'\nimport { reportSurfaceOpen } from './header-launchers.tsx'\nimport { ACTIVATE_EVENT, announceActivation, onActivation } from './settings-panel.tsx'\nimport { attachLauncher } from './surfaces.ts'\nimport { useEffect, useRef, useState } from 'react'\nimport launcherCss from './surfaces.module.css'\nimport css from './library-panel.module.css'\n\n/** Marks the library launcher so other surfaces can find it by selector. */\nexport const LIBRARY_LAUNCHER_ATTR = 'data-dsh-seework-library-launcher'\n\n/** The drawer and its detail overlay; the launcher lives in the dock. */\nfunction LibrarySurfaces({ store, api, launcher }: {\n store: LibraryStore\n api: SeeWorkApi\n launcher: HTMLElement\n}): JSX.Element | null {\n void api\n const [open, setOpen] = useState(false)\n const [detailId, setDetailId] = useState<string | undefined>(undefined)\n const [tick, setTick] = useState(0)\n useEffect(() => store.subscribe(() => { setTick(value => value + 1) }), [store])\n void tick\n\n const state = store.getSnapshot()\n\n // The launcher button is outside this tree (it lives in the shared dock), so\n // its label and click handler are wired imperatively.\n useEffect(() => {\n const render = (): void => {\n const current = store.getSnapshot()\n launcher.textContent = ''\n launcher.append('素材库')\n if (current.entries.length > 0) {\n const badge = document.createElement('span')\n badge.className = launcherCss.launcherCount\n badge.textContent = String(current.imageCount)\n launcher.append(badge)\n }\n launcher.title = '查看用 SeeWork 生成过的图片'\n launcher.setAttribute('aria-expanded', String(open))\n }\n render()\n const stop = store.subscribe(render)\n return () => { stop() }\n }, [launcher, open, store])\n\n useEffect(() => {\n const onClick = (): void => {\n const next = !open\n setOpen(next)\n if (next) {\n announceActivation('library')\n // The conversation may have generated something since the last look.\n void store.refresh()\n }\n }\n launcher.addEventListener('click', onClick)\n return () => { launcher.removeEventListener('click', onClick) }\n }, [launcher, open, store])\n\n // Only one SeeWork surface is open at a time, and announcing this surface is\n // how anything outside this tree (the conversation header) asks for it.\n useEffect(() => {\n const onOtherActivate = (event: Event): void => {\n if ((event as CustomEvent).detail !== 'library') setOpen(false)\n }\n document.addEventListener(ACTIVATE_EVENT, onOtherActivate)\n return () => { document.removeEventListener(ACTIVATE_EVENT, onOtherActivate) }\n }, [])\n\n // The header launcher asks for the library by announcing it; the surface has\n // this one entry point so there is no second way to open it.\n useEffect(() => onActivation(surface => {\n if (surface !== 'library') return\n setOpen(true)\n // The conversation may have generated something since the last look.\n void store.refresh()\n }), [store])\n\n // The header button toggles, which means it has to know whether this surface\n // is really up — the drawer can also be closed from its own chrome.\n const openRef = useRef(false)\n openRef.current = open\n useEffect(() => reportSurfaceOpen('library', () => openRef.current), [])\n\n if (!open) return null\n return (\n <>\n <LibraryPanel\n store={store}\n onClose={() => {\n setOpen(false)\n setDetailId(undefined)\n }}\n onOpen={id => { setDetailId(id) }}\n />\n {detailId === undefined\n ? null\n : <LibraryDetail store={store} entryId={detailId} onClose={() => { setDetailId(undefined) }} />}\n </>\n )\n}\n\n/**\n * Mount the library launcher, drawer and detail overlay.\n * @param api - the plugin route client.\n * @param store - the shared library store (also read by the canvas).\n * @returns disposer unmounting everything this created.\n */\nexport function mountLibraryPanel(api: SeeWorkApi, store: LibraryStore): () => void {\n const host = document.createElement('div')\n document.body.appendChild(host)\n const launcher = document.createElement('button')\n launcher.type = 'button'\n launcher.setAttribute(LIBRARY_LAUNCHER_ATTR, '')\n launcher.className = launcherCss.launcher\n const detachLauncher = attachLauncher(launcher)\n // Warm the count badge so the launcher is meaningful before it is opened.\n void store.refresh()\n\n const root: Root = createRoot(host)\n root.render(<LibrarySurfaces store={store} api={api} launcher={launcher} />)\n return () => {\n root.unmount()\n detachLauncher()\n host.remove()\n }\n}\n","/**\n * Browser-side settings scope for the `dsh-seework` namespace.\n *\n * The shell's own settings transport only serves namespaces it knows, so a\n * third-party plugin re-serves its namespace over its own same-origin,\n * loopback-only HTTP pair (the settings bridge mounted by the host half). This\n * module implements the same read/write contract the shell's scopes expose —\n * serialized writes, revision-fenced mutations, a recovery read after a\n * refusal — over that bridge.\n */\n\nimport { SnapshotStore } from './snapshot-store.ts'\nimport { SETTINGS_API, type ModelConfig } from '../protocol.ts'\n\n/** The fields this plugin's settings card edits. */\nexport interface SeeWorkConfig {\n enabled?: boolean\n allowAgentGeneration?: boolean\n announceToAgent?: boolean\n apiUrl?: string\n /** SeeAI Hub service base used only for the model catalog. */\n serviceUrl?: string\n /** Redacted on the wire: presence comes from the `secrets` sidecar. */\n apiKey?: string\n models?: ModelConfig[]\n defaultModel?: string\n defaultAspectRatio?: string\n outputFormat?: string\n dataDir?: string\n}\n\n/** One settings path-op as the bridge consumes it. */\nexport type SettingsOp = { op: 'set'; path: string[]; value: unknown } | { op: 'unset'; path: string[] }\n\n/** Snapshot shape shared with the shell's `SettingsScope` consumers. */\nexport interface ScopeSnapshot<T> {\n status: 'loading' | 'ready' | 'unavailable'\n value: T | undefined\n base: unknown\n user: unknown\n revision: number | undefined\n writable: boolean\n}\n\n/** The bound scope plus the secret-presence view the card needs. */\nexport interface SeeWorkScope {\n getSnapshot(): ScopeSnapshot<SeeWorkConfig>\n subscribe(listener: () => void): () => void\n /** Queue a bridge refresh. */\n load(): Promise<void>\n set(field: string, value: unknown): Promise<void>\n unset(field: string): Promise<void>\n /** Apply several path ops in one revision-fenced call (atomic save). */\n mutateOps(ops: SettingsOp[]): Promise<void>\n /** Whether a stored API key currently exists (the wire never returns it). */\n getSecretSet(field: string): boolean\n subscribeSecrets(listener: () => void): () => void\n}\n\n/** Wire shape of one namespace view from the bridge. */\ninterface BridgeView {\n ns: string\n value: unknown\n base?: unknown\n user?: unknown\n revision: number\n secrets?: Array<{ path: string[]; set: boolean }>\n}\n\n/** The bridge response envelope. */\ntype BridgeEnvelope =\n | { ok: true; value: { namespaces?: BridgeView[]; writable?: boolean } | BridgeView }\n | { ok: false; code: string; message: string }\n\n/** POST one bridge request and decode its envelope. */\nasync function post(path: string, body: unknown, fetchFn: typeof fetch): Promise<BridgeEnvelope> {\n try {\n const response = await fetchFn(path, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!response.ok) {\n return { ok: false, code: 'internal', message: `设置接口返回 HTTP ${response.status}` }\n }\n return await response.json() as BridgeEnvelope\n } catch {\n return { ok: false, code: 'internal', message: '设置接口不可达' }\n }\n}\n\n/** The scope implementation over the bridge. */\nclass BridgeScope implements SeeWorkScope {\n private readonly store: SnapshotStore<ScopeSnapshot<SeeWorkConfig>>\n private readonly secrets: SnapshotStore<Record<string, boolean>>\n private tail: Promise<void> = Promise.resolve()\n private disposed = false\n\n constructor(private readonly fetchFn: typeof fetch) {\n this.store = new SnapshotStore<ScopeSnapshot<SeeWorkConfig>>({\n status: 'loading',\n value: undefined,\n base: undefined,\n user: undefined,\n revision: undefined,\n writable: false,\n })\n this.secrets = new SnapshotStore<Record<string, boolean>>({})\n }\n\n getSnapshot(): ScopeSnapshot<SeeWorkConfig> {\n return this.store.getSnapshot()\n }\n\n subscribe(listener: () => void): () => void {\n return this.store.subscribe(listener)\n }\n\n subscribeSecrets(listener: () => void): () => void {\n return this.secrets.subscribe(listener)\n }\n\n getSecretSet(field: string): boolean {\n return this.secrets.getSnapshot()[field] === true\n }\n\n load(): Promise<void> {\n return this.enqueue(() => this.read())\n }\n\n set(field: string, value: unknown): Promise<void> {\n return this.enqueue(() => this.write([{ op: 'set', path: [field], value }]))\n }\n\n unset(field: string): Promise<void> {\n return this.enqueue(() => this.write([{ op: 'unset', path: [field] }]))\n }\n\n mutateOps(ops: SettingsOp[]): Promise<void> {\n return this.enqueue(() => this.write(ops))\n }\n\n /** Queue one operation behind every earlier one. */\n private enqueue(operation: () => Promise<void>): Promise<void> {\n if (this.disposed) return Promise.resolve()\n const task = this.tail.then(async () => {\n if (this.disposed) return\n await operation()\n })\n this.tail = task.catch(() => {})\n return task\n }\n\n private async read(): Promise<void> {\n const envelope = await post(SETTINGS_API.describe, {}, this.fetchFn)\n if (this.disposed) return\n if (!envelope.ok) {\n this.store.update(draft => { draft.status = 'unavailable' })\n return\n }\n const { namespaces, writable } = envelope.value as { namespaces?: BridgeView[]; writable?: boolean }\n const view = namespaces?.[0]\n if (view === undefined) {\n this.store.update(draft => {\n draft.status = 'unavailable'\n draft.writable = writable === true\n })\n this.secrets.set({})\n return\n }\n this.accept(view, writable)\n }\n\n private async write(ops: SettingsOp[]): Promise<void> {\n const revision = this.getSnapshot().revision\n const envelope = await post(SETTINGS_API.mutate, {\n ns: 'dsh-seework',\n ops,\n ...revision === undefined ? {} : { expectedRevision: revision },\n }, this.fetchFn)\n if (this.disposed) return\n if (!envelope.ok) {\n // A refused write (validation or revision conflict) leaves the form to\n // re-read the authoritative document rather than guessing the outcome.\n await this.read()\n return\n }\n this.accept(envelope.value as BridgeView, undefined)\n }\n\n /** Fold one bridge view into the snapshot and the secret-presence sidecar. */\n private accept(view: BridgeView, writable: boolean | undefined): void {\n this.store.update(draft => {\n draft.revision = view.revision\n draft.base = view.base\n draft.user = view.user\n if (writable !== undefined) draft.writable = writable\n draft.status = 'ready'\n draft.value = view.value as SeeWorkConfig\n })\n const bits: Record<string, boolean> = {}\n for (const secret of view.secrets ?? []) bits[secret.path.join('.')] = secret.set\n this.secrets.set(bits)\n }\n}\n\n/**\n * Bind the plugin's settings scope and start its initial read.\n * @param fetchFn - the fetch implementation (the page's own fetch on loopback).\n * @returns the bound scope.\n */\nexport function bindSeeWorkScope(fetchFn: typeof fetch = fetch): SeeWorkScope {\n const scope = new BridgeScope(fetchFn)\n void scope.load()\n return scope\n}\n","/**\n * Conversation images: show them small, open them big.\n *\n * The transcript renders our images itself (the assistant's markdown reply, and\n * the card we register into the tool row), so this module owns the two things\n * that make them usable rather than obnoxious:\n *\n * 1. **A display cap.** A 1K–4K result is a multi-megabyte picture; rendered at\n * its natural size one image fills the whole window. Markdown cannot carry a\n * size, and the shell's own `<img>` is not ours to class, so the cap is a\n * style rule keyed on the plugin's own URL prefix — it cannot leak onto\n * anybody else's images, and the card's own thumbnails (which have their own\n * attribute) keep their own smaller size.\n * 2. **Click to enlarge.** The shell's markdown renders a bare `<img>` with no\n * link, and our card's link opens a raw tab; instead a plain left click on\n * one of these images opens an in-app lightbox (Esc or a click outside\n * closes it). Ctrl/middle click still falls through to the browser, so\n * \"open the original in a tab\" stays available.\n *\n * Failure policy matches the rest of the plugin: no slot, no shell hook, nothing\n * here throws — a page without the chat (the home screen) simply never matches.\n */\n\nimport { createRoot, type Root } from 'react-dom/client'\nimport { addToCanvas, canvasAddAvailable, type CanvasAddTarget } from './canvas-add.ts'\nimport css from './chat-images.module.css'\n\n/** Marker on the card's own thumbnails, which size themselves. */\nexport const CARD_IMAGE_ATTR = 'data-dsh-seework-card-image'\n\n/** Attribute marking the lightbox host, so anything can find it. */\nexport const LIGHTBOX_ATTR = 'data-dsh-seework-image-zoom'\n\n/**\n * Images this module sizes and zooms: images the chat nodes render, whose source\n * is one of this plugin's routes. Scoped to the chat, so the material library and\n * the canvas keep their own layout.\n */\nexport const CHAT_IMAGE_SELECTOR = `[data-chat-anchor-key] img[src*=\"/api/dsh-seework/\"]:not([${CARD_IMAGE_ATTR}])`\n\n/** Same scope, but including the card's thumbnails, for the zoom click. */\nconst ZOOMABLE_SELECTOR = `[data-chat-anchor-key] img[src*=\"/api/dsh-seework/\"]`\n\n/** Cap on a conversation image's rendered size (the original opens on click). */\nconst THUMBNAIL_MAX_PX = 320\n\n/** The style rule that keeps a generated picture from taking the whole window. */\nexport const CHAT_IMAGE_CSS = `${CHAT_IMAGE_SELECTOR} {\n max-width: ${THUMBNAIL_MAX_PX}px;\n max-height: ${THUMBNAIL_MAX_PX}px;\n width: auto;\n height: auto;\n border-radius: 10px;\n cursor: zoom-in;\n}\n${ZOOMABLE_SELECTOR} {\n cursor: zoom-in;\n}\n`\n\n/** Is this element one of the conversation images we own? */\nfunction zoomableImage(target: EventTarget | null): HTMLImageElement | undefined {\n if (!(target instanceof Element)) return undefined\n const image = target.closest('img')\n if (image === null || !(image instanceof HTMLImageElement)) return undefined\n return image.matches(ZOOMABLE_SELECTOR) ? image : undefined\n}\n\n/** Whether this click should open the lightbox rather than the browser's own. */\nfunction isPlainLeftClick(event: MouseEvent): boolean {\n return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey\n}\n\n/**\n * The enlarged view: the picture at up to the viewport, closed by a click\n * outside. When the picture is one the plugin can place, it also offers\n * 「加到画布」 — the deliberate, one-click way to put it on a board.\n */\nexport function ChatImageLightbox(\n { src, onClose, onAddToCanvas }: { src: string; onClose: () => void; onAddToCanvas?: (() => void) | undefined },\n): JSX.Element {\n return (\n <div\n className={css.backdrop}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"查看大图\"\n onClick={onClose}\n >\n <img\n className={css.full}\n src={src}\n alt=\"生成的图片(大图)\"\n onClick={event => { event.stopPropagation() }}\n />\n {onAddToCanvas === undefined ? null : (\n <div className={css.actions} onClick={event => { event.stopPropagation() }}>\n <button\n type=\"button\"\n className={css.action}\n data-dsh-seework-add-to-canvas=\"\"\n onClick={onAddToCanvas}\n >\n 加到画布\n </button>\n </div>\n )}\n </div>\n )\n}\n\n/** One lightbox at a time, mounted on demand. */\nlet lightbox: { host: HTMLElement; root: Root } | undefined\n\n/**\n * Show one conversation image enlarged.\n * @param src - the image's source (the original, not a thumbnail copy).\n * @param doc - the document to mount into (tests pass their jsdom document).\n * @param target - the library picture behind it, when the plugin can place it.\n */\nexport function openChatImage(src: string, doc: Document = document, target?: CanvasAddTarget): void {\n closeChatImage()\n const host = doc.createElement('div')\n host.setAttribute(LIGHTBOX_ATTR, '')\n doc.body.appendChild(host)\n const root = createRoot(host)\n lightbox = { host, root }\n const onAdd = target === undefined || !canvasAddAvailable()\n ? undefined\n : () => {\n // Close first: the overlay covers the window, and the point of adding is\n // to see the board it landed on.\n closeChatImage()\n addToCanvas(target)\n }\n root.render(<ChatImageLightbox src={src} onClose={() => { closeChatImage() }} onAddToCanvas={onAdd} />)\n}\n\n/** Put the enlarged view away, when there is one. */\nexport function closeChatImage(): void {\n const current = lightbox\n if (current === undefined) return\n lightbox = undefined\n current.root.unmount()\n current.host.remove()\n}\n\n/**\n * Install the cap and the click-to-enlarge behaviour.\n *\n * @param doc - the document to watch (tests pass their jsdom document).\n * @param resolveTarget - turns an image URL into the library picture behind it,\n * when the plugin can place that picture (see {@link CanvasAddTarget}).\n * @returns disposer removing the style, the listeners, and any open lightbox.\n */\nexport function installChatImageZoom(\n doc: Document = document,\n resolveTarget?: (src: string) => CanvasAddTarget | undefined,\n): () => void {\n const style = doc.createElement('style')\n style.setAttribute('data-dsh-seework', 'chat-image-zoom')\n style.textContent = CHAT_IMAGE_CSS\n doc.head.appendChild(style)\n\n const onClick = (event: MouseEvent): void => {\n const image = zoomableImage(event.target)\n if (image === undefined || !isPlainLeftClick(event)) return\n // The image sits inside shell chrome (and, in the card, inside a link): take\n // the click before either of them turns it into a page navigation.\n event.preventDefault()\n event.stopPropagation()\n const src = image.currentSrc === '' ? image.src : image.currentSrc\n const target = resolveTarget === undefined ? undefined : resolveTarget(src)\n // The card tells us which library picture its thumbnail shows; a markdown\n // image says it through the URL. Either way the lightbox can offer the action.\n openChatImage(src, doc, target)\n }\n doc.addEventListener('click', onClick, true)\n\n // Escape closes the enlarged view. Owned here rather than inside the lightbox\n // component: the overlay lives in its own React root, while the plugin's own\n // lifetime is exactly the lifetime of this listener.\n const onKey = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') closeChatImage()\n }\n doc.addEventListener('keydown', onKey)\n\n return () => {\n doc.removeEventListener('click', onClick, true)\n doc.removeEventListener('keydown', onKey)\n style.remove()\n closeChatImage()\n }\n}\n","/**\n * SeeWork's image card inside the conversation (the tool-call row).\n *\n * A generated image is deliberately NOT part of the model-visible tool result:\n * the result text stays JSON so text-only conversation models keep working, and\n * the attachment references ride the result's presentation metadata instead.\n * That leaves the row itself generic — a wall of JSON — unless the plugin that\n * owns the tool supplies its own view, which is what this module does.\n *\n * Why the images are fetched here rather than through the shell:\n *\n * - the shell's `tool.call.images` gallery slot is declared as a child slot by\n * the built-in `read_image` row and a slot has exactly one owner, so a second\n * declarer would make the whole client bundle fail to load;\n * - the shell's session-authorized `loadImage` resolves attachments the session\n * log references, and a tool result's images are not in it (by design), so it\n * would refuse them;\n * - therefore the references come from the persisted `block.meta` and the bytes\n * from this plugin's own loopback-only attachment route.\n *\n * Failure policy: this view never throws. Anything it cannot read simply does\n * not render, and the shell's flattened result text remains the fallback.\n */\n\nimport { useMemo, useState } from 'react'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { ATTACHMENT_API, isImageMedia } from '../protocol.ts'\nimport { addToCanvas, canvasAddAvailable } from './canvas-add.ts'\nimport css from './tool-card.module.css'\n\n/** One image record as the host persists it in the tool result's metadata. */\nexport interface ToolCardImage {\n attachment_id: string\n media_type: string\n bytes: number\n width: number\n height: number\n name?: string\n /** Library file name, when the host sent one (see `presentationMeta`). */\n file?: string\n}\n\n/**\n * The frozen call/result node the shell hands a keyed tool view.\n *\n * Spelled here (like this package's other slot contracts) instead of importing\n * the sibling UI package: only the fields this view reads are declared, and\n * every one of them is treated as unvalidated wire data.\n */\nexport interface ToolCardBlock {\n /** Absent while the call is still running (the shell's running-call shape). */\n kind?: string\n content?: unknown\n meta?: unknown\n isError?: boolean\n}\n\n/** Owner payload the shell supplies to a `tool.call.toolview` registration. */\nexport interface ToolCardProps {\n callId?: string\n toolName?: string\n block?: ToolCardBlock\n}\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface SlotMap {\n /**\n * Keyed per wire Tool name: a hit REPLACES the shell's generic row with the\n * registered view, which is how SeeWork's results render as picture cards.\n */\n 'tool.call.toolview': { kind: 'keyed'; scope: 'session'; owner: ToolCardProps }\n }\n}\n\n/**\n * Tool names whose results carry images.\n *\n * `get_seework_task` is no longer registered by the host half — SeeAI Hub's image\n * API is synchronous, so the plugin stopped offering a task handle — but this key\n * stays on purpose: results of that name are already persisted in session logs,\n * and dropping the key would turn those historical rows back into raw JSON. Same\n * reasoning as the board still reading cards written before `origin` existed.\n *\n * `edit_image` deliberately does **not** get that treatment (#664): the tool was\n * merged into `generate_image` while this plugin is still in development, and a\n * historical edit row is allowed to fall back to the shell's generic row. Its\n * images are still in the material library, so nothing is lost by not having a\n * bespoke card for it.\n */\nexport const TOOL_CARD_KEYS = ['generate_image', 'get_seework_task'] as const\n\n/**\n * Priority of these registrations inside the keyed slot.\n *\n * Two plugins can (and on a real machine do) claim the same wire tool name — the\n * reference image plugin registers its own view for `generate_image`. The slot\n * registry refuses a second registration at the SAME priority and fails the\n * whole plugin that lost the race (observed live: the other plugin's loader\n * entry failed with \"already has an entry for key generate_image at priority 0 …\n * register at a different priority to shadow it\"), so claiming a distinct, lower\n * priority is not a nicety — it is how both plugins stay loadable, and \"lowest\n * renders\" makes this card the one shown.\n *\n * That is the right winner here: this view reads the images from the result's\n * persisted `meta`, which both plugins emit under the same field names, while a\n * view that reads them out of the result content has nothing to show (tool\n * results are not model-visible content).\n */\nconst TOOL_CARD_PRIORITY = -10\n\n/** Human-readable state for the result JSON's `status`. */\nfunction statusLabel(status: string): string {\n if (status === 'running' || status === 'queued') return '生成中'\n if (status === 'failed') return '生成失败'\n if (status === 'cancelled') return '已取消'\n return '已生成'\n}\n\n/**\n * Card titles. The shell prints the wire tool name above the row, which is not\n * something a user should have to read, so the card says what it did instead.\n * @param toolName - the wire tool name.\n * @returns the display title.\n */\nfunction cardTitle(toolName: string): string {\n if (toolName === 'get_seework_task') return 'SeeWork 生图任务'\n return 'SeeWork 生图'\n}\n\n/** Whether the node is a settled result rather than a running call. */\nfunction isSettled(block: ToolCardBlock | undefined): boolean {\n return block !== undefined && typeof block.kind === 'string'\n}\n\n/** The result's own JSON text, if the row carries exactly what we wrote. */\nfunction resultText(block: ToolCardBlock | undefined): string {\n if (block === undefined || !Array.isArray(block.content)) return ''\n return block.content\n .flatMap(part => {\n if (typeof part !== 'object' || part === null) return []\n const { type, text } = part as { type?: unknown; text?: unknown }\n return type === 'text' && typeof text === 'string' ? [text] : []\n })\n .join('\\n')\n}\n\n/** `status` / `message` from that JSON, degrading to the raw text. */\nfunction resultInfo(block: ToolCardBlock | undefined): { status: string; message: string } {\n if (!isSettled(block)) return { status: 'running', message: '正在生成图片…' }\n const text = resultText(block)\n try {\n const parsed = JSON.parse(text) as { status?: unknown; message?: unknown }\n return {\n status: typeof parsed.status === 'string' ? parsed.status : block?.isError === true ? 'failed' : 'completed',\n message: typeof parsed.message === 'string' ? parsed.message : '',\n }\n } catch {\n return { status: block?.isError === true ? 'failed' : 'completed', message: text }\n }\n}\n\n/**\n * Narrow the persisted metadata's `images` into references we can request.\n *\n * All-or-nothing per record: a half-valid reference would make the route answer\n * 400 for a picture the row claims to have, so it is dropped instead.\n *\n * @param meta - the result's persisted presentation metadata, of unknown shape.\n * @returns the valid image records, in result order.\n */\nexport function imagesOf(meta: unknown): ToolCardImage[] {\n if (typeof meta !== 'object' || meta === null || Array.isArray(meta)) return []\n const images = (meta as { images?: unknown }).images\n if (!Array.isArray(images)) return []\n const out: ToolCardImage[] = []\n for (const value of images) {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) continue\n const raw = value as Record<string, unknown>\n const { attachment_id: attachmentId, media_type: mediaType, bytes, width, height, name, file } = raw\n if (typeof attachmentId !== 'string' || attachmentId === '') continue\n if (typeof mediaType !== 'string' || !isImageMedia(mediaType)) continue\n if (typeof bytes !== 'number' || !Number.isSafeInteger(bytes) || bytes < 1) continue\n if (typeof width !== 'number' || !Number.isSafeInteger(width) || width < 1) continue\n if (typeof height !== 'number' || !Number.isSafeInteger(height) || height < 1) continue\n out.push({\n attachment_id: attachmentId,\n media_type: mediaType,\n bytes,\n width,\n height,\n ...typeof name === 'string' && name !== '' ? { name } : {},\n ...typeof file === 'string' && file !== '' ? { file } : {},\n })\n }\n return out\n}\n\n/**\n * Library file names carried by the result's own JSON text.\n *\n * A row recorded before `presentationMeta` included `file` still has the name in\n * its model-facing JSON, and those older rows keep their button this way.\n *\n * @param block - the settled result node.\n * @returns the file names in result order (empty strings where absent).\n */\nexport function filesFromResultText(block: ToolCardBlock | undefined): string[] {\n try {\n const parsed = JSON.parse(resultText(block)) as { images?: unknown }\n if (!Array.isArray(parsed.images)) return []\n return parsed.images.map(image => {\n const file = typeof image === 'object' && image !== null ? (image as { file?: unknown }).file : undefined\n return typeof file === 'string' ? file : ''\n })\n } catch {\n return []\n }\n}\n\n/**\n * The URL that serves one such record back.\n * @param image - one validated image record.\n * @returns the same-origin route URL with the complete reference.\n */\nexport function imageUrl(image: ToolCardImage): string {\n const query = new URLSearchParams({\n attachment_id: image.attachment_id,\n media_type: image.media_type,\n bytes: String(image.bytes),\n width: String(image.width),\n height: String(image.height),\n })\n return `${ATTACHMENT_API.image}?${query.toString()}`\n}\n\n/**\n * The conversation card for one SeeWork image-generating tool call.\n * @param props - the shell's owner payload for this call.\n * @returns the card row.\n */\nexport function ToolCard(props: ToolCardProps): JSX.Element {\n const block = props.block\n const { status, message } = resultInfo(block)\n const images = useMemo(() => imagesOf(block?.meta), [block?.meta])\n // Older rows carry the library file name only in their model-facing JSON.\n const fallbackFiles = useMemo(() => filesFromResultText(block), [block])\n const [broken, setBroken] = useState<ReadonlySet<string>>(new Set())\n const name = props.toolName ?? 'generate_image'\n\n return (\n <section className={css.root} data-state={status} data-tool={name}>\n <header className={css.header}>\n <strong className={css.title}>{cardTitle(name)}</strong>\n <span className={css.status}>{statusLabel(status)}</span>\n </header>\n {message === '' ? null : <p className={css.message}>{message}</p>}\n {images.length === 0 ? null : (\n <div className={css.images}>\n {images.map((image, index) => {\n const url = imageUrl(image)\n if (broken.has(image.attachment_id)) {\n return <span key={image.attachment_id} className={css.missing}>图片已不可读</span>\n }\n // The board references the library file, so the action needs one; a\n // picture whose entry was pruned has none to offer.\n const file = image.file ?? fallbackFiles[index] ?? ''\n const addable = file !== '' && canvasAddAvailable()\n return (\n <div key={image.attachment_id} className={css.thumb}>\n <a\n className={css.imageLink}\n href={url}\n target=\"_blank\"\n rel=\"noreferrer\"\n title={image.name ?? '打开原图'}\n >\n <img\n className={css.image}\n src={url}\n alt={image.name ?? '生成的图片'}\n width={image.width}\n height={image.height}\n loading=\"lazy\"\n data-dsh-seework-card-image=\"\"\n onError={() => {\n setBroken(previous => new Set(previous).add(image.attachment_id))\n }}\n />\n </a>\n {addable ? (\n <button\n type=\"button\"\n className={css.action}\n data-dsh-seework-add-to-canvas=\"\"\n title=\"把这张图放到画布上\"\n onClick={() => {\n // Model and prompt come from the library record; the card\n // only knows what it is showing.\n addToCanvas({ file, width: image.width, height: image.height })\n }}\n >\n 加到画布\n </button>\n ) : null}\n </div>\n )\n })}\n </div>\n )}\n </section>\n )\n}\n\n/**\n * Register the card for every image-bearing tool name.\n *\n * The registration is deferred through `slots.inject` because the slot exists\n * only while the shell's tool-presentation package is mounted; a shell that\n * never declares it simply never calls back, leaving the generic row. A refused\n * registration (an occupied key, say) is logged and must not fail the fiber.\n *\n * @param ctx - client root context (services: slots).\n * @returns disposer unregistering the views.\n */\nexport function registerToolCards(ctx: unknown): () => void {\n const context = ctx as { slots?: { inject?: unknown; register?: unknown } }\n const slots = context.slots\n if (slots === undefined || typeof slots.inject !== 'function' || typeof slots.register !== 'function') {\n return () => {}\n }\n const disposers: Array<() => void> = []\n const stop = (slots.inject as (name: string, callback: () => void) => () => void).call(slots, 'tool.call.toolview', () => {\n for (const key of TOOL_CARD_KEYS) {\n try {\n disposers.push((slots.register as (options: unknown, component: unknown) => () => void).call(slots, {\n name: 'tool.call.toolview',\n key,\n priority: TOOL_CARD_PRIORITY,\n }, ToolCard as unknown as (props: ToolCardProps) => JSX.Element))\n } catch (error) {\n console.warn(`[dsh-seework] tool card rejected for ${key}:`, error)\n }\n }\n })\n return () => {\n stop()\n for (const dispose of disposers.splice(0)) dispose()\n }\n}\n","/**\n * The material library as a right-sidebar tab body.\n *\n * The body renders the same panel the floating drawer renders, and the store is\n * the shared one, so a refresh triggered elsewhere (a new generation) is visible\n * here without this component doing anything.\n */\n\nimport { useEffect, useState } from 'react'\nimport type { LibraryStore } from './library-store.ts'\nimport { setComposerFace, type ComposerInput } from './composer-draft.ts'\nimport { LibraryPanel } from './LibraryPanel.tsx'\nimport { LibraryDetail } from './library-detail.tsx'\n\n/**\n * Props the sidebar seat supplies: the shared store, plus the session-scope facts\n * 「加入到对话框」 needs (the seat is session-scoped; the floating drawer is not).\n */\nexport interface LibraryTabBodyProps {\n /** Injected by the shell's tab seat; unused until the library needs tab actions. */\n hooks?: { tabInfo?: unknown }\n /** The session this tab is showing. */\n sessionId?: string\n /** The composer's attachment actions for that session. */\n inputActions?: ComposerInput\n}\n\n/**\n * Build the tab body for one store.\n *\n * The shell renders tab bodies without plugin-provided props, so the store is\n * bound here and the component receives only the seat's own props.\n * @param store - the shared library store.\n * @returns a component the tab seat can render.\n */\nexport function libraryTabBody(store: LibraryStore): (props: LibraryTabBodyProps) => JSX.Element {\n return function LibraryTabBody(props: LibraryTabBodyProps): JSX.Element {\n const [detailId, setDetailId] = useState<string | undefined>(undefined)\n // Opening the tab is the user asking \"what do I have?\": re-read, because the\n // store may have been loaded before the conversation generated anything.\n useEffect(() => { void store.refresh() }, [store])\n const sessionId = props.sessionId\n const inputActions = props.inputActions\n // Same contract as the canvas tab: the seat owns the session scope, so the\n // composer face is published for exactly as long as this tab is mounted.\n useEffect(() => {\n if (sessionId === undefined || inputActions === undefined) return\n return setComposerFace({ sessionId, input: inputActions })\n }, [inputActions, sessionId])\n return (\n <>\n <LibraryPanel\n store={store}\n tab\n hideClose\n onClose={() => { setDetailId(undefined) }}\n onOpen={id => { setDetailId(id) }}\n />\n {detailId === undefined\n ? null\n : <LibraryDetail store={store} entryId={detailId} onClose={() => { setDetailId(undefined) }} />}\n </>\n )\n }\n}\n\n/**\n * Factory form used by the tab registry: the registry closes over the store.\n * Kept separate from {@link LibraryTabBodyProps} so tests can render the body\n * without a shell.\n */\nexport type LibraryTabBodyFactory = ReturnType<typeof libraryTabBody>\n","/**\n * The canvas as a right-sidebar tab body.\n *\n * The board is the same component the full-screen overlay renders; only the\n * frame changes. The board list becomes a collapsible strip instead of a fixed\n * column, because a 320px tab cannot afford a 200px sidebar next to the board.\n */\n\nimport { useEffect, useState } from 'react'\nimport type { CanvasStore } from './canvas-store.ts'\nimport type { LibraryEntry } from '../protocol.ts'\nimport { CanvasBoard, CanvasBoardList } from './CanvasBoard.tsx'\nimport { setComposerFace, type ComposerInput } from './composer-draft.ts'\nimport { Button } from './controls.tsx'\nimport css from './canvas-shell.module.css'\n\n/** What the tab body needs from the plugin's composition root. */\nexport interface CanvasTabDeps {\n store: CanvasStore\n /** The shared library, for the picture picker. */\n library: { entries: LibraryEntry[] }\n /** Load the library (the picker needs it even if the drawer was never opened). */\n onNeedLibrary: () => void\n}\n\n/**\n * Props the sidebar seat supplies.\n *\n * The seat is session-scoped, which is what makes 「加入到对话框」 possible at\n * all: it hands over the session identity and the composer's input actions.\n */\nexport interface CanvasTabBodyProps {\n /** Injected by the shell's tab seat. */\n hooks?: { tabInfo?: unknown }\n /** The session this tab is showing. */\n sessionId?: string\n /** The composer's attachment actions for that session. */\n inputActions?: ComposerInput\n}\n\n/**\n * Build the canvas tab body for one store.\n * @param deps - the canvas store and the shared library face.\n * @returns a component the tab seat can render.\n */\nexport function canvasTabBody(deps: CanvasTabDeps): (props: CanvasTabBodyProps) => JSX.Element {\n return function CanvasTabBody(props: CanvasTabBodyProps): JSX.Element {\n const [listOpen, setListOpen] = useState(false)\n const store = deps.store\n const sessionId = props.sessionId\n const inputActions = props.inputActions\n // The session-scope seat is the only place that knows which conversation the\n // user is looking at, so the composer face lives exactly as long as this tab.\n useEffect(() => {\n if (sessionId === undefined || inputActions === undefined) return\n return setComposerFace({ sessionId, input: inputActions })\n }, [inputActions, sessionId])\n // Opening a board is normally the overlay's job; the tab is its own way in,\n // so it has to load the list and open something itself — otherwise the\n // board area just says \"正在读取画布…\" forever.\n useEffect(() => {\n if (store.getSnapshot().board !== undefined) {\n void store.refreshList()\n return\n }\n void (async () => {\n await store.refreshList()\n if (store.getSnapshot().board !== undefined) return\n const first = store.getSnapshot().boards[0]\n await store.open(first?.id ?? '')\n })()\n }, [store])\n return (\n <div className={css.tabOverlay} data-dsh-seework-canvas-tab=\"\">\n <header className={css.tabHeader}>\n <Button onClick={() => { setListOpen(open => !open) }} title=\"切换画布列表\">\n {listOpen ? '收起列表' : '画布列表'}\n </Button>\n <span className={css.headerHint}>拖标题栏移动、右下角调整大小、滚轮缩放</span>\n </header>\n {listOpen\n ? (\n <div className={css.tabList}>\n <CanvasBoardList store={store} />\n </div>\n )\n : null}\n <CanvasBoard\n store={store}\n library={deps.library}\n onNeedLibrary={deps.onNeedLibrary}\n />\n </div>\n )\n }\n}\n\n/** The factory's component type, for tests. */\nexport type CanvasTabBodyFactory = ReturnType<typeof canvasTabBody>\n","/**\n * SeeWork's right-sidebar tabs: the material library and the canvas.\n *\n * The shell owns the right column, so both surfaces are contributed as tab\n * types rather than mounted on `document.body`:\n *\n * 1. `ctx.sidebarRightTabs.register({ id, kind, title })` declares the type;\n * 2. the body registers into the keyed `sidebar.right.pane.tab` seat under the\n * same id, and the chip title into `sidebar.right.pane.tab.title`.\n *\n * Opening is `ctx.sidebarRight.openTab(kind)`: the controller expands the column\n * if it was collapsed, which is what \"auto-open\" means for the shell — the\n * plugin never touches layout itself.\n *\n * Degradation: every call is guarded, and the caller keeps the floating dock. A\n * shell without the right sidebar must still be able to show these surfaces.\n */\n\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'\nimport type { LibraryStore } from './library-store.ts'\nimport type { CanvasStore } from './canvas-store.ts'\nimport type { LibraryEntry } from '../protocol.ts'\nimport { libraryTabBody } from './LibraryTab.tsx'\nimport { canvasTabBody } from './CanvasTab.tsx'\n\n/** Tab type ids, also the keys the bodies and titles register under. */\nexport const LIBRARY_TAB_ID = 'dsh-seework/library'\nexport const CANVAS_TAB_ID = 'dsh-seework/canvas'\n\n/** Tab kinds: what `openTab` names. */\nexport const LIBRARY_TAB_KIND = 'seework-library'\nexport const CANVAS_TAB_KIND = 'seework-canvas'\n\n/** The two surfaces as tab types. */\nexport interface SeeWorkTabTarget {\n id: string\n kind: string\n title: string\n}\n\n/** Both tab types in strip order. */\nexport const TAB_TARGETS: readonly SeeWorkTabTarget[] = [\n { id: LIBRARY_TAB_ID, kind: LIBRARY_TAB_KIND, title: '素材库' },\n { id: CANVAS_TAB_ID, kind: CANVAS_TAB_KIND, title: '画布' },\n]\n\n/** The stores the tab bodies render from (the same ones the fallback panels use). */\nexport interface TabStores {\n library: LibraryStore\n canvas: CanvasStore\n /** Library entries for the canvas picture picker. */\n entries: () => LibraryEntry[]\n /** Load the library when the picker asks for it. */\n onNeedLibrary: () => void\n}\n\n/**\n * The slice of `ctx` these registrations use.\n *\n * Services come through `ctx.get(name)`, never `ctx.<name>`: the client\n * runner's guard refuses an undeclared property read (and reports it as a\n * plugin failure), while `get` is the sanctioned probe that answers `undefined`\n * for a service this page does not have. Declaring the sidebar services in\n * `inject` is not an option either — a package whose provider is ordered\n * differently parks the whole plugin, which takes the settings, library and\n * canvas surfaces down with the tabs.\n */\ninterface SidebarClientFace {\n get?: (name: string) => unknown\n}\n\n/** The tab registry as this module uses it. */\ninterface TabRegistry {\n register: (definition: unknown) => () => void\n}\n\n/** The column controller as this module uses it. */\ninterface SidebarController {\n openTab: (kind: string, options?: { revealIfOpened?: boolean }) => void\n active: () => { id: string; kind: string } | undefined\n close: (tabId: string) => void\n}\n\n/** The slot registry as this module uses it. */\ninterface SlotRegistry {\n inject: (name: string, callback: () => void) => () => void\n register: (options: unknown, component: unknown) => () => void\n}\n\n/** Look up one optional service without tripping the client guard. */\nfunction service<T>(ctx: ClientContext, name: string): T | undefined {\n try {\n const value = (ctx as unknown as SidebarClientFace).get?.(name)\n return value === null || value === undefined ? undefined : value as T\n } catch (error) {\n console.warn(`[dsh-seework] service \"${name}\" is unavailable:`, error)\n return undefined\n }\n}\n\n/**\n * Is the right column part of this page at all?\n * @param ctx - client root context.\n * @returns true when both sidebar services are present.\n */\nexport function sidebarAvailable(ctx: ClientContext): boolean {\n return service<TabRegistry>(ctx, 'sidebarRightTabs') !== undefined\n && service<SidebarController>(ctx, 'sidebarRight') !== undefined\n}\n\n/**\n * Register both tab types and their bodies.\n * @param ctx - client root context carrying `slots`, `sidebarRightTabs` and `sidebarRight`.\n * @param stores - the shared stores the bodies render from.\n * @returns the disposer plus whether the tabs actually landed (a shell without\n * the column keeps its floating launchers).\n */\nexport function registerSidebarTabs(ctx: ClientContext, stores: TabStores): {\n dispose: () => void\n tabsAvailable: boolean\n} {\n const tabs = service<TabRegistry>(ctx, 'sidebarRightTabs')\n const slots = service<SlotRegistry>(ctx, 'slots')\n const disposers: Array<() => void> = []\n let registered = 0\n const guard = (work: () => (() => void) | undefined): void => {\n try {\n const dispose = work()\n if (dispose !== undefined) disposers.push(dispose)\n } catch (error) {\n console.warn('[dsh-seework] right sidebar rejected a tab:', error)\n }\n }\n\n for (const target of TAB_TARGETS) {\n guard(() => {\n const dispose = tabs?.register({\n id: target.id,\n kind: target.kind,\n title: () => target.title,\n })\n if (dispose !== undefined) registered += 1\n return dispose\n })\n const body = target.kind === LIBRARY_TAB_KIND\n ? libraryTabBody(stores.library)\n : canvasTabBody({\n store: stores.canvas,\n get library() { return { entries: stores.entries() } },\n onNeedLibrary: stores.onNeedLibrary,\n })\n // The deferred callback is guarded on its own: `slots.inject` may run it\n // immediately (a shell that already declared the slot), and a registry that\n // refuses the key throws from inside it rather than from `inject`.\n guard(() => slots?.inject('sidebar.right.pane.tab', () => {\n try {\n return slots.register({\n name: 'sidebar.right.pane.tab',\n key: target.id,\n }, body)\n } catch (error) {\n console.warn('[dsh-seework] right sidebar rejected the tab body:', error)\n return undefined\n }\n }))\n guard(() => slots?.inject('sidebar.right.pane.tab.title', () => {\n try {\n return slots.register({\n name: 'sidebar.right.pane.tab.title',\n key: target.id,\n }, () => target.title)\n } catch (error) {\n console.warn('[dsh-seework] right sidebar rejected the tab title:', error)\n return undefined\n }\n }))\n }\n\n return {\n dispose: () => {\n for (const dispose of disposers.reverse()) dispose()\n },\n tabsAvailable: registered === TAB_TARGETS.length,\n }\n}\n\n/**\n * Show one tab, expanding the column when it was collapsed.\n * @param ctx - client root context carrying `sidebarRight`.\n * @param kind - the tab kind to reveal.\n * @returns true when the request was accepted.\n */\nexport function openSeeWorkTab(ctx: ClientContext, kind: string): boolean {\n const controller = service<SidebarController>(ctx, 'sidebarRight')\n if (controller === undefined) return false\n try {\n controller.openTab(kind, { revealIfOpened: true })\n return true\n } catch (error) {\n console.warn('[dsh-seework] could not open the sidebar tab:', error)\n return false\n }\n}\n\n/**\n * Is that tab the one the column is showing right now?\n * @param ctx - client root context carrying `sidebarRight`.\n * @param kind - the tab kind to test.\n * @returns true when it is the active tab.\n */\nexport function isSeeWorkTabActive(ctx: ClientContext, kind: string): boolean {\n const controller = service<SidebarController>(ctx, 'sidebarRight')\n try {\n return controller?.active()?.kind === kind\n } catch {\n return false\n }\n}\n\n/**\n * Put one tab away.\n * @param ctx - client root context carrying `sidebarRight`.\n * @param kind - the tab kind to close, when it is the active one.\n * @returns true when a tab was closed.\n */\nexport function closeSeeWorkTab(ctx: ClientContext, kind: string): boolean {\n const controller = service<SidebarController>(ctx, 'sidebarRight')\n try {\n const active = controller?.active()\n if (active === undefined || active.kind !== kind) return false\n controller?.close(active.id)\n return true\n } catch (error) {\n console.warn('[dsh-seework] could not close the sidebar tab:', error)\n return false\n }\n}\n","/**\n * Browser-half entry for the dsh-seework plugin — runs inside the dsh web GUI.\n *\n * Responsibilities:\n * 1. bind the plugin's own settings scope (its bridge routes serve the\n * namespace the shell's settings transport does not know about);\n * 2. register the settings card into the shell's plugin-configuration slot\n * when that slot exists, and fall back to a DOM-mounted settings panel\n * when it does not — a plugin must never be un-configurable;\n * 3. declare the not-yet-typed slot.\n *\n * Failure policy: DOM and slot problems are logged, never thrown. The web shell\n * fails the whole boot when a plugin apply throws, and an external plugin must\n * not take the GUI down.\n */\n\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { SeeWorkApi } from './api.ts'\nimport { SeeWorkSettingsCard, type SettingsCardFace } from './SettingsCard.tsx'\nimport { mountCanvasSurfaces, stageSizeFor } from './canvas-panel.tsx'\nimport { CanvasStore } from './canvas-store.ts'\nimport { watchGenerations } from './generation-watch.ts'\nimport { HeaderLaunchers, onHeaderPresence, setLauncherControls, type LauncherControls } from './header-launchers.tsx'\nimport { LibraryStore } from './library-store.ts'\nimport { mountLibraryPanel } from './library-panel.tsx'\nimport { bindSeeWorkScope, type SeeWorkScope } from './settings-scope.ts'\nimport { mountSettingsPanel, requestSurface } from './settings-panel.tsx'\nimport { installChatImageZoom } from './chat-images.tsx'\nimport { createCanvasAddFace, libraryFileFromUrl, setCanvasAddFace } from './canvas-add.ts'\nimport { setComposerProbe, type ComposerDrafts } from './composer-draft.ts'\nimport { registerToolCards } from './tool-card.tsx'\nimport {\n CANVAS_TAB_KIND,\n LIBRARY_TAB_KIND,\n closeSeeWorkTab,\n isSeeWorkTabActive,\n openSeeWorkTab,\n registerSidebarTabs,\n sidebarAvailable,\n type TabStores,\n} from './sidebar-tabs.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface SlotMap {\n /**\n * One page inside the Plugins settings section, declared at runtime by the\n * feature that owns that section. Spelled here so this package can register\n * without depending on the sibling UI package; a shell that does not declare\n * it makes the registration throw, which the client half turns into the\n * DOM-mounted fallback instead of a boot failure.\n */\n 'settings.plugin.item': { kind: 'keyed'; scope: 'root'; owner: SettingsPluginItemOwnerProps }\n }\n}\n\n/** Owner share of a plugin card (the section supplies nothing). */\nexport interface SettingsPluginItemOwnerProps {\n /** Marker field: card owner props are intentionally empty. */\n children?: never\n}\n\n/** The slot face registered into `settings.plugin.item`. */\ninterface PluginCardProps extends SettingsPluginItemOwnerProps, SettingsCardFace {}\n\n/**\n * The plugin's client half declares only `slots` as a required service.\n *\n * The right-sidebar services are optional for this plugin: declaring them would\n * park the WHOLE plugin (settings, library and canvas included) whenever the\n * boot graph orders the column's own plugin differently, which is a worse\n * failure than not having tabs. They are probed through `ctx.get` instead.\n */\nexport const inject = ['slots']\n\n/** Settings namespace this plugin owns. */\nconst NS = 'dsh-seework'\n\n/**\n * Position of the SeeWork page among the settings dialog's nav entries.\n * The shipped sections use 10 (models), 15 (plugins) and 20 (agent presets), so\n * a larger number lands after them — the end of the list, where a third-party\n * plugin belongs.\n */\nconst SETTINGS_SECTION_ORDER = 40\n\n/**\n * Mount the plugin's browser surfaces.\n * @param ctx - client root context (services: slots).\n */\nexport function apply(ctx: ClientContext): void {\n const scope: SeeWorkScope = bindSeeWorkScope()\n const api = new SeeWorkApi()\n\n // The floating panel mounts first and is removed once the shell takes a\n // settings surface. Ordering it this way (rather than predicting the shell's\n // answer up front) means there is never a window in which SeeWork has no\n // reachable settings UI at all — a slot's answer only arrives on a later tick.\n const disposeFloatingSettings = mountFallback(scope, api)\n let floatingSettingsRetired = false\n const retireFloatingSettings = (): void => {\n if (floatingSettingsRetired) return\n floatingSettingsRetired = true\n disposeFloatingSettings?.()\n }\n // One settings page, one place. The nav page is the home; the tab inside the\n // Plugins section is only the fallback for a shell that has no nav slot at\n // all, because offering the same card twice is just clutter.\n //\n // The nav slot may already be declared when this runs (its callback fires\n // immediately) or be declared later (its callback fires then), so both orders\n // have to end up at the same place: if the section landed, do not keep a tab.\n let sectionLanded = false\n let disposeSlot: (() => void) | undefined\n const disposeSection = registerSettingsSection(ctx, scope, api, () => {\n sectionLanded = true\n retireFloatingSettings()\n disposeSlot?.()\n disposeSlot = undefined\n })\n if (!sectionLanded) disposeSlot = registerSettingsSlot(ctx, scope, api, retireFloatingSettings, () => sectionLanded)\n\n // The canvas store is created here rather than inside the panel mount: the\n // right-sidebar tab and the floating fallback must render the SAME board, and\n // two stores would mean two boards fighting over one document.\n const canvas = new CanvasStore(api)\n\n // The library store is shared: the drawer and the library tab show it, the\n // canvas picks pictures from it, and two stores would mean one of them showing\n // a stale list.\n const library = new LibraryStore(api)\n // The floating launchers mount first: the right column's services only exist\n // once its own plugin has applied, which the boot graph may order after this\n // one. When the tabs do land, the launchers retire themselves.\n const disposeLibrary = mountLibrary(api, library)\n const disposeCanvas = mountCanvas(api, library, canvas)\n // A floating launcher is hidden — never removed — while the conversation\n // header shows its own buttons, and comes back the moment those go away (the\n // home screen has no session, so it has no header). Removing them instead\n // would leave a shell with no way in at all.\n const stopPresence = onHeaderPresence(present => { setFloatingLaunchersHidden(present) })\n const attachTabs = registerSidebarTabsWhenReady(ctx, {\n library,\n canvas,\n entries: () => library.getSnapshot().entries,\n onNeedLibrary: () => { void library.refresh() },\n })\n // Top-right entry points. The floating dock stays even when the header slot\n // accepts them: an external plugin must remain reachable on a shell that does\n // not expose the header slot or the right column at all.\n const launchers = registerHeaderLaunchers(ctx)\n // The conversation card for this plugin's own tool results. The result text is\n // JSON on purpose (text-only models must keep working), so without this view a\n // finished generation shows up as a wall of JSON instead of its pictures.\n const disposeToolCards = registerToolCards(ctx)\n // Generated pictures are big; the transcript shows them capped and one click\n // opens the original in place. Independent of the view above, because the\n // assistant's own markdown reply renders images in the shell's markup.\n const disposeChatImageZoom = installChatImageZoom(document, src => {\n // A markdown reply image points at the library route, so its file name is in\n // the URL and the library record supplies the rest.\n const file = libraryFileFromUrl(src)\n return file === undefined ? undefined : { file }\n })\n // 「加到画布」: the user points at one picture, and only then does the plugin\n // place a card and bring the canvas up. Automatic placement stayed removed.\n const restoreCanvasAdd = setCanvasAddFace(createCanvasAddFace({\n library,\n canvas,\n reveal: () => {\n if (openSeeWorkTab(ctx, CANVAS_TAB_KIND)) return\n requestSurface('canvas')\n },\n stageSize: () => stageSizeFor(canvas),\n }))\n // 「加入到对话框」 needs the shell's conversation service; the session identity\n // comes from whichever of our tab bodies is mounted (they are session-scoped).\n // A probe, not a captured value: plugins attach in boot order, so resolving at\n // call time is the only thing that cannot come up empty by accident.\n const restoreComposer = setComposerProbe(() => probeService<ComposerDrafts>(ctx, 'conversation'))\n // Point the header buttons at the right-sidebar tabs; without the column they\n // keep working through the floating surfaces.\n const restoreControls = setLauncherControls(launchers.controls)\n // The host pushes nothing, so the page asks what the newest entry is; a change\n // means a generation finished (an agent one included) and the shared library\n // store is refreshed. Deliberately nothing else: the canvas is NOT opened and\n // nothing is placed on it — a background generation must not rearrange the\n // screen, and adding a picture to a board is the canvas's own 「从素材库选图」.\n const stopWatching = watchGenerations({ api, library })\n\n ctx.effect(() => () => {\n stopWatching()\n stopPresence()\n restoreControls()\n launchers.dispose()\n disposeToolCards()\n disposeChatImageZoom()\n restoreCanvasAdd()\n restoreComposer()\n attachTabs.dispose()\n disposeSection?.()\n disposeSlot?.()\n disposeFloatingSettings?.()\n disposeLibrary?.()\n disposeCanvas?.()\n }, 'dsh-seework: settings, library and canvas surfaces')\n}\n\n/**\n * Probe one optional service without tripping the client guard.\n *\n * Services come through `ctx.get(name)`, never `ctx.<name>` (the guard refuses\n * an undeclared property read), and never through `inject` (a package ordered\n * differently would park the whole plugin — settings, library and canvas with it).\n *\n * @param ctx - client root context.\n * @param name - the service name.\n * @returns the service, or undefined when this page has none.\n */\nfunction probeService<T>(ctx: ClientContext, name: string): T | undefined {\n try {\n const value = (ctx as unknown as { get?: (name: string) => unknown }).get?.(name)\n return value === null || value === undefined ? undefined : value as T\n } catch (error) {\n console.warn(`[dsh-seework] service \"${name}\" is unavailable:`, error)\n return undefined\n }\n}\n\n/**\n * Show or hide the floating library/canvas launchers.\n *\n * Hidden rather than removed: they are the home screen's only way into the two\n * surfaces (the conversation header, and with it the header buttons, exists\n * only inside a session), so they have to be able to come back. The settings\n * button is left alone — its slot is the one a shell is most likely to lack.\n *\n * @param hidden - true while the conversation header's own buttons are on screen.\n */\nfunction setFloatingLaunchersHidden(hidden: boolean): void {\n if (typeof document === 'undefined') return\n for (const attr of ['data-dsh-seework-library-launcher', 'data-dsh-seework-canvas-launcher']) {\n document.querySelectorAll<HTMLElement>(`[${attr}]`).forEach(button => {\n button.style.display = hidden ? 'none' : ''\n })\n }\n}\n\n/**\n * Register the right-sidebar tabs as soon as the column's services exist.\n *\n * The services are optional (see the `inject` note), so this probes for them on\n * an interval — cheap, bounded, and it never throws when they never appear. The\n * tabs only matter once the user opens something, so a short delay costs\n * nothing, while parking the whole plugin on the dependency would take the\n * settings, library and canvas surfaces down with it.\n *\n * @param ctx - client root context.\n * @param stores - the shared stores the tab bodies render from.\n * @param onAvailable - called once the tabs actually registered.\n * @returns disposer cancelling the watch and unregistering what landed.\n */\nfunction registerSidebarTabsWhenReady(\n ctx: ClientContext,\n stores: TabStores,\n): { dispose: () => void } {\n let tabs: { dispose: () => void; tabsAvailable: boolean } | undefined\n let cancelled = false\n let attempts = 0\n const MAX_ATTEMPTS = 40\n const attempt = (): void => {\n if (cancelled || tabs !== undefined) return\n attempts += 1\n if (sidebarAvailable(ctx)) {\n const registered = registerSidebarTabs(ctx, stores)\n if (registered.tabsAvailable) {\n tabs = registered\n return\n }\n // Services exist but the registry refused: retrying will not help.\n registered.dispose()\n cancelled = true\n return\n }\n if (attempts < MAX_ATTEMPTS) setTimeout(attempt, 250)\n }\n // The column's plugin may already have applied (a reload), in which case the\n // very next tick is enough.\n setTimeout(attempt, 0)\n return {\n dispose: () => {\n cancelled = true\n tabs?.dispose()\n tabs = undefined\n },\n }\n}\n\n/**\n * Contribute the SeeWork entry buttons to the conversation header's utilities,\n * and teach them to work the right-sidebar tabs.\n *\n * Same degradation contract as the settings slot: a shell without the utilities\n * slot simply never calls the injection callback, leaving the floating dock as\n * the way in. A shell with the header but without the right column keeps the\n * buttons working through the floating surfaces.\n *\n * @param ctx - client root context (services: slots).\n * @returns the disposer and the launcher controller face.\n */\nfunction registerHeaderLaunchers(ctx: ClientContext): {\n dispose: () => void\n controls: LauncherControls\n} {\n const controls: LauncherControls = {\n isOpen: surface => isSeeWorkTabActive(ctx, surface === 'library' ? LIBRARY_TAB_KIND : CANVAS_TAB_KIND),\n open: surface => {\n const kind = surface === 'library' ? LIBRARY_TAB_KIND : CANVAS_TAB_KIND\n if (openSeeWorkTab(ctx, kind)) return true\n // No right column on this shell: fall back to the floating surface.\n requestSurface(surface)\n return false\n },\n close: surface => {\n const kind = surface === 'library' ? LIBRARY_TAB_KIND : CANVAS_TAB_KIND\n if (closeSeeWorkTab(ctx, kind)) return true\n requestSurface(surface === 'library' ? 'canvas' : 'library')\n return false\n },\n }\n\n try {\n const slots = (ctx as unknown as { slots?: { inject?: unknown; register?: unknown } }).slots\n if (slots === undefined || typeof slots.inject !== 'function' || typeof slots.register !== 'function') {\n return { dispose: () => {}, controls }\n }\n let dispose: (() => void) | undefined\n const injected = slots.inject as (name: string, callback: () => void) => () => void\n const stop = injected.call(slots, 'conversation.session.header.utilities', () => {\n try {\n dispose = (slots.register as (options: unknown, component: unknown) => () => void).call(slots, {\n name: 'conversation.session.header.utilities',\n id: NS,\n order: -20,\n }, HeaderLaunchers as unknown as () => JSX.Element)\n } catch (error) {\n console.warn('[dsh-seework] header utilities slot rejected the launchers:', error)\n }\n })\n return {\n dispose: () => {\n dispose?.()\n stop()\n },\n controls,\n }\n } catch (error) {\n console.warn('[dsh-seework] header utilities slot unavailable:', error)\n return { dispose: () => {}, controls }\n }\n}\n\n/**\n * Contribute the SeeWork page to the settings dialog's own navigation.\n *\n * `settings.plugin.item` is a tab INSIDE the Plugins section, which is easy to\n * miss; `settings.section` is a first-class page in the nav list beside\n * 通用设置 / 模型 / 插件 / Agent 预设, which is where a user looks for a plugin\n * they just installed. The shipped sections order themselves 10 / 15 / 20, so\n * this one sits after them.\n *\n * While this section is accepted, {@link registerSettingsSlot} is not kept: the\n * same card in two places is clutter. A shell without the nav slot still gets\n * the Plugins tab, and one without either keeps the floating panel.\n *\n * @param ctx - client root context (services: slots).\n * @param scope - the bound settings scope the card reads and writes.\n * @param api - the plugin route client.\n * @param onAccepted - called once the nav actually took the section.\n * @returns the disposer when the slot exists, undefined when it does not.\n */\nfunction registerSettingsSection(\n ctx: ClientContext,\n scope: SeeWorkScope,\n api: SeeWorkApi,\n onAccepted: () => void,\n): (() => void) | undefined {\n try {\n const slots = (ctx as unknown as { slots?: { inject?: unknown; register?: unknown } }).slots\n if (slots === undefined || typeof slots.inject !== 'function' || typeof slots.register !== 'function') return undefined\n const face: SettingsCardFace = { scope, api }\n let dispose: (() => void) | undefined\n const injected = slots.inject as (name: string, callback: () => void) => () => void\n const stop = injected.call(slots, 'settings.section', () => {\n try {\n dispose = (slots.register as (options: unknown, component: unknown) => () => void).call(slots, {\n name: 'settings.section',\n id: NS,\n order: SETTINGS_SECTION_ORDER,\n label: () => 'SeeWork',\n inject: () => face,\n }, SeeWorkSettingsCard as unknown as (props: { close: () => void } & SettingsCardFace) => JSX.Element)\n onAccepted()\n } catch (error) {\n console.warn('[dsh-seework] settings navigation rejected the section:', error)\n }\n })\n return () => {\n dispose?.()\n stop()\n }\n } catch (error) {\n console.warn('[dsh-seework] settings navigation slot unavailable:', error)\n return undefined\n }\n}\n\n/**\n * Contribute the settings card to the shell's plugin-configuration slot.\n *\n * Both the registration AND the deferred `inject` callback are guarded. A shell\n * that never declares the slot simply never runs the callback — that is the\n * `slots.inject` contract, not an error — so the fallback panel stays until\n * `onAccepted` says a card actually landed. A callback that throws (the\n * registry refuses a slot it considers occupied, say) is logged and leaves the\n * fallback in place; it must not fail the plugin's fiber, because the shell\n * reports a failed fiber to the user as a boot problem.\n *\n * @param onAccepted - called once the slot has actually accepted the card, so\n * the caller can retire its fallback surface.\n * @returns the disposer when the slot exists, undefined when it does not.\n */\nfunction registerSettingsSlot(\n ctx: ClientContext,\n scope: SeeWorkScope,\n api: SeeWorkApi,\n onAccepted: () => void,\n /**\n * Asked when this slot's deferred callback finally runs: true means another\n * settings surface already won, so this registration must not happen at all.\n * Without it, a nav page that lands first would leave this tab registered by a\n * callback that arrives afterwards — the duplicate this fallback exists to\n * avoid.\n */\n skipWhen?: () => boolean,\n): (() => void) | undefined {\n try {\n const slots = (ctx as unknown as { slots?: { inject?: unknown; register?: unknown } }).slots\n if (slots === undefined || typeof slots.inject !== 'function' || typeof slots.register !== 'function') return undefined\n const face: SettingsCardFace = { scope, api }\n let dispose: (() => void) | undefined\n const injected = slots.inject as (name: string, callback: () => void) => () => void\n const stop = injected.call(slots, 'settings.plugin.item', () => {\n if (skipWhen?.() === true) return\n try {\n dispose = (slots.register as (options: unknown, component: unknown) => () => void).call(slots, {\n name: 'settings.plugin.item',\n key: NS,\n inject: () => face,\n }, SeeWorkSettingsCard as unknown as (props: PluginCardProps) => JSX.Element)\n onAccepted()\n } catch (error) {\n console.warn('[dsh-seework] settings slot rejected the card:', error)\n }\n })\n return () => {\n dispose?.()\n stop()\n }\n } catch (error) {\n console.warn('[dsh-seework] settings slot unavailable:', error)\n return undefined\n }\n}\n\n/** DOM-mounted settings launcher (the no-slot path). */\nfunction mountFallback(scope: SeeWorkScope, api: SeeWorkApi): (() => void) | undefined {\n try {\n return mountSettingsPanel(scope, api)\n } catch (error) {\n console.warn('[dsh-seework] settings panel mount failed:', error)\n return undefined\n }\n}\n\n/**\n * Mount the material-library launcher and drawer. A DOM mount failure degrades\n * the library only — the plugin never takes the GUI down with it.\n */\nfunction mountLibrary(api: SeeWorkApi, store: LibraryStore): (() => void) | undefined {\n try {\n return mountLibraryPanel(api, store)\n } catch (error) {\n console.warn('[dsh-seework] library panel mount failed:', error)\n return undefined\n }\n}\n\n/**\n * Mount the canvas launcher and overlay. Sharing the library store means the\n * picture picker shows what the drawer shows.\n */\nfunction mountCanvas(api: SeeWorkApi, store: LibraryStore, canvas: CanvasStore): (() => void) | undefined {\n try {\n return mountCanvasSurfaces(api, store, canvas)\n } catch (error) {\n console.warn('[dsh-seework] canvas mount failed:', error)\n return undefined\n }\n}\n"],"mappings":"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"}