dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
An Images workspace for DeepSeek Harness: generate with fal.ai, browse every result, compare candidates, animate the winner in Kling, and assemble a cut with ffmpeg.
An Images workspace for DeepSeek Harness: one page to generate with fal.ai or a ComfyUI you run yourself, keep every result in the shared studio or inside a project, compare candidates, import a picture by link, and hand the winner to Kling as an image-to-video shot. The model can also generate into the same library straight from the conversation.
Images
┌────────────────────────────────────────────────────────────────────────────┐
│ Describe an image… │
│ Model: FLUX.2 Klein 9B ▾ [+ Own model] Aspect: 1:1 ▾ │
│ Variants: 4 ▾ [Generate] │
├────────────────────────────────────────────────────────────────────────────┤
│ Gallery | Templates | Montage All · Images · Videos · Favourites │
│ [Import by link] [Search prompts] │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 🖼 │ │ 🖼 │ │ 🎬 5s │ │ 🖼 │ click → lightbox │
│ └────────┘ └────────┘ └────────┘ └────────┘ → Animate in Kling │
└────────────────────────────────────────────────────────────────────────────┘
fal-ai/flux-2/klein/9b (FLUX.2 Klein 9B — fast and inexpensive), openai/gpt-image-2 (GPT Image 2 — quality tier,
text rendering), fal-ai/nano-banana-2 (Nano Banana 2 — Gemini, 0.5K–4K).owner/name) in the plugin's own settings, and it appears in the picker like a built-in.image_generate from a conversation and
the picture lands in the same library the page reads.<DSH_HOME>/image-studio is the default; the
Media source control in the page header can point the same page at <project>/dsh-media instead, so screenshots
and clips sit beside the code they belong to. The root is resolved per request, so the switch needs no restart.http(s) link to an image or a video and it is downloaded into the same gallery.| Requirement | Needed for |
|---|---|
DSH core 0.2.0-rc.2 (also qualified on 0.2.0-rc.1) | The host half and the page. See compatibility.json. |
Node.js ^22.19.0 || >=24.0.0 | Everything; package.json declares it and CI runs Node 22.x and 24.x. |
ffmpeg on PATH, or ffmpegPath in the row configuration | The Montage tab only. Without it the tab says so and hides the action; generation, the gallery and Kling are unaffected. |
| A fal.ai account and API key | The fal.ai provider and Kling. Browsing, importing, the montage and the local ComfyUI path need no key. |
| A local ComfyUI server (optional) | The Local ComfyUI provider only. The plugin connects to a server that is already running (by default http://127.0.0.1:8188) and downloads no models, workflows or nodes. |
@deepseek-ai/dsh-tools and the harness tools service (optional) | The image_generate chat tool. Without either, the page and the plugin keep working. |
| Windows or macOS | Both are supported. No native modules, no build step, no shell invocation; the only external process is ffmpeg, and only for the montage. |
React 18 and the DSH client packages the page builds on are supplied by the harness as optional peer dependencies, so installing from Git needs no build-script approval.
Open Plugins → Add plugin.
Paste the repository spec and install:
github:naletko/dsh-image-studio
A local clone works too — paste the absolute path to the folder. The bare name dsh-image-studio also works when the
package is available from the npm registry the profile uses.
Install, then restart the harness only if the page asks for it. The sidebar then shows Images.
dsh plugin --profile web add github:naletko/dsh-image-studio # web profile
dsh plugin --profile desktop add github:naletko/dsh-image-studio # only while DSH Desktop is fully quit
dsh --profile web --dump-config # compose the tree without booting
The desktop profile belongs to the DSH Desktop application: it refuses terminal management while the app is running,
so use Plugins → Add plugin there.
To remove the plugin later, use Plugins → Remove or dsh plugin --profile web remove dsh-image-studio. The library
folder (the shared studio or a project's dsh-media), the credentials and the other plugins are left untouched.
The key is a credential reference, never a configuration value. Configuration (cordis.patch.yml, the profile
patch, config.json) holds only the name of the reference — by default FAL_API_KEY — and never the key itself.
Get the key from fal.ai → Keys; it is the whole key_id:key_secret string.
The easiest way to store it:
key_id:key_secret string and press Save. The plugin writes it through the harness credential
seam (ctx.credentials).The harness resolves the reference in its own order:
the environment the harness was launched with (FAL_API_KEY=… dsh) — read-only for the app;
the credential store, ~/.dsh/.credentials.yaml:
version: 1
refs:
FAL_API_KEY: <key_id>:<key_secret>
<workspace>/.env, then ~/.dsh/.env.
If the key comes from the environment, the card says so and saving from the page is disabled — the environment wins.
The reference name can be changed in the row's configuration with falKeyRef, which is useful when a machine keeps its
keys under a different name. The key is read from the store on every call, so rotating it needs no restart.
The picker ships three fal endpoints (see What you get). For anything else:
owner/name part of the endpoint URL, for example fal-ai/qwen-image.config.json in the studio folder), appears in
the picker marked · custom, and is selected straight away. Remove it with the ✕ chip; up to 40 slugs are kept.Only a well-formed owner/name slug is accepted — a bare word, a URL or a duplicate of a built-in is ignored rather
than stored, and one bad entry never discards the rest of the list. The plugin does not carry a catalogue of fal
models: it sends the prompt plus the fields the selected family documents, so an endpoint that needs a field this
plugin does not send fails with fal's own error text, which the page shows verbatim.
Generation can also run on a ComfyUI you started yourself. The plugin downloads nothing — no model weights, no
workflows, no custom nodes. It only talks to a server that is already running, and the default address is
http://127.0.0.1:8188.
http(s) on 127.0.0.1, localhost or ::1, and it is checked when
the setting is saved and again before every request, so a hand-edited profile patch cannot point a generation at
someone else's machine.localWorkflow (which graph to run), localModel, localClip, localVae
(the model file names the graph's placeholders stand for) and localSteps.workflow/qwen-image.json is a standard ComfyUI API-format graph
with the placeholders {{prompt}}, {{width}}, {{height}}, {{seed}}, {{steps}}, {{model}}, {{clip}} and
{{vae}}. The model file names are deliberately left empty — this repository ships no weights and names none. Fill them
from the settings above, or point localWorkflow at a graph you exported from ComfyUI yourself.UnetLoaderGGUF instead of UNETLoader. A graph the server rejects is reported with ComfyUI's own message.count > 1 runs sequentially — one job per image with a different seed — so it takes proportionally longer.width/height: 1:1 → 1024×1024, 4:3 → 1024×768, 3:4 → 768×1024, 16:9 → 1280×720,
9:16 → 720×1280.local:<model file> (local:comfyui when no file is set) with the label ComfyUI · <model file>, and behave like any
other entry: view, download, favourite, delete, Animate in Kling.On a harness that mounts the tools service and can load @deepseek-ai/dsh-tools, the plugin registers one chat tool,
image_generate. Ask the model for a picture and the result is written into the same library the page shows — the
shared studio or the current project's media, whichever source is selected.
| Parameter | ||
|---|---|---|
prompt | required | What to draw: subject, composition, style, lighting, and any exact text to render. |
aspect | optional | One of 1:1, 4:3, 3:4, 16:9, 9:16; defaults to the page's aspect. |
count | optional | How many variations to request (1–8); defaults to the model's or the page's count. |
model | optional | Any fal endpoint slug, e.g. fal-ai/flux-2/klein/9b; defaults to the configured model. |
The result carries a one-line summary, the prompt, the model id and label, the aspect, how many images were stored, the
library root, a markdown reference per image, and every saved file with its id, file, markdown, mime, bytes,
url and — when the provider reported them — width and height.
A failure is a failed tool call, not a value: a missing key, an invalid or unknown endpoint (401/404), a timeout, an
empty answer, an argument that is not an owner/name slug, or a write error all raise a short human sentence. Those
messages never contain the fal key and never contain this machine's file paths; a full path appears only in a successful
result's markdown link.
The tool runs the same generation code and writes into the same library root as the page — there is no second implementation — and it is fal-only: a local ComfyUI generation is started from the page. It is registered only when the composition mounts the tools service and the package can be loaded; without either, the plugin and the page work as usual and the tool is simply not offered.
Gallery → Import by link → paste a direct link to an image or a video file → Download.
The host downloads the link server-side and stores the result in the same gallery as any other entry (the link stays as
its caption). Only links a plugin should fetch are accepted: http and https only, and loopback, private, link-local
and .local/.internal hosts are refused, so the route cannot be used as a probe into your own network. Imported media
still has to fit the studio's per-file cap (64 MB).
Open a still in the lightbox and press Animate in Kling:
The chosen still is sent to fal as a data URI, so no upload step and no public URL is needed. The job is polled while
Kling renders (bounded by videoTimeoutMs, 15 minutes by default, because Kling takes minutes), then the clip lands in
the gallery next to its source image.
Montage collects clips and stills into a timeline, in the order you arrange them (Up / Down / Remove), then:
The host normalizes every segment to the same size, 30 fps, codec and pixel format in a scratch directory, then joins
them with ffmpeg's concat demuxer without re-encoding, and stores the result in the gallery like any other video.
This is a cut, not an editor: no crossfades, music or titles yet. ffmpeg is required and is looked for on PATH unless
ffmpegPath names a binary; when it is missing, the tab says so instead of offering the action.
The plugin updates itself, so nothing has to be removed and reinstalled:
Plugins page → the Image Studio card (or the row's Configure) → Updates asks GitHub for the branch revision,
shows the version installed next to the version on main, lists the recent commits, and hands the pinned commit to
the harness plugin manager in one click.
A newer branch also puts an Update → button in the page header, next to the counters. When the deployment has no plugin manager, the button is disabled and its tooltip prints the manual spec.
Afterwards reload the page. A browser-half change is live immediately; a host-half change needs an application restart, and the section says so when the running version has not moved.
If the deployment has no plugin manager, the same section prints the spec to paste into Plugins → Add plugin:
github:naletko/dsh-image-studio
The check reads two things and nothing else: raw.githubusercontent.com/<repo>/main/package.json and the GitHub commits
API for the plugin's own repository (updateRepo, by default naletko/dsh-image-studio). The result is cached for five
minutes, and updateCheck: false in the row configuration switches the whole feature off. The install spec is built by
the plugin from the configured repository plus a validated commit hash — never from a request — so a hand-crafted call
cannot ask the package manager for something else.
The media can live in one of two places. The Media source control in the page header chooses which, and the same
choice can be made from configuration with the library* keys:
Media source: Source: Shared studio ▾ Subfolder: ▾ [Apply] Folder: <the root in use>
librarySource: studio → <DSH_HOME>/image-studio
librarySource: workspace, libraryWorkspace: /path/to/project, librarySubdir: shots/2026
→ /path/to/project/dsh-media/shots/2026
| Source | Media lives in | Notes |
|---|---|---|
| Shared studio (default) | <DSH_HOME>/image-studio/ | One gallery per machine, independent of any project. |
| A project | <project>/dsh-media[/<subfolder>] | Created on first use. The subfolder takes up to 3 plain segments, e.g. shots/2026. |
Either folder holds the same three things:
gallery.json the index: one entry per image or video
config.json non-secret settings (only in the shared studio — see below)
files/<id>.<ext> the media itself, named from a generated id
<DSH_HOME> is $DSH_HOME or ~/.dsh; DSH_IMAGE_STUDIO_HOME relocates it. The settings file always stays at
<DSH_HOME>/image-studio/config.json, even when the media points into a project: the switch that points the library
there must not travel with the project, or it could never be pointed back.
A configured project that no longer exists (moved, renamed, deleted) does not break the page — the studio falls back to
the shared folder, and /state's library.root says which root actually answered. Switching source does not move
existing media: each gallery is a separate folder and the other one is simply not shown.
When the chosen project is a git repository that does not ignore the media folder yet, the page offers the
Add dsh-media to .gitignore button; it calls POST /api/image-studio/gitignore, which appends dsh-media/ once and
never writes to a directory that is not a repository.
Nothing in either folder is ever served to another machine: /api/image-studio/file answers loopback callers only, and
every path is rebuilt from a validated id, never from a prompt or a remote URL. Both folders are safe to back up or
move: gallery.json is the index beside the files it points at.
Row configuration comes from the profile patch (~/.dsh/profiles/<profile>/cordis.patch.yml) or from the bundle row in
cordis.patch.yml. A patch replaces the whole config value of a row instead of merging keys, so
an override must restate every key it needs. The profile patch also wins over what the settings card saved.
| Key | Default | Meaning |
|---|---|---|
falKeyRef | FAL_API_KEY | Credential reference holding the fal key. |
defaultModel | fal-ai/flux-2/klein/9b | Model preselected on the page. |
customModels | [] | Your own fal endpoint slugs, as added with + Own model. |
librarySource | studio | studio = the shared <DSH_HOME>/image-studio; workspace = inside a project. |
libraryWorkspace | empty | Absolute path of an existing project directory (used when the source is workspace). |
librarySubdir | empty | Relative subfolder inside the project, up to 3 plain segments (e.g. shots/2026); .., absolute paths and empty segments are ignored. |
defaultAspect | 1:1 | Aspect preselected on the page. |
defaultCount | 1 | Variants preselected on the page (1–8). |
imageTimeoutMs | 240000 | Budget for one text-to-image job. |
videoTimeoutMs | 900000 | Budget for one video job; Kling renders take minutes. |
ffmpegPath | empty | ffmpeg executable for the montage; empty means ffmpeg from PATH. |
updateRepo | naletko/dsh-image-studio | Repository the update check asks (owner/name). |
updateCheck | true | Whether the page may ask GitHub for a newer revision. |
provider | fal | Provider the host reports in /state (fal or local). The page's own Provider toggle picks one per session and starts on fal.ai. |
localUrl | http://127.0.0.1:8188 | Address of your ComfyUI. Only http(s) on 127.0.0.1, localhost or ::1 is accepted. |
localWorkflow | the bundled workflow/qwen-image.json | API-format graph to run; it must be an existing .json file when the configuration is read. |
localModel | empty | The value of {{model}} — the UNET (or GGUF) file name inside ComfyUI. |
localClip | empty | The value of {{clip}}. |
localVae | empty | The value of {{vae}}. |
localSteps | 20 | Sampling steps ({{steps}}), rounded and capped at 150. |
localTimeoutMs | 300000 | Budget for one local job. |
Everything the page does goes through the plugin's own loopback routes. They exist so the browser half never touches a provider, a key, or a file path.
| Route | Purpose |
|---|---|
GET /state | Catalogue, defaults, credential status, counters, the resolved library root and the library block, the local provider settings, ffmpeg status |
GET /workspaces | The projects the library may point at (supported: false when the composition mounts no workspace registry) |
GET /local/status | Probe the configured ComfyUI (/system_stats). A server that is off answers reachable: false with a reason — that is a normal answer, not an error |
GET /gallery?kind=&q=&favorite= | The filtered index |
GET /file?id= | Media bytes for one entry |
POST /credentials | Store or clear the fal key (write-only) |
POST /config | Non-secret defaults, the custom model list, the library source, and the local provider settings |
POST /gitignore | Append dsh-media/ to the chosen project's .gitignore, once, and only for a git repository → { ok, changed } |
POST /generate | Queue a text-to-image job for provider: "fal" (default) or provider: "local", answer 202 with the job |
POST /video, POST /montage | Queue a video or montage job, answer 202 with the job |
GET /job?id= | Job status, queue position, produced entries |
POST /import | Download a public http(s) link into the gallery |
GET /update, POST /update/apply | Ask GitH |