DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

janewas /

janewas/dsh-image-annotate

Verified

Annotate a pending composer image in place (pen / box / arrow / typed text) and insert the PNG back next to the original. Minimal footprint: nothing in DSH UI is restyled, no host routes, no dependencies.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@f17ce39f

dsh-image-annotate

A DeepSeek Harness (dsh) web plugin: click a pending image in the composer to annotate it, then insert the annotated copy back as a second attachment. The original stays.

English | 中文

Sending a screenshot to the agent usually means circling something first. The composer's pending images were preview-only, so the circle had to happen in another app and the file had to be re-attached. This plugin moves that step into the composer.

Minimal footprint. Nothing in DSH's own UI is restyled: the attachment rail, drop target, file cards and the original-image lightbox stay exactly as shipped — the plugin only takes over the click on a pending thumbnail. One browser-side file, an empty host stub, no host routes, no dependencies, no configuration files, and no work at all until you click an image.

Annotation editor

Features

  • Click a pending image in the composer to open the annotation dialog instead of the plain original-image lightbox. The dialog shows the image large and lets you draw on it.
  • Four tools: pen (freehand), box, arrow, and text.
  • Text lives in a frame you can move and resize: with the text tool, drag a frame (a plain click gives one at a default size) and type inside it. Lines wrap to the frame and the font size is solved for it, so a long paragraph shrinks instead of running off the image — nothing is ever truncated. Afterwards drag the frame's body to move it, or any of its eight handles to resize it; the text re-fits as you go, and the frame refuses to shrink below what its text needs at the minimum readable size. Double-click inside to change the text, select and press Delete to remove it, Ctrl/Cmd+Z to undo. The frame has a shape (rectangle / rounded / ellipse), uses the current colour, and can be filled (transparent / tinted / solid); transparent keeps only the border and the text. Shift+Enter starts a new line.
  • Text frames are separate from the box tool: the box tool still just draws an outline, and text frames are only selectable while the text tool is active, so circling something and labelling it never interfere.
  • Six colours (red, yellow, green, blue, white, black) and three widths (thin / medium / thick) stored as a fraction of the image's short side, so they look the same on a phone screenshot and on a 4K capture.
  • Undo (button or Ctrl/Cmd+Z), clear, Escape or backdrop press to dismiss.
  • Insert as a new attachment: the result is added to the composer as <name>-<suffix>.png, next to the image it came from. The annotated file is an ordinary attachment — it can be opened and annotated again.
  • Settings page (Settings → 图片标注) with the plugin's defaults; see below.
  • Nothing about the frame is guessed from the image: shape, colour, fill and the solver are the whole text feature.
  • Works in light and dark themes, and does not restyle any of DSH's own UI.

Result: the annotated copy is added next to the original

Install

Requires DSH 0.1.5-rc.1 or a compatible release (dsh web, profile web); verified on that version.

From GitHub (recommended)

dsh plugin --profile web add github:janewas/dsh-image-annotate

This adds the package to the profile's bundle stack, so the plugin's own cordis.patch.yml mounts it. The bundle list is read at startup, so restart dsh web afterwards, then refresh the page.

From the release tarball (no pnpm, no restart)

Download dsh-image-annotate.tgz from Releases, then:

tar -xzf dsh-image-annotate.tgz
cd package
bash install.sh          # pass a profile name for anything other than web

install.sh is idempotent. It links the directory into ~/.dsh/profiles/web/node_modules/ and appends the mount row to that profile's cordis.patch.yml, backing the file up first. Profile patches apply live, so just refresh the page — no restart. Keep the extracted directory where it is; the profile links to it.

Both routes end in the same mount. The row written by install.sh disables itself automatically if the package is later installed as a bundle, so following both does not double-mount.

Usage

  1. Add an image to the composer as usual (drag it in, or use the paperclip).
  2. Click its thumbnail. The annotation dialog opens with the image at full size.
  3. Draw: pick a tool, a colour and a width; for text, click a spot and type.
  4. Click 「插入到输入框」 (Insert into composer). The annotated PNG appears at the end of the attachment rail, and the original is still there.
Tool Interaction
Pen drag to draw freehand
Box drag from one corner to the other
Arrow drag from tail to head
Text drag a frame, type inside it; Enter confirms (Esc cancels, clicking elsewhere confirms, Shift+Enter adds a line)
Move / resize a frame with the text tool: drag the frame body to move, drag one of the eight handles to resize. The text re-fits live, and the frame stops shrinking when the text would no longer fit at the minimum readable size
Edit / delete a frame double-click inside it to change the text (clearing the text deletes the frame); select it and press Delete. Ctrl/Cmd+Z undoes a move, a resize, a text change or a delete

Undo and Clear sit in the toolbar (Ctrl/Cmd+Z also undoes). Both stay available while you are typing inside a frame: Undo discards the text you have not committed yet, Clear discards it and wipes the committed annotations; they are greyed out only when there is genuinely nothing to undo or clear. Close with Esc, the 关闭 button, or a press on the backdrop.

Settings

Settings → 图片标注, after the built-in sections.

Settings page

Setting Default Effect
启用点击编辑 (Enable click-to-edit) on Master switch. Turned off, clicking a thumbnail opens DSH's original-image lightbox again and the plugin does nothing.
默认工具 (Default tool) pen Tool pre-selected when the dialog opens.
默认颜色 (Default colour) red Starting colour for pen, box, arrow and text.
默认线宽 (Default width) medium Line width for pen, box and arrow. Text size is not set here — the frame decides it.
文字框形状 (Text frame shape) rounded Shape of the frame a new text box is drawn with.
文字框底纹 (Text frame fill) tinted Transparent keeps only the border and the text; tinted and solid fill the frame so the type stands out.
插入方式 (Insert mode) append append keeps the original; replace removes it once the annotated copy is in.
标注图文件名 (Output file name) 标注 <name>-<suffix>.png. Leave empty to only change the extension.

「恢复默认」 resets everything. Settings live in the browser's localStorage under dsh-image-annotate.settings.v1: they apply to that browser and origin only, so a different origin (a LAN or Tailscale address, say) starts from the defaults. The plugin writes no host configuration.

Dark theme

How it works

Two halves, in lib/:

  • lib/index.js — the host half, deliberately empty. Its only job is to give the loader an entry, so dsh-client-modules scans the package's dsh.client declaration and puts lib/client.js in the browser plugin graph.
  • lib/client.js — the browser half; everything else lives here.

Key points for anyone changing it:

  • Mount point: conversation.input.overlay, a session-scoped list slot. Its standard props supply sessionId and inputActions; the component renders null until a thumbnail is clicked, and installs one capture-phase click listener. The editor is portalled to document.body, because the composer card establishes a containing block for position: fixed.
  • Hit test: a click whose target is an <img> inside [data-composer-card] and whose src equals a draft attachment's previewUrl. Pending thumbnails use their own object URL, so the match is exact and never catches a transcript image. A hit calls stopPropagation(), which suppresses DSH's own lightbox for that click only.
  • Inserting: conversation.createDrafts(sessionId, [file]) registers the file, then inputActions.addAttachments(ids) appends it through the same admission path the paperclip uses; a refusal (a submission in flight) rolls back and reports inside the dialog.
  • Canvas: a base canvas holds the image at its natural size and is painted once; an overlay canvas holds the annotations and is repainted whole. Shapes are stored in image pixels with width/size as a short-side fraction, and exporting composites both onto one canvas for toBlob('image/png'). Display size is CSS only, so the exported bitmap is always the original resolution.
  • Text: dragging with the text tool draws a frame; a <textarea> is then laid over it, styled to match the committed result. Line breaking and the font size come from one solver shared by the preview and the export: fitTextInBox wraps the text token by token (CJK characters break anywhere, Latin stays whole and is hard-split only if a single word cannot fit) and binary-searches the largest size whose wrapped lines still fit inside the frame — if even the minimum size cannot fit, the frame grows taller instead of clipping. Enter/blur commits {type:'textbox', x, y, w, h, text, color, shape, fill}; the frame is filled and stroked, and the text is centred inside with a contrasting colour over a solid fill and an outline over anything lighter.
  • Settings: registered into settings.section and stored with a small localStorage + useSyncExternalStore store that the dialog reads each time it opens.
  • No dependencies: the browser half requires only the platform seed modules (react, react-dom); the host half imports nothing. dsh.client.inject is therefore empty and there is no load-order constraint.

Known limitations

  • Pending composer images only. A transcript image still opens DSH's lightbox.
  • Export is always PNG: animated GIFs lose their animation, and very large images (tens of megapixels) use a lot of memory.
  • Very long text in a small frame becomes small; if it still cannot fit at the minimum readable size the frame grows downwards rather than clipping.
  • No crop or rotate.
  • One image at a time.

Uninstall

Remove the mount row (id: image-annotate for the bundle route, id: image-annotate-manual for install.sh) from ~/.dsh/profiles/web/cordis.patch.yml, drop the node_modules/dsh-image-annotate link if install.sh created one, and refresh the page. For dsh plugin installs, run dsh plugin --profile web remove dsh-image-annotate and restart.

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit f17ce39fc556

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout