dsh-plugin-lookatstudy 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +144 -15
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +274 -39
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -156,6 +156,7 @@ window.__ModuleLoader__.load({
|
|
|
156
156
|
.lks-ui .lks14-viewtab:hover{color:var(--ink)}
|
|
157
157
|
.lks-ui .lks14-viewtab.on{background:rgb(var(--brand-rgb)/0.16);color:var(--brand)}
|
|
158
158
|
.lks-ui .lks14-prose{color:var(--ink);font-size:.9375rem;line-height:1.65}
|
|
159
|
+
.lks-ui .lks14-prose img{max-width:100%;height:auto}
|
|
159
160
|
.lks-ui .lks14-prose h1,.lks-ui .lks-note-text h1{font-size:1.25rem;font-weight:800;color:var(--ink-strong)}
|
|
160
161
|
.lks-ui .lks14-prose h2,.lks-ui .lks-note-text h2{font-size:1.1rem;font-weight:700;color:var(--ink-strong);border-bottom:1px solid var(--border);padding-bottom:.3em}
|
|
161
162
|
.lks-ui .lks14-prose h3,.lks-ui .lks-note-text h3{font-size:1rem;font-weight:700;color:var(--ink)}
|
|
@@ -434,9 +435,27 @@ window.__ModuleLoader__.load({
|
|
|
434
435
|
|
|
435
436
|
/* ══════════ P11b (C-track closure): chat parts, notes closure, tooltip, confirm ══════════ */
|
|
436
437
|
/* C14: the collapsible reasoning block */
|
|
438
|
+
/* 0.24.0: the host-style thinking disclosure (ReasoningRow port) — running
|
|
439
|
+
rows stream the latest line follow-end with the sweep, settled rows lead
|
|
440
|
+
with the first line, the expanded body reads the tertiary tier */
|
|
437
441
|
.lks-ui .lks14-reasoning{border:1px solid var(--border-faint);border-radius:10px;padding:4px 10px;font-size:12px;color:var(--ink-faint)}
|
|
438
442
|
.lks-ui .lks14-reasoning summary{cursor:pointer;font-weight:600;color:var(--ink-muted);user-select:none}
|
|
439
443
|
.lks-ui .lks14-reasoning[open] summary{margin-bottom:4px}
|
|
444
|
+
.lks-ui .lks14-reasoning-row{display:flex;align-items:center;gap:6px;min-width:0;position:relative;overflow:hidden}
|
|
445
|
+
.lks-ui .lks14-reasoning-title{flex:none;font-weight:400;color:var(--ink-muted)}
|
|
446
|
+
.lks-ui .lks14-reasoning-sep{flex:none;width:2px;height:2px;margin:0 4px;border-radius:1px;background:var(--ink-faint)}
|
|
447
|
+
.lks-ui .lks14-reasoning-summary{min-width:0;overflow:hidden;flex:1 1 auto;color:var(--ink-faint);white-space:nowrap}
|
|
448
|
+
.lks-ui .lks14-reasoning-sumtext{display:block;overflow:hidden;text-overflow:ellipsis}
|
|
449
|
+
.lks-ui .lks14-reasoning-summary[data-follow-end]{display:flex;justify-content:flex-end}
|
|
450
|
+
.lks-ui .lks14-reasoning-summary[data-follow-end] .lks14-reasoning-sumtext{flex:0 0 auto;width:max-content;min-width:100%;overflow:visible;text-align:start;text-overflow:clip}
|
|
451
|
+
.lks-ui .lks14-reasoning[data-state='running'] .lks14-reasoning-row::after{content:'';position:absolute;inset-block:0;left:0;width:180px;background:linear-gradient(90deg,transparent 0%,rgb(var(--surface-2-rgb)/0.9) 55%,transparent 100%);animation:lks-reasoning-sweep 2.6s ease-out infinite;pointer-events:none}
|
|
452
|
+
@keyframes lks-reasoning-sweep{0%{left:-180px}90%,100%{left:100%}}
|
|
453
|
+
@media (prefers-reduced-motion: reduce){.lks-ui .lks14-reasoning[data-state='running'] .lks14-reasoning-row::after{animation:none}}
|
|
454
|
+
.lks-ui .lks14-reasoning.open .lks14-reasoning-body,.lks-ui .lks14-reasoning[open] .lks14-reasoning-body{padding:4px 0 4px 20px;color:var(--ink-faint);white-space:pre-wrap;word-break:break-word}
|
|
455
|
+
/* 0.24.0: the teach-tab 原文/对照/译文 segmented control */
|
|
456
|
+
.lks-ui .lks-langview{display:inline-flex;gap:2px;padding:2px;border:1px solid var(--border-faint);border-radius:8px;background:rgb(var(--surface-2-rgb)/0.6)}
|
|
457
|
+
.lks-ui .lks-langview button{border:0;background:transparent;color:var(--ink-muted);font-size:12px;line-height:1;padding:4px 8px;border-radius:6px;cursor:pointer}
|
|
458
|
+
.lks-ui .lks-langview button.on{background:rgb(var(--brand-rgb)/0.16);color:var(--brand)}
|
|
440
459
|
/* C14: the tool-call chip states */
|
|
441
460
|
.lks-ui .lks14-toolchip{display:inline-flex;align-items:center;gap:6px;align-self:flex-start;font-size:12px;font-weight:600;
|
|
442
461
|
padding:3px 10px;border-radius:999px;background:rgb(var(--ink-rgb)/0.05);color:var(--ink-muted)}
|
|
@@ -1802,6 +1821,16 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1802
1821
|
});
|
|
1803
1822
|
this.refresh();
|
|
1804
1823
|
}
|
|
1824
|
+
/** 0.24.0: push the host interface language (the panel reads <html lang>,
|
|
1825
|
+
* which the host keeps in sync with the active locale) — the apply-side
|
|
1826
|
+
* translation matching consumes it. */
|
|
1827
|
+
async setInterfaceLang(lang) {
|
|
1828
|
+
await fetchJson("/lookatstudy/api/prefs", {
|
|
1829
|
+
method: "POST",
|
|
1830
|
+
headers: { "content-type": "application/json" },
|
|
1831
|
+
body: JSON.stringify({ interfaceLang: lang })
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1805
1834
|
/** E3: land one attachment verbatim in the study workspace; returns the workspace-relative path. */
|
|
1806
1835
|
async uploadAttachment(name, dataBase64) {
|
|
1807
1836
|
const body = await fetchJson("/lookatstudy/api/attachment", {
|
|
@@ -2084,6 +2113,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
2084
2113
|
activate: studyStore.activate.bind(studyStore),
|
|
2085
2114
|
setMode: studyStore.setMode.bind(studyStore),
|
|
2086
2115
|
setHistoryBudget: studyStore.setHistoryBudget.bind(studyStore),
|
|
2116
|
+
setInterfaceLang: studyStore.setInterfaceLang.bind(studyStore),
|
|
2087
2117
|
uploadAttachment: studyStore.uploadAttachment.bind(studyStore),
|
|
2088
2118
|
setFocus: studyStore.setFocus.bind(studyStore),
|
|
2089
2119
|
searchLessons: studyStore.searchLessons.bind(studyStore),
|
|
@@ -2328,6 +2358,10 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
2328
2358
|
"rail.due.start": "开始复习",
|
|
2329
2359
|
"rail.due.none": "暂时没有到期的复习,保持节奏!",
|
|
2330
2360
|
"chat.reasoning": "思考过程({n} 字)",
|
|
2361
|
+
"chat.think": "思考",
|
|
2362
|
+
"langview.original": "原文",
|
|
2363
|
+
"langview.bilingual": "对照",
|
|
2364
|
+
"langview.translation": "译文",
|
|
2331
2365
|
"msg.speak": "朗读",
|
|
2332
2366
|
"note.pin": "置顶笔记",
|
|
2333
2367
|
"note.unpin": "取消置顶",
|
|
@@ -2695,6 +2729,10 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
2695
2729
|
"rail.due.start": "Start reviewing",
|
|
2696
2730
|
"rail.due.none": "Nothing due right now — keep the streak!",
|
|
2697
2731
|
"chat.reasoning": "Thinking ({n} chars)",
|
|
2732
|
+
"chat.think": "Think",
|
|
2733
|
+
"langview.original": "Original",
|
|
2734
|
+
"langview.bilingual": "Both",
|
|
2735
|
+
"langview.translation": "Translation",
|
|
2698
2736
|
"msg.speak": "Speak",
|
|
2699
2737
|
"note.pin": "Pin note",
|
|
2700
2738
|
"note.unpin": "Unpin",
|
|
@@ -3693,6 +3731,22 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3693
3731
|
return (0, react.createElement)("div", { className: "lks-propcard-err" }, error);
|
|
3694
3732
|
}
|
|
3695
3733
|
/**
|
|
3734
|
+
* The thinking disclosure's collapsed one-line summary (0.24.0, the host
|
|
3735
|
+
* ReasoningRow contract verbatim): while RUNNING the LAST line rides (the
|
|
3736
|
+
* summary visibly follows the stream's tail), settled the FIRST line leads;
|
|
3737
|
+
* double-asterisk markers are omitted either way. Pure.
|
|
3738
|
+
*/
|
|
3739
|
+
function reasoningSummaryLine(text, running) {
|
|
3740
|
+
return (running ? (() => {
|
|
3741
|
+
const v = text.trimEnd();
|
|
3742
|
+
const nl = v.lastIndexOf("\n");
|
|
3743
|
+
return nl === -1 ? v : v.slice(nl + 1);
|
|
3744
|
+
})() : (() => {
|
|
3745
|
+
const nl = text.indexOf("\n");
|
|
3746
|
+
return nl === -1 ? text : text.slice(0, nl);
|
|
3747
|
+
})()).replaceAll("**", "");
|
|
3748
|
+
}
|
|
3749
|
+
/**
|
|
3696
3750
|
* Tooltip text for one course-tree status glyph. Pure (the translator is an
|
|
3697
3751
|
* optional parameter defaulting to the active one, evaluated per call).
|
|
3698
3752
|
*/
|
|
@@ -4755,6 +4809,27 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4755
4809
|
*/
|
|
4756
4810
|
const TURN_STALL_MS = 24e4;
|
|
4757
4811
|
/**
|
|
4812
|
+
* 0.23.1 issue 3: how long an import funnel may sit with NO live turn before
|
|
4813
|
+
* the panel reads the import as failed. The old failure exit armed on the
|
|
4814
|
+
* turnActive prop having rendered true once — but a turn that dies inside one
|
|
4815
|
+
* event-window notification batch (turn/start + turn/end between two reads,
|
|
4816
|
+
* the keyless-provider fast fail; journal-verified: attempt → step/end →
|
|
4817
|
+
* turn/end within seconds) never flips the prop, so the exit never armed and
|
|
4818
|
+
* the funnel spun forever. This deadline needs no observation: past grace
|
|
4819
|
+
* with the turn not active = failed. The grace only covers prompt queue
|
|
4820
|
+
* latency (turn/start journals within seconds of the queue-time resolve); a
|
|
4821
|
+
* live turn holds the funnel open for its whole life however long the import
|
|
4822
|
+
* tools run.
|
|
4823
|
+
*/
|
|
4824
|
+
const IMPORT_FAIL_GRACE_MS = 3e4;
|
|
4825
|
+
/**
|
|
4826
|
+
* The funnel's failure decision (pure — the wall clock lives at the poll call
|
|
4827
|
+
* site, mirroring {@link turnStalled}).
|
|
4828
|
+
*/
|
|
4829
|
+
function importJobDead(job, turnActive, now, grace = IMPORT_FAIL_GRACE_MS) {
|
|
4830
|
+
return job !== null && !turnActive && now - job.startedAt > grace;
|
|
4831
|
+
}
|
|
4832
|
+
/**
|
|
4758
4833
|
* The stall decision (pure — the wall clock lives at the poll call site):
|
|
4759
4834
|
* an ACTIVE fold stays active while the window's top seq keeps advancing;
|
|
4760
4835
|
* once it has been frozen for longer than stallMs the panel treats the turn
|
|
@@ -4988,15 +5063,18 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4988
5063
|
continue;
|
|
4989
5064
|
}
|
|
4990
5065
|
const chunk = event.data?.chunk;
|
|
4991
|
-
if (chunk === void 0 || chunk.type !== "text-delta") continue;
|
|
5066
|
+
if (chunk === void 0 || chunk.type !== "text-delta" && chunk.type !== "reasoning-delta") continue;
|
|
4992
5067
|
const attemptId = event.data?.attemptId ?? String(event.seq);
|
|
5068
|
+
const delta = chunk.text ?? "";
|
|
4993
5069
|
const stream = streams.get(attemptId);
|
|
4994
5070
|
if (stream === void 0) streams.set(attemptId, {
|
|
4995
|
-
text: chunk.text
|
|
5071
|
+
text: chunk.type === "text-delta" ? delta : "",
|
|
5072
|
+
reasoning: chunk.type === "reasoning-delta" ? delta : "",
|
|
4996
5073
|
anchor: rows.length,
|
|
4997
5074
|
settled: false
|
|
4998
5075
|
});
|
|
4999
|
-
else
|
|
5076
|
+
else if (chunk.type === "text-delta") stream.text += delta;
|
|
5077
|
+
else stream.reasoning += delta;
|
|
5000
5078
|
latest = attemptId;
|
|
5001
5079
|
}
|
|
5002
5080
|
let askOpen = false;
|
|
@@ -5019,6 +5097,13 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
5019
5097
|
role: "streaming",
|
|
5020
5098
|
text
|
|
5021
5099
|
});
|
|
5100
|
+
const reasoning = stream.reasoning.trim();
|
|
5101
|
+
if (reasoning !== "") rows.splice(stream.anchor, 0, {
|
|
5102
|
+
key: "streaming-reasoning",
|
|
5103
|
+
role: "reasoning",
|
|
5104
|
+
text: reasoning,
|
|
5105
|
+
running: true
|
|
5106
|
+
});
|
|
5022
5107
|
}
|
|
5023
5108
|
return rows;
|
|
5024
5109
|
}
|
|
@@ -7541,10 +7626,21 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
7541
7626
|
key: row.key,
|
|
7542
7627
|
className: "lks14-msg lks14-msg-user"
|
|
7543
7628
|
}, row.text);
|
|
7544
|
-
if (row.role === "reasoning")
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7629
|
+
if (row.role === "reasoning") {
|
|
7630
|
+
const running = row.running === true;
|
|
7631
|
+
return (0, react.createElement)("details", {
|
|
7632
|
+
key: row.key,
|
|
7633
|
+
className: "lks14-reasoning",
|
|
7634
|
+
"data-state": running ? "running" : "ok",
|
|
7635
|
+
"data-testid": "reasoning-row"
|
|
7636
|
+
}, (0, react.createElement)("summary", { className: "lks14-reasoning-row" }, (0, react.createElement)(IconThinkOutline16, { size: 14 }), (0, react.createElement)("span", { className: "lks14-reasoning-title" }, tr("chat.think")), (0, react.createElement)("span", {
|
|
7637
|
+
className: "lks14-reasoning-sep",
|
|
7638
|
+
"aria-hidden": true
|
|
7639
|
+
}), (0, react.createElement)("span", {
|
|
7640
|
+
className: "lks14-reasoning-summary",
|
|
7641
|
+
"data-follow-end": running || void 0
|
|
7642
|
+
}, (0, react.createElement)("span", { className: "lks14-reasoning-sumtext" }, reasoningSummaryLine(row.text, running)))), (0, react.createElement)("div", { className: "lks14-reasoning-body" }, row.text));
|
|
7643
|
+
}
|
|
7548
7644
|
if (row.role === "tool") return (0, react.createElement)("div", {
|
|
7549
7645
|
key: row.key,
|
|
7550
7646
|
className: "lks14-toolchip " + String(row.toolState ?? "loading")
|
|
@@ -7620,7 +7716,15 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
7620
7716
|
};
|
|
7621
7717
|
}
|
|
7622
7718
|
function StudyPanelBody({ ctx }) {
|
|
7623
|
-
const { data, activate, setMode, setFocus, searchLessons, deleteCourse, deleteNote, bindLessonSession, lessonThreadOp, setCourseScope, uploadAttachment } = useStudy();
|
|
7719
|
+
const { data, activate, setMode, setFocus, searchLessons, deleteCourse, deleteNote, bindLessonSession, lessonThreadOp, setCourseScope, uploadAttachment, setInterfaceLang } = useStudy();
|
|
7720
|
+
(0, react.useEffect)(() => {
|
|
7721
|
+
const read = () => {
|
|
7722
|
+
const lang = document.documentElement.lang;
|
|
7723
|
+
if (/^[a-z]{2,3}(-[A-Za-z0-9]+)*$/.test(lang)) setInterfaceLang(lang).catch(() => {});
|
|
7724
|
+
};
|
|
7725
|
+
read();
|
|
7726
|
+
return ctx.locale?.subscribe(read);
|
|
7727
|
+
}, []);
|
|
7624
7728
|
(0, react.useEffect)(() => {
|
|
7625
7729
|
wireCodeBlockCopy();
|
|
7626
7730
|
}, []);
|
|
@@ -8583,11 +8687,8 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8583
8687
|
};
|
|
8584
8688
|
const [importJob, setImportJob] = (0, react.useState)(null);
|
|
8585
8689
|
const [importResult, setImportResult] = (0, react.useState)(null);
|
|
8586
|
-
const sawTurnRef = (0, react.useRef)(false);
|
|
8587
|
-
if (importJob !== null && turnActive) sawTurnRef.current = true;
|
|
8588
8690
|
const startImport = (label, prompt) => {
|
|
8589
8691
|
setImportResult(null);
|
|
8590
|
-
sawTurnRef.current = false;
|
|
8591
8692
|
setImportJob({
|
|
8592
8693
|
label,
|
|
8593
8694
|
startedAt: Date.now(),
|
|
@@ -8608,7 +8709,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8608
8709
|
setPanel("map");
|
|
8609
8710
|
return;
|
|
8610
8711
|
}
|
|
8611
|
-
if (
|
|
8712
|
+
if (importJobDead(importJob, turnActive, Date.now())) {
|
|
8612
8713
|
setImportJob(null);
|
|
8613
8714
|
setImportResult({
|
|
8614
8715
|
ok: false,
|
|
@@ -9845,6 +9946,17 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
9845
9946
|
const [read, setRead] = (0, react.useState)(null);
|
|
9846
9947
|
const readCtl = (0, react.useRef)(null);
|
|
9847
9948
|
const [readError, setReadError] = (0, react.useState)(null);
|
|
9949
|
+
const [langView, setLangView] = (0, react.useState)("bilingual");
|
|
9950
|
+
(0, react.useEffect)(() => {
|
|
9951
|
+
if (lesson === null) return;
|
|
9952
|
+
let next = "bilingual";
|
|
9953
|
+
try {
|
|
9954
|
+
const stored = localStorage.getItem(`lks-langview:${lesson.lessonId}`);
|
|
9955
|
+
if (stored === "original" || stored === "bilingual" || stored === "translation") next = stored;
|
|
9956
|
+
} catch {}
|
|
9957
|
+
setLangView(next);
|
|
9958
|
+
}, [lesson?.lessonId]);
|
|
9959
|
+
const teachHtml = lesson === null ? "" : langView === "translation" && lesson.translationHtml !== void 0 ? lesson.translationHtml : langView === "bilingual" && lesson.bilingualHtml !== void 0 ? lesson.bilingualHtml : lesson.html;
|
|
9848
9960
|
const { tts, addUserNote, recordReview, editNote, pinNote } = useStudy();
|
|
9849
9961
|
const proseRef = (0, react.useRef)(null);
|
|
9850
9962
|
const diagRef = (0, react.useRef)(null);
|
|
@@ -10046,7 +10158,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
10046
10158
|
if (proseRef.current !== null) applyHighlights(proseRef.current, recordQuotes);
|
|
10047
10159
|
setEnhanceTick(Date.now());
|
|
10048
10160
|
});
|
|
10049
|
-
}, [tab,
|
|
10161
|
+
}, [tab, teachHtml]);
|
|
10050
10162
|
(0, react.useEffect)(() => {
|
|
10051
10163
|
if (pendingLocate === null || tab !== "teach" || proseRef.current === null) return;
|
|
10052
10164
|
locateNoteQuote(pendingLocate, proseRef.current);
|
|
@@ -10109,7 +10221,24 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
10109
10221
|
setRateBusy(false);
|
|
10110
10222
|
});
|
|
10111
10223
|
}
|
|
10112
|
-
}, tr(key))))) : null, (0, react.createElement)("div", { className: "lks-readbar" }, (0, react.createElement)("
|
|
10224
|
+
}, tr(key))))) : null, (0, react.createElement)("div", { className: "lks-readbar" }, lesson.bilingualHtml !== void 0 ? (0, react.createElement)("div", {
|
|
10225
|
+
className: "lks-langview",
|
|
10226
|
+
"data-testid": "langview"
|
|
10227
|
+
}, [
|
|
10228
|
+
"original",
|
|
10229
|
+
"bilingual",
|
|
10230
|
+
"translation"
|
|
10231
|
+
].map((v) => (0, react.createElement)("button", {
|
|
10232
|
+
key: v,
|
|
10233
|
+
className: langView === v ? "on" : "",
|
|
10234
|
+
"data-testid": `langview-${v}`,
|
|
10235
|
+
onClick: () => {
|
|
10236
|
+
setLangView(v);
|
|
10237
|
+
try {
|
|
10238
|
+
localStorage.setItem(`lks-langview:${lesson.lessonId}`, v);
|
|
10239
|
+
} catch {}
|
|
10240
|
+
}
|
|
10241
|
+
}, tr(`langview.${v}`)))) : null, (0, react.createElement)("button", {
|
|
10113
10242
|
className: "lks-btn ghost",
|
|
10114
10243
|
style: {
|
|
10115
10244
|
padding: "3px 8px",
|
|
@@ -10143,7 +10272,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
10143
10272
|
}, (0, react.createElement)("div", {
|
|
10144
10273
|
className: "lks14-prose",
|
|
10145
10274
|
ref: proseRef,
|
|
10146
|
-
dangerouslySetInnerHTML: { __html:
|
|
10275
|
+
dangerouslySetInnerHTML: { __html: teachHtml }
|
|
10147
10276
|
})), quoteBtn !== null ? (0, react.createElement)("div", {
|
|
10148
10277
|
className: "lks-quote-btn",
|
|
10149
10278
|
"data-lks-quote-btn": "",
|