Skip to content

feat(reader): page presets and 发给主会话 for the page question (#456, 2/4) - #482

Merged
EricWang1358 merged 3 commits into
mainfrom
codex/reader-page-chips-handoff
Oct 25, 2026
Merged

EricWang1358 merged 3 commits into
mainfrom
codex/reader-page-chips-handoff

Conversation

@EricWang1358

@EricWang1358 EricWang1358 commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

PR 2 of 4 for #456, on top of #479 (merged). It covers asking about a page in 原始 PDF: built-in presets, and 发给主会话 for what the built-in answer cannot do (figures, scanned pages).

What

Presets for a page

Under the question box of the page panel (PageAsk.jsx) there are four chips: 讲解这一页 / 翻译这一页 / 按知识点拆分这一页 / 提取这一页的公式与代码.

  • One click fills the question and asks at once, the same as the chips of a selected passage.
  • Hovering a chip shows the exact question it sends, through the existing Tooltip group (no title=).
  • 翻译这一页 also says on hover what it is: a one-off answer about the page's saved text, not kept. It is not the reader's own translation, which is 「阅读」 toolbar 「翻译」: whole document, kept per revision, with glossary, estimate and stop.
  • The chip component is shared. QuickIntents moved to ui/document-preview/QuickIntents.jsx and takes a list: PASSAGE_INTENTS (unchanged, still has 出题考我) and PAGE_INTENTS.
  • The presets are built in only. Deliberately deferred: the issue's custom preset prompts / template management (learner-editable presets, storage). That is left for the owner to decide as a follow-up.

Prompt modes

  • materials.selection.ask takes an optional intent: explain, translate, points or extract. It is listed in lib/ask-prompt.js ASK_INTENTS and in the contract.
  • The intent only picks one fixed instruction sentence:
    • explain uses the explanation skeleton, as 没听懂 does;
    • translate translates into the request language;
    • points splits the page into knowledge points with short quotes;
    • extract copies formulas (LaTeX) and code verbatim, or says plainly there are none.
  • An explicit translate / points / extract intent wins over the explanation regex. Without an intent, the words of the question still decide.
  • The intent never travels in the untrusted model input. An unknown intent is refused before any model call.
  • Every prompt without an intent is byte-identical to before. A sha256 test pins 8 earlier combinations.
  • The UI request is built by ui/document-preview/ask-request.js (askRequest). The intent is kept on the thread node, and 重试 re-sends it (retryOf).

发给主会话 for a page

  • One new registry id, reader.page, built in ui/agent-prompts/reader.js. The message carries:
    • the material's name (name span), the page number, and the page's saved text in a data fence;
    • the text is cut at PAGE_EVIDENCE_MAX, the same cap as the built-in answer, and the message says when it was cut;
    • the question typed on this page, in a request fence;
    • a last line for the screenshot (new message tail image): 「这一页的截图(主会话能看图片时,粘贴在这里):」.
  • The wording is conditional everywhere: a screenshot helps only if the learner's chat takes pictures. Nothing promises the chat reads it.
  • The question stays on its page. The panel holds a draft per page, so a question typed on page 2 never follows to page 4, and it is back when you return to page 2. A page without saved text has no box, so it sends no typed words.
  • Clipboard note. When there is no main chat, or its box already holds the learner's words, the shared delivery puts the message on the clipboard. That would replace a screenshot the learner copied before. So the hover text and the empty-page sentence say: press the link first, take the screenshot after.
  • The button is the shared HandOff piece. It only fills the chat input box, never sends, and never writes over text already typed there.
  • Placement follows the owner rule:
    • on a page with text: a quiet line under the answers, 「想让主会话接着讲(可附截图):发给主会话」;
    • on a page without saved text, which was a dead end: a short sentence and the hand-off as that page's one action (secondary, never primary). It carries only the material name and the page number.
  • It only shows inside the app (services context). The bare DSH document preview has no chat to take it.
  • Guard rails (serviceHandoff, chatHandOff) are unchanged.

Other changes

  • Locale keys for zh and en (en.selection.json, en.agent.json).
  • docs/pdf-workflow.md and .zh-CN.md.
  • Changelog fragments changes/unreleased/reader-page-chips-handoff.{en,zh-CN}.md.

What not

  • No image upload or multimodal path, and no change to DSH model paths. The learner pastes the screenshot into the main chat.
  • No learner-editable presets (deferred, see above).
  • No UI polish (PR 3) and no tour or guide (PR 4).

How verified

  • node scripts/test.mjs --fast at e59fc04 (API keys unset): 7751 tests, 7749 pass, 0 fail, 2 skipped.
  • eslint is clean and changelog:check passes.
  • New tests:
    • tests/ask-prompt.test.mjs: byte identity (hashes); the mode sentences; an explicit preset beats the explanation regex (no skeleton, no term markers); normalizeIntent.
    • tests/materials-ask-shape.test.mjs: the intent reaches the instructions and not the input; an unknown intent is refused; the contract enum.
    • tests/reader-panel-flow.test.mjs:
      • page chips and their hover text, including 翻译这一页's note;
      • a chip click sends question + name + intent, and the request carries scope: 'page' and intent;
      • passage chips carry no intent, and their request keys are unchanged;
      • 重试 re-sends node.intent;
      • draft keys per page; an empty page's hand-off input has no question;
      • the hand-off is quiet under the answers and secondary on an empty page, absent outside the app, and its wording never promises picture reading;
      • English has no Chinese.
    • tests/hand-off-registry.test.mjs: a reader.page fixture in every shared check (layout, both languages, hostile title / text / question stay in their fences), plus the cut note, the empty page, the conditional screenshot line and the first sentence.
    • tests/hand-off-places.test.mjs: reader.page is used twice in PageAsk.jsx.
  • Browser: scripts/qa/page-ask.mjs uses a generated PDF and the preview fake model. The preview has no main chat, so 发给主会话 copies there and the QA reads the clipboard. All 16 steps pass with 0 page, console or API errors in zh dark 1440, zh dark 420 and en light 1440. New steps:
    • 翻译这一页 asks at once; the answer is headed by the chip's name and quotes page 2's text.
    • 发给主会话 on page 2 hands over page 2, its text, the typed question and the screenshot line; the 复 receipt shows.
    • Page 4 (no text): the hand-off is that page's one action. Its message says the page has no saved text and has no request fence, and none of page 2's question. Back on page 2, that page's question is still in the box.

Known limits / Not verified

  • Not checked with a real model. The four presets were only exercised with the preview model, which answers from the page text whatever the mode.
  • Not checked whether a given main chat can read a pasted screenshot. That depends on the learner's DSH chat and model.
  • Not checked inside DeepSeek Harness itself. In the preview the message lands on the clipboard. Filling the chat box is covered by tests/hand-off-browser.test.mjs, which runs the real host adapter (ui/host/workspace.jsx) against a faked DSH conversation, not a real one, and by tests/hand-off-unit.test.mjs. This PR does not change either.
  • With the clipboard fallback, the message replaces whatever was on the clipboard. The UI says to press first and take the screenshot after.
  • StudyHub cannot attach the screenshot; the learner pastes it.
  • The preset chips take two lines in a narrow panel (PR 3 can tighten this).

Refs #456

- Four built-in presets for a page (讲解这一页 / 翻译这一页 / 按知识点拆分这一页
  / 提取这一页的公式与代码) through the shared QuickIntents (now its own file,
  used by the passage chips too, which send exactly what they sent before).
- materials.selection.ask takes an optional intent (explain, translate,
  points, extract) that chooses one fixed instruction sentence in
  lib/ask-prompt.js; every earlier prompt is byte-identical (hash test).
- New hand-off reader.page (ui/agent-prompts/reader.js): material name, page,
  the page's saved text (cut at PAGE_EVIDENCE_MAX and said), the typed
  question, and a last line to paste the screenshot (new tail 'image').
  PageAsk: a quiet link under the answers; the way forward (secondary) on a
  page without saved text. Shown only inside the app (services context).
- Preview: ui/dev.jsx accepts a QA stand-in for the chat input box
  (window.STUDY_QA_CHAT); scripts/qa/page-ask.mjs: preset chip answers from
  the page, hand-off fills the box and never over typed text, image-only page.
- Docs (pdf-workflow en/zh), changelog fragments.

Refs #456
…wording; explicit preset wins (#482 review)

- The question typed on a page is held per page and never sent from another
  page; a page without saved text sends no typed words (pageHandOffInput).
- Wording never promises the main chat reads pictures: if your chat takes
  pictures, paste the screenshot; press the link first, then take the
  screenshot (the clipboard fallback would replace a copied screenshot).
- askSystemPrompt: an explicit translate/points/extract preset wins over the
  explanation regex; every no-intent prompt stays byte-identical.
- askRequest / retryOf (ask-request.js) with tests: a chip click sends its
  intent, 重试 re-sends node.intent.
- 翻译这一页 says on hover it is a one-off answer, not the reader's kept
  translation (Read toolbar 翻译). Docs and changelog reworded; Not verified
  says exactly how the hand-off fill is tested (real host adapter, faked DSH).
- QA: page 4's message carries no question from page 2; page 2 keeps its draft.

Refs #456
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant