Bundle
dsh-text-explain
Select text in a DeepSeek Harness conversation and explain it with AI in a small floating window.
- Source
- HEArtattaCK2332
- License
- MIT
- Updated
- Updated 8 days ago
Readme
# dsh-text-explain [简体中文](README.zh.md) | English **Made for people who don't speak AI.** Select a passage in a DeepSeek Harness conversation — a term, an error message, a wall of jargon — and get it explained in plain words, right where you are reading.  *40 seconds, unedited: a Godot build file explained as 「`.exe` is the cow, `.pck` is the suitcase — the cow and the suitcase have to travel together」, followed by two follow-up questions.* ## Why this exists AI output is full of words you are expected to already know. The usual advice is "just ask the model" — but you have to know *what* to ask, and the answer often comes back in the same jargon you were stuck on. This plugin removes that step: **select the confusing part, click once, read the plain-words version.** No prompt engineering, no second tab, no API key. ## Features - **Context-aware.** When you select text, it also grabs the *whole message* the passage came from and hands both to the model. "It will transfer the funds at the end" is ambiguous — until the model can see that "it" means the finance department. - **Depth knob (讲给谁听).** Three depths — **Plain / General / Pro**, defaulting to Plain. Switch one and it **re-explains the current question on the spot**: the same sentence goes from kitchen-table analogy to engineering vocabulary. A switch in the popup lasts that popup only; set a default in Settings and press Save. - **Standby handoff (✦ New session).** The button packs the selection, its source message and everything already asked into a handoff document, drops it into a fresh session and jumps you there. **The new session answers with a single line — "背景已了解,等你开口" — and waits**: no summary, no questions, no expansion. You already had your explanation; the new session is for what comes next. - **Markdown rendering.** Answers render with headings, lists, quotes, code blocks, **tables** and links — via a hand-written, dependency-free renderer built from React elements (so it is XSS-safe by construction). - **Web search and local files (optional).** Two toggles let the model call `web_search` / `web_fetch` for current information, and `search_files` / `read_file` to look things up in your local docs. ## Install ```sh dsh plugin --profile web add github:HEArtattaCK2332/dsh-text-explain ``` Then restart the Web profile so the new bundle layer is loaded. <details> <summary>From a local checkout instead</summary> ```sh dsh plugin --profile web add file:<path-to-dsh-text-explain> ``` </details> ## Usage 1. **Select text** in any conversation message. The popup automatically carries that message along as context. 2. Click **AI 解释** to explain it, or **追问** to ask a question about it. 3. Read the streamed answer. Type a follow-up and press Enter to keep the same thread; close the window with ✕. 4. Switch depth with **讲给谁听** at the bottom of the popup. Changing it re-explains the current question immediately. 5. Toggle **🌐 联网** / **📄 读文件** before asking, if you want the model to search the web or read local files. 6. Click **✦ 新建为对话** when the thread is worth pursuing: the whole exchange lands in a new session as a **handoff**, which opens automatically and waits for you to speak. 7. Open **设置 → 划词解释** for defaults: which workspace a new session lands in, the default depth, and (under 高级设置) whether the two toggles start on. ## Settings Settings live on the host, in `<DSH_HOME>/text-explain/settings.json` (`DSH_HOME` defaults to `~/.dsh`). The settings page keeps a **draft**: every click only edits that draft, **保存** writes the whole file, **刷新** throws the draft away and re-reads what is stored. **Nothing you did not save is ever written to disk** — not to the config file, not to browser storage (this plugin stores nothing in the browser at all). ## Browser extension The same backend also powers a browser extension: select any passage on any web page and read the explanation in a floating panel (three depths, switchable on the spot). Source lives in [`extension/`](extension/) with its own [README](extension/README.md). **You do not need DSH to use it.** The extension offers three backends: - **Free models** (easiest) — endpoints and model names are pre-filled; all you do is apply for a free key at the provider. Recommended: Zhipu GLM-4.7-Flash, free and reachable from mainland China without a proxy. Also Qwen, Gemini, Groq, OpenRouter, or a local Ollama / LM Studio. - **Local DeepSeek Harness** — talks to this plugin, so web search and hand-off-to-a-new-session work as well. - **Your own API** — any OpenAI-compatible endpoint. The three depth levels work identically in all three. Load it unpacked: open `chrome://extensions/`, turn on **Developer mode**, click **Load unpacked**, and pick the `extension/` folder. ## Use it from other apps The host half also speaks the **OpenAI chat-completions protocol**, so any client that accepts a custom API base URL can use this plugin as its backend — Obsidian plugins, browser extensions, your own scripts. ``` Base URL : http://127.0.0.1:3080/api/text-explain/v1 API key : anything (or leave it empty) Model : text-explain-plain | text-explain-general | text-explain-pro ``` - `GET /models` lists the three depths. - `POST /chat/completions` takes the standard shape and supports `stream: true` (SSE). - **The model name picks the depth**: `text-explain-plain` is the default voice (for people who don't speak AI), `text-explain-pro` the most technical. - A `system` message from the caller is **ignored on purpose** — the whole point of this plugin is that the explanation style is not up to the caller. - Callers **without** an `Origin` header (`curl`, local scripts) work out of the box. Callers **with** one must be listed in `allowedOrigins` (see Design notes) — desktop apps included: **Obsidian sends `Origin: app://obsidian.md`**, so that exact line belongs in the allowlist. ## Tests Three stub-based self-check suites ship in the box. They need nothing but Node's built-in modules — no test framework, no `npm install`: ```sh node test/run-all.mjs # all three; or run any single file ``` - `test/host.test.mjs` — drives the real HTTP endpoints against a fake `llm` / `webServer`: the three depths must produce three **different** system prompts; an unknown `level` must fall back to `plain` (never an error); context must still be injected; an empty selection must be rejected. It also checks the settings endpoint creates no file until you save, sanitizes dirty input, and leaves stored settings untouched on a bad payload. Everything is written to a temp `DSH_HOME`, never your real one. - `test/settings.test.mjs` — loads `lib/client.js` against React / fetch stubs: module shape, slot registration, settings normalization, cache behaviour, failure fallbacks, and the rule that matters — **only Save issues a POST**. The `window` stub deliberately has no `localStorage`, proving browser storage is really gone. - `test/render.test.mjs` — renders both components on a React stub: the settings page (loading / ready / error, collapsed and expanded) and the popup with a full Markdown answer, exercising the hand-written renderer. ## Design notes - The selected text goes to `deepseek-official` / `deepseek-v4-flash` by default; no separate API key is stored by this plugin. - Intentionally dependency-free and hand-written: **no build step**. - **Host half** (`lib/index.js`) registers `POST /api/text-explain` and `GET/POST /api/text-explain/settings` as *exact* routes. The four depth prompts share one skeleton (「语境」/「格式」are common; only 「读者 + 怎么讲」 differ), so a single code path serves all four. - **Context** comes from the DOM: the client walks up from the selection anchor to the outermost ancestor that still fits the same message (capped at 4000 characters). If that walk fails, the explanation still works — just without context. - **Web search** uses Bing's RSS endpoint (`format=rss`) — no API key, and reachable on networks where DuckDuckGo is not. `web_fetch` strips HTML to plain text; it works for static pages, not JavaScript-rendered ones. - **`search_files`** returns only document files (README / SKILL.md / package.json / …) and skips runtime directories such as `undo`, `snapshots` and `logs`. **`read_file`** refuses files over 512 KB, truncates long text, and never writes. - **Cross-origin calls** (the door for a future browser extension): the endpoint inspects `Origin` first. No origin, or same-origin as the host, behaves exactly as before; only origins listed in the `allowedOrigins` allowlist get CORS headers, everything else gets a flat **403** — so a random web page cannot burn your quota. The allowlist defaults to an empty array, so **the default behaviour is unchanged**. ## License MIT — see [LICENSE](LICENSE).
Install
dsh plugin --profile web add github:HEArtattaCK2332/dsh-text-explain
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-text-explain from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.