Initial commit: thinking-level-selector DSH plugin with docs

This commit is contained in:
Agent committed 2026-10-10 17:37:32 +00:00
commit 15cc134310
6 files changed
+236

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
node_modules/
*.bak
*.orig
.DS_Store
+60
View File
@@ -0,0 +1,60 @@
# thinking-level-selector
A compact **thinking / reasoning level** picker for the DeepSeek Harness (DSH) Web UI composer.
## Features
- Adds a selector to the composer activity bar (slot `conversation.input.activity`) listing the reasoning efforts the current model advertises (e.g. off / low / medium / high / xhigh).
- Uses the same model-selection data the model seat renders from, so it always matches the active provider/model.
- Friendlier labels (`xhigh` is shown as **Extra**); English and Chinese strings built in.
## How it works
| File | Role |
| --- | --- |
| `client.js` | Web client module (`@local/thinking-level-selector`). |
| `index.js` | Empty Host half. |
| `cordis.patch.yml` | Bundle patch inserting the plugin row. |
## Installation
Requirements: a working DeepSeek Harness (`dsh`) install with the **web** profile (or any profile that includes the Web UI).
### Option A — install from git (recommended)
```bash
dsh plugin --profile web add git+https://git.rizzy.dev/DSH-Plugins/thinking-level-selector.git
```
You can also paste `git+https://git.rizzy.dev/DSH-Plugins/thinking-level-selector.git` into **Settings → Plugins → Install** in the Web UI, or ask the agent to install it with its `plugin_manager` tool (`install_bundle`).
The plugin manager adds `@local/thinking-level-selector` to `dependencies` and to `dsh.profile.bundles` in `~/.dsh/profiles/web/package.json`.
### Option B — local checkout (for development)
```bash
git clone https://git.rizzy.dev/DSH-Plugins/thinking-level-selector.git ~/dsh-plugins/thinking-level-selector
dsh plugin --profile web add link:$HOME/dsh-plugins/thinking-level-selector
```
With a `link:` install, edits in the checkout are picked up directly (client changes reload on page refresh, Host changes after a DSH restart or HMR reload).
### Enable / verify
1. Make sure `@local/thinking-level-selector` is listed in `dsh.profile.bundles` of `~/.dsh/profiles/web/package.json` (the installer does this; add it by hand if you edited `package.json` manually).
2. Restart DSH (`dsh web`) or reload the page.
3. Check **Settings → Plugins**: the `@local/thinking-level-selector` bundle should be enabled.
### Uninstall
```bash
dsh plugin --profile web remove @local/thinking-level-selector
```
### Updating
Re-run the `add` command from Option A (or `git pull` in your Option B checkout).
## Configuration
None. Pick a model that supports reasoning efforts and the selector appears next to the composer input.
+148
View File
@@ -0,0 +1,148 @@
window.__ModuleLoader__.load({
id: '@local/thinking-level-selector',
factory(require) {
const React = require('react');
const h = React.createElement;
const NS = 'thinkingLevel';
const en = {
label: 'Thinking level',
none: 'Thinking',
default: 'Default',
loading: 'Loading…',
};
const zh = {
label: '思考强度',
none: '思考',
default: '默认',
loading: '加载中…',
};
// DSH derives names from the level key ("xhigh" -> "Xhigh"); show friendlier ones.
const LEVEL_NAMES = { xhigh: 'Extra' };
const levelName = (e) => LEVEL_NAMES[e.id] ?? e.name;
// Same shape of data the model seat renders from.
function currentReasoning(state) {
const cur = state.current;
if (!cur) return undefined;
for (const g of state.groups) {
if (g.id !== cur.provider) continue;
const m = g.models.find((x) => x.id === cur.model);
if (m) return m.reasoning;
}
return undefined;
}
function ThinkingLevel({ locked, available, directory, load, select, t }) {
const state = React.useSyncExternalStore(
(fn) => directory.subscribe(fn),
() => directory.getSnapshot(),
);
const [open, setOpen] = React.useState(false);
const rootRef = React.useRef(null);
React.useEffect(() => { if (available) load(); }, [available]);
React.useEffect(() => {
if (!open) return undefined;
const onDown = (e) => {
if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
};
const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
document.addEventListener('mousedown', onDown);
document.addEventListener('keydown', onKey);
return () => {
document.removeEventListener('mousedown', onDown);
document.removeEventListener('keydown', onKey);
};
}, [open]);
const reasoning = currentReasoning(state);
if (!available || !reasoning || reasoning.efforts.length === 0) return null;
const cur = state.current;
const effective = cur.reasoningEffort ?? reasoning.defaultEffort;
const active = reasoning.efforts.find((e) => e.id === effective);
const label = active ? levelName(active) : t('default');
const disabled = locked || state.status === 'selecting';
const pick = (effort) => {
setOpen(false);
if (effort === effective) return;
select({
provider: cur.provider,
model: cur.model,
...(effort === undefined ? {} : { reasoningEffort: effort }),
});
};
const itemStyle = (selected) => ({
display: 'flex', alignItems: 'center', gap: 8, width: '100%',
padding: '6px 10px', border: 0, borderRadius: 6, cursor: 'pointer',
textAlign: 'left', font: 'inherit', fontSize: 13, whiteSpace: 'nowrap',
background: selected ? 'var(--dsw-alias-interactive-bg-hover)' : 'transparent',
color: 'var(--dsw-alias-label-primary, inherit)',
});
return h('div', { ref: rootRef, style: { position: 'relative', display: 'inline-flex' } },
h('button', {
type: 'button',
disabled,
title: t('label'),
'aria-label': t('label'),
'aria-haspopup': 'listbox',
'aria-expanded': open,
onClick: () => setOpen((v) => !v),
style: {
display: 'inline-flex', alignItems: 'center', gap: 4,
padding: '2px 8px', border: 0, borderRadius: 6, background: 'transparent',
font: 'inherit', fontSize: 13, cursor: disabled ? 'default' : 'pointer',
color: 'var(--dsw-alias-label-secondary, inherit)',
opacity: disabled ? 0.5 : 1, whiteSpace: 'nowrap',
},
}, label, h('span', { 'aria-hidden': true, style: { fontSize: 10 } }, '▾')),
open && h('div', {
role: 'listbox',
style: {
position: 'absolute', bottom: 'calc(100% + 6px)', left: 0, zIndex: 50,
minWidth: 140, padding: 4, borderRadius: 8,
background: 'var(--dsw-specific-input-major, #fff)',
boxShadow: 'var(--dsw-elevation-soft, 0 4px 16px rgba(0,0,0,.18))',
border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.3))',
},
},
reasoning.defaultEffort === undefined && h('button', {
type: 'button', role: 'option', 'aria-selected': effective === undefined,
onClick: () => pick(undefined), style: itemStyle(effective === undefined),
}, t('default')),
reasoning.efforts.map((e) => h('button', {
key: e.id, type: 'button', role: 'option', title: e.description,
'aria-selected': e.id === effective,
onClick: () => pick(e.id), style: itemStyle(e.id === effective),
}, levelName(e), e.id === reasoning.defaultEffort ? h('span', { style: { opacity: 0.55, fontSize: 11 } }, '· ' + t('default')) : null)),
),
);
}
return {
inject: ['slots', 'locale', 'sessions', 'remote', 'remote.session', 'modelDirectories'],
apply(ctx) {
ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'thinking-level: dictionaries');
ctx.slots.inject('conversation.input.activity', () => ctx.slots.register({
name: 'conversation.input.activity',
locale: NS,
inject: (sessionId) => {
const directory = ctx.modelDirectories.directoryFor(sessionId);
const available = ctx.sessions.subagentAddress(sessionId) === undefined;
return {
available,
directory: directory.store,
load: () => { if (available) directory.load().catch(() => {}); },
select: (sel) => available ? directory.select(sel) : Promise.resolve(undefined),
};
},
}, ThinkingLevel));
},
};
},
});
+3
View File
@@ -0,0 +1,3 @@
- insert:
- id: thinking-level-selector
name: '@local/thinking-level-selector'
+2
View File
@@ -0,0 +1,2 @@
/** Host half of the thinking-level selector bundle; the Client module owns the rendering. */
export function apply() {}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@local/thinking-level-selector",
"version": "1.0.0",
"private": true,
"type": "module",
"exports": { ".": "./index.js", "./client": "./client.js" },
"dsh": {
"bundle": { "patch": "./cordis.patch.yml" },
"client": {
"platform": "web",
"immediately": true,
"inject": [
"@deepseek-ai/dsh-client-ui-conversation",
"@deepseek-ai/dsh-client-ui-model-selection",
"@deepseek-ai/dsh-client-locale"
]
}
}
}