dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
A two-level model picker (provider → model) and a Codex-style draggable reasoning-effort bar for DeepSeek Harness
A hundred models? Pick the route on the left, the model on the right — grouped into DeepSeek / GPT / others.
简体中文 · English
When you add a custom provider in DSH (an OpenAI-compatible gateway, a self-hosted server, a relay), "Fetch available models" returns a checkbox list. Providers routinely serve dozens or hundreds of models, and that dialog has no search box — you just scroll.
Once the models are configured there are two more of the same:
This plugin fixes all three:
Both screenshots come from a real, locally running DSH instance (produced by
node scripts/e2e-dsh.mjs, which drives the UI in headless Chrome).
① The model menu becomes a two-level cascade (providers → models)
openToModels turns that off)② The model menu: search
dsv4flash → deepseek-v4-flash) and say so③ Reasoning effort: a draggable energy bar in the same box
Reasoning ▮▮▮▯ Highrole="slider" + aria-valuenow/valuetext semanticsdirectory.select() — exactly what clicking the host's menu does④ The "fetch available models" dialog: search and batch selection
6 / 19 shown · 12 selectedEsc clears the search (without closing the dialog), ↑↓ moves between results, Enter picks a single remaining match⑤ Out of the way
drilldown: false restores the single-column list)Option 1 — the official command (recommended)
# Install from GitHub (no build step: the bundle is committed)
dsh plugin --profile web add github:Xstone1129/dsh-model-search
# …or from npm (the package is named dsh-model-cascade; the GitHub repo keeps dsh-model-search)
dsh plugin --profile web add dsh-model-cascade
Then reload the page; restart dsh web if it does not take effect.
Naming: the GitHub repo is
dsh-model-search, but that npm name was already taken by a similar community plugin, so the npm package is published asdsh-model-cascade(after its defining feature, the two-level cascade). A DSH plugin's package name must equal its client module id, so both aredsh-model-cascade.
Option 2 — manual (no restart, handy for local hacking)
cd ~/.dsh/profiles/web
pnpm add file:/path/to/this-checkout
and add the entry to that profile's cordis.patch.yml (the file is watched live):
- insert:
- id: dsh-model-cascade
name: 'dsh-model-cascade'
Reload the page — no dsh web restart needed.
⚠️ Do not use both at once: inserting the same entry twice loads the plugin twice. Remove the three manual lines before installing with option 1.
Nothing to configure:
From the browser console, via dshModelCascade (persisted in localStorage):
dshModelCascade.options // current options
dshModelCascade.set({ drilldown: false }) // no cascade: one flat list + search
dshModelCascade.set({ openToModels: false }) // keep the host's root pane when opening
dshModelCascade.set({ minItems: 3 }) // take over from 3 models on
dshModelCascade.set({ menu: false }) // dialog only, leave the model menu alone
dshModelCascade.set({ lang: 'en' }) // force English copy (default 'auto')
dshModelCascade.reset() // back to defaults
| Option | Default | Meaning |
|---|---|---|
dialog | true | Search box in the "fetch available models" dialog |
menu | true | Rework the composer model menu (search + cascade + effort bar) |
drilldown | true | Two-level cascade (routes left / models right); false = flat list |
openToModels | true | Jump straight to the model pane (press Esc for the root pane) |
minItems | 8 | Do nothing below this many models |
autoFocus | true | Focus the dialog search box on open |
lang | 'auto' | Copy language: auto / zh / en |
Storage key: localStorage["dsh-model-cascade:options"] (the pre-rename key
dsh-model-search:options is read once for migration, so existing settings survive);
the old console name dshModelSearch remains as an alias to the same switch.
| Where | Key | Effect |
|---|---|---|
| Search box | typing | Live filtering across both columns |
| Search box | Esc | Clear the search → leave "All providers" → hand Esc back to the host |
| Search box | ↑ ↓ | Focus the first / last model in the right column |
| Search box | Enter | Select the only candidate, otherwise focus the first one |
| Effort bar | ← → / ↑ ↓ | Step one level down / up and apply it |
| Either column | ↑ ↓ | Move focus within that column |
The two host components (dsh-client-ui-settings-models,
dsh-client-ui-model-selection) own private DOM with no extension slot exposed, so
this plugin enhances the DOM in place under three strict rules:
style.display (the host sets no style prop, so React never overwrites it) — never className, never controlled props;<ul> holding only <li>s that each contain a checkbox" (unique in the app), the model menu as div[role="menu"] plus section[role="group"].Clicking a model still has exactly one source of truth: every row in the right column forwards its click to the host's own (hidden) button, so selection, menu closing and error reporting all run the host's existing logic.
The effort levels are never guessed: they are per-model capabilities, so the bar
reads reasoning.efforts for the current model from the host's client service
modelDirectories and commits through its select() — the identical entry point the
host itself uses. When that service is unavailable (or the session is unknown) the bar
quietly disappears and everything else keeps working.
Why there is no flash: a MutationObserver callback is a microtask and the browser only paints after the current task, so the plugin performs "hide the host list + switch the pane + mount the two columns" synchronously inside that callback. The throttled 80 ms scan is only a fallback.
So: if a future DSH release changes those structures, the plugin degrades
silently (it stops taking over) rather than breaking the UI, and every entry point is
wrapped in try/catch that at worst leaves a console warning.
Development is verified against @deepseek-ai/dsh@0.1.1-rc.2 (the DSH Web UI). The
real-browser E2E has already caught three issues that only show up when it actually
runs: the model items are role="menuitemradio", the auto-focus guard was wrong, and
the effort bar was clipped by the menu's max-height.
This repo has zero build dependencies: lib/client.js is produced by wrapping
src/client.js in DSH's window.__ModuleLoader__.load({ id, factory }) envelope.
npm run build # src/ → lib/client.js (inlines CSS, checks the version)
npm test # 60 cases: pure logic + the real artifact in jsdom + a fake model directory
npm run check:browser # real-Chrome checks (layout/focus); does NOT need dsh web running
node scripts/e2e-dsh.mjs --screenshot out.png # real-browser E2E (needs a running dsh web)
node scripts/e2e-dsh.mjs --skip-effort # the run that leaves your settings alone
node scripts/e2e-dsh.mjs --set-effort High # maintenance: drag the effort to a named level
src/client.js browser half (the factory body: apply / inject)
src/client.css injected styles (DSH theme variables, light and dark)
scripts/build.mjs bundler (dependency-free: envelope + inlined CSS)
scripts/e2e-dsh.mjs headless-Chrome E2E: dialog search / cascade / effort drag (real mouse events)
scripts/browser-check.mjs real-Chrome self-check: stable panel size, real clicks keep the menu open
test/harness.html the page that self-check drives (inlines the host menu's layout rules)
lib/index.js host half (a no-op shell so the Loader discovers the browser half)
lib/client.js built artifact (committed — installs need no build)
test/ node:test suites (jsdom fixtures + a fake modelDirectories service)
The E2E drag does change a setting: model/effort selection in an empty session is written to
settings.yamlunderagent-default-model. The script restores the level it found; pass--skip-effortto keep it away entirely.
dsh web), desktop and mobile browsersMIT © 2026 Xstone1129