본문으로 건너뛰기
H

dsh-text-explain

heartattack2332/dsh-text-explain

Select a passage in a DSH conversation and read a plain-language explanation in a floating panel, which carries the surrounding message as the context. Three explanation depths (plain / general / pro) that re-explain on the spot when switched; optional web search and local file lookup (the model decides by default, and a button can force one lookup that reports only what is new); follow-up questions; and a button that hands the whole exchange to a new session in standby mode - the background is queued without waking the model or spending tokens. Settings live on the host in settings.json, and nothing is written until you press save. The repository also ships an OpenAI-compatible endpoint and a browser extension (source).

설치

dsh plugin --profile web add github:heartattack2332/dsh-text-explain

README

dsh-text-explain

简体中文 | 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.

Demo: select a confusing passage, read the plain-words explanation, ask a follow-up, then hand the thread to a new session

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

dsh plugin --profile web add github:HEArtattaCK2332/dsh-text-explain

Then restart the Web profile so the new bundle layer is loaded.

From a local checkout instead
dsh plugin --profile web add file:<path-to-dsh-text-explain>

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/ with its own README.

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:

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.

관련 플러그인