dsh-plugin-lookatstudy 0.8.1 → 0.9.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/README.md +3 -3
- package/lib/adoc-parser-1UxcYHid.mjs +47 -0
- package/lib/client.js +38 -14
- package/lib/client.js.map +1 -1
- package/lib/code-parser-BOOk9IWV.mjs +0 -2
- package/lib/index.d.mts +12 -5
- package/lib/index.mjs +967 -565
- package/lib/notebook-parser-ChbZBIKJ.mjs +0 -2
- package/lib/org-parser-BT5yvx9h.mjs +53 -0
- package/lib/rmd-parser-EgaMaffn.mjs +17 -0
- package/lib/rst-parser-CV93w3Sq.mjs +99 -0
- package/package.json +7 -2
- package/lib/code-parser-BOOk9IWV.mjs.map +0 -1
- package/lib/index.d.mts.map +0 -1
- package/lib/index.mjs.map +0 -1
- package/lib/notebook-parser-ChbZBIKJ.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-plugin-lookatstudy
|
|
2
2
|
|
|
3
|
-
Turn any markdown document, local folder, or GitHub learning repository into a guided course inside [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) — your dsh agent becomes a full AI tutor with the interaction design of [LookatStudy](https://github.com/
|
|
3
|
+
Turn any markdown document, local folder, or GitHub learning repository into a guided course inside [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) — your dsh agent becomes a full AI tutor with the interaction design of [LookatStudy](https://github.com/Kaiji-Z/LookatStudy): per-concept knowledge tracking, mastery-driven progression, spaced repetition, mastery proposals, friction awareness, learner memory, a Cornell notebook, and an in-chat proposal card. Learning engine modules are vendored from LookatStudy (MIT).
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -65,8 +65,8 @@ LookatStudy's Electron-native experiences have no host surface in dsh: persisten
|
|
|
65
65
|
pnpm exec tsdown # build lib/ (host + client entries, peers external)
|
|
66
66
|
pnpm test # 48 node:test cases over the real source (no key needed)
|
|
67
67
|
|
|
68
|
-
# iterate against a live dsh (
|
|
69
|
-
pnpm dsh web --patch
|
|
68
|
+
# iterate against a live dsh (this repo lives beside a deepseek-harness checkout):
|
|
69
|
+
pnpm dsh web --patch ../dsh-plugin-lookatstudy/cordis.dev.yml # run from the harness checkout
|
|
70
70
|
# then open http://127.0.0.1:3080/ and switch to the 学习 tab
|
|
71
71
|
```
|
|
72
72
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/vendor/adoc-parser.ts
|
|
2
|
+
function parseAdoc(adocText) {
|
|
3
|
+
const lines = adocText.split("\n");
|
|
4
|
+
const output = [];
|
|
5
|
+
let inSourceBlock = false;
|
|
6
|
+
let pendingLang = "";
|
|
7
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8
|
+
const line = lines[i] ?? "";
|
|
9
|
+
const sourceMatch = line.match(/^\[source,\s*(\w*)\s*\]$/);
|
|
10
|
+
if (sourceMatch && !inSourceBlock) {
|
|
11
|
+
pendingLang = sourceMatch[1] ?? "";
|
|
12
|
+
if ((lines[i + 1] ?? "").trim() === "----") {
|
|
13
|
+
inSourceBlock = true;
|
|
14
|
+
i++;
|
|
15
|
+
output.push(`\`\`\`${pendingLang}`);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (inSourceBlock && line.trim() === "----") {
|
|
20
|
+
inSourceBlock = false;
|
|
21
|
+
output.push("```");
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (inSourceBlock) {
|
|
25
|
+
output.push(line);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
let processed = line;
|
|
29
|
+
processed = processed.replace(/^(=+)\s+/, (_match, eqs) => {
|
|
30
|
+
if (eqs.length > 6) return "#".repeat(6) + " ";
|
|
31
|
+
return "#".repeat(eqs.length) + " ";
|
|
32
|
+
});
|
|
33
|
+
processed = processed.replace(/image::(\S+?)\[([^\]]*)\]/g, (_m, path, alt) => {
|
|
34
|
+
return alt ? `` : ``;
|
|
35
|
+
});
|
|
36
|
+
processed = processed.replace(/image:(\S+?)\[([^\]]*)\]/g, (_m, path, alt) => {
|
|
37
|
+
return alt ? `` : ``;
|
|
38
|
+
});
|
|
39
|
+
processed = processed.replace(/link:(\S+?)\[([^\]]*)\]/g, "[$2]($1)");
|
|
40
|
+
processed = processed.replace(/(\s|^)\*([^\s*][^*]*?)\*(?=\s|[.,;:!?)])/gm, "$1**$2**");
|
|
41
|
+
processed = processed.replace(/(\s|^)_([^\s_][^_]*?)_(?=\s|[.,;:!?)])/gm, "$1*$2*");
|
|
42
|
+
output.push(processed);
|
|
43
|
+
}
|
|
44
|
+
return { markdown: output.join("\n") };
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { parseAdoc };
|
package/lib/client.js
CHANGED
|
@@ -102,6 +102,7 @@ window.__ModuleLoader__.load({
|
|
|
102
102
|
.lks-pct{font-size:12px;color:var(--dsw-alias-label-tertiary);flex:none;width:32px;text-align:right}
|
|
103
103
|
|
|
104
104
|
.lks-empty{color:var(--dsw-alias-label-tertiary);text-align:center;padding:48px 8px;font-size:14px;line-height:2}
|
|
105
|
+
.lks-actbar{display:flex;flex-direction:row;justify-content:flex-end;flex:none;padding-bottom:8px}
|
|
105
106
|
|
|
106
107
|
/* ── middle column: the tutor ── */
|
|
107
108
|
.lks-transcript{flex:1;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:10px;padding:4px 2px}
|
|
@@ -325,6 +326,18 @@ window.__ModuleLoader__.load({
|
|
|
325
326
|
});
|
|
326
327
|
this.refresh();
|
|
327
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Flip learning mode. The host syncs its tool registry before responding,
|
|
331
|
+
* so awaiting this guarantees the `study_*` tools exist for the next prompt.
|
|
332
|
+
*/
|
|
333
|
+
async activate(active) {
|
|
334
|
+
await fetchJson("/lookatstudy/api/active", {
|
|
335
|
+
method: "POST",
|
|
336
|
+
headers: { "content-type": "application/json" },
|
|
337
|
+
body: JSON.stringify({ active })
|
|
338
|
+
});
|
|
339
|
+
this.refresh();
|
|
340
|
+
}
|
|
328
341
|
/** Point the tutor's focus at one lesson. */
|
|
329
342
|
async setFocus(lessonId) {
|
|
330
343
|
await fetchJson("/lookatstudy/api/focus", {
|
|
@@ -362,6 +375,7 @@ window.__ModuleLoader__.load({
|
|
|
362
375
|
function useStudy() {
|
|
363
376
|
return {
|
|
364
377
|
data: (0, react.useSyncExternalStore)(studyStore.subscribe, studyStore.getSnapshot, studyStore.getSnapshot),
|
|
378
|
+
activate: studyStore.activate.bind(studyStore),
|
|
365
379
|
setMode: studyStore.setMode.bind(studyStore),
|
|
366
380
|
setFocus: studyStore.setFocus.bind(studyStore),
|
|
367
381
|
deleteCourse: studyStore.deleteCourse.bind(studyStore),
|
|
@@ -707,14 +721,11 @@ window.__ModuleLoader__.load({
|
|
|
707
721
|
call: node.call
|
|
708
722
|
});
|
|
709
723
|
break;
|
|
710
|
-
case "turn-error":
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
});
|
|
716
|
-
break;
|
|
717
|
-
default: break;
|
|
724
|
+
case "turn-error": rows.push({
|
|
725
|
+
key: `e${node.seq}`,
|
|
726
|
+
role: "error",
|
|
727
|
+
text: node.message
|
|
728
|
+
});
|
|
718
729
|
}
|
|
719
730
|
if (partial !== null) {
|
|
720
731
|
const text = assistantText(partial.blocks);
|
|
@@ -762,18 +773,27 @@ window.__ModuleLoader__.load({
|
|
|
762
773
|
}
|
|
763
774
|
/** Tab body: the factory-bound ctx carries workspaces/sessions for the per-lesson session jumps. */
|
|
764
775
|
function StudyTab({ useSession, inputActions, ctx }) {
|
|
765
|
-
const { data, setMode, setFocus, deleteCourse, bindLessonSession } = useStudy();
|
|
776
|
+
const { data, activate, setMode, setFocus, deleteCourse, bindLessonSession } = useStudy();
|
|
766
777
|
const snapshot = useSession((s) => s);
|
|
767
778
|
const [pane, setPane] = (0, react.useState)(storedPane);
|
|
768
779
|
const send = (text) => {
|
|
769
|
-
|
|
770
|
-
|
|
780
|
+
(async () => {
|
|
781
|
+
if (data?.active !== true) await activate(true);
|
|
782
|
+
inputActions.setDraft(text);
|
|
783
|
+
inputActions.submit();
|
|
784
|
+
})();
|
|
771
785
|
};
|
|
772
786
|
return (0, react.createElement)("div", {
|
|
773
787
|
className: "lks-root lks-study",
|
|
774
788
|
"data-conversation-composer-overlay": "",
|
|
775
789
|
"data-pane": pane
|
|
776
|
-
}, (0, react.createElement)("div", { className: "lks-
|
|
790
|
+
}, (0, react.createElement)("div", { className: "lks-actbar" }, data === null ? null : (0, react.createElement)("button", {
|
|
791
|
+
className: `lks-btn ${data.active ? "ghost" : "primary"}`,
|
|
792
|
+
title: data.active ? "注销 study 工具与导师人格;学习进度保留,可随时重新开启" : "注册 study 工具并载入导师人格,之后普通对话和现在一样",
|
|
793
|
+
onClick: () => {
|
|
794
|
+
activate(!data.active);
|
|
795
|
+
}
|
|
796
|
+
}, data.active ? "⏻ 退出学习模式" : "▶ 开始学习")), (0, react.createElement)("div", { className: "lks-body" }, (0, react.createElement)("div", { className: "lks-switch" }, ...[
|
|
777
797
|
{
|
|
778
798
|
id: "rail",
|
|
779
799
|
label: "课程"
|
|
@@ -797,6 +817,7 @@ window.__ModuleLoader__.load({
|
|
|
797
817
|
}
|
|
798
818
|
}, p.label))), (0, react.createElement)(CourseRail, {
|
|
799
819
|
data,
|
|
820
|
+
activate,
|
|
800
821
|
setFocus,
|
|
801
822
|
deleteCourse,
|
|
802
823
|
bindLessonSession,
|
|
@@ -811,7 +832,7 @@ window.__ModuleLoader__.load({
|
|
|
811
832
|
}), (0, react.createElement)(BlackboardColumn, { data })));
|
|
812
833
|
}
|
|
813
834
|
/** Left column: course management (pick/delete/search/import), lesson tree, due box. */
|
|
814
|
-
function CourseRail({ data, setFocus, deleteCourse, bindLessonSession, send, ctx, currentSessionId }) {
|
|
835
|
+
function CourseRail({ data, activate, setFocus, deleteCourse, bindLessonSession, send, ctx, currentSessionId }) {
|
|
815
836
|
const [jumping, setJumping] = (0, react.useState)(null);
|
|
816
837
|
/** Open (or mint) the lesson's own session — the simplified thread system. */
|
|
817
838
|
const openLessonThread = (lesson) => {
|
|
@@ -828,6 +849,7 @@ window.__ModuleLoader__.load({
|
|
|
828
849
|
}
|
|
829
850
|
setJumping(lesson.id);
|
|
830
851
|
setFocus(lesson.id).then(() => (async () => {
|
|
852
|
+
if (data?.active !== true) await activate(true);
|
|
831
853
|
const area = await fetch("/lookatstudy/api/study-workspace");
|
|
832
854
|
if (!area.ok) throw new Error(`study area unavailable (HTTP ${area.status})`);
|
|
833
855
|
const { path } = await area.json();
|
|
@@ -1129,6 +1151,7 @@ window.__ModuleLoader__.load({
|
|
|
1129
1151
|
}
|
|
1130
1152
|
/** Button body in its own component so hook order stays constant. */
|
|
1131
1153
|
function Inner({ ctx }) {
|
|
1154
|
+
const { data, activate } = useStudy();
|
|
1132
1155
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
1133
1156
|
const [error, setError] = (0, react.useState)(null);
|
|
1134
1157
|
const start = () => {
|
|
@@ -1137,6 +1160,7 @@ window.__ModuleLoader__.load({
|
|
|
1137
1160
|
setError(null);
|
|
1138
1161
|
(async () => {
|
|
1139
1162
|
try {
|
|
1163
|
+
if (data?.active !== true) await activate(true);
|
|
1140
1164
|
const area = await fetch("/lookatstudy/api/study-workspace");
|
|
1141
1165
|
if (!area.ok) throw new Error(`study area unavailable (HTTP ${area.status})`);
|
|
1142
1166
|
const { path } = await area.json();
|
|
@@ -1169,7 +1193,7 @@ window.__ModuleLoader__.load({
|
|
|
1169
1193
|
title: "一键准备学习:建立学习工作区、开启会话并让导师就位",
|
|
1170
1194
|
disabled: busy,
|
|
1171
1195
|
onClick: start
|
|
1172
|
-
}, busy ? (0, react.createElement)(IconLoadingOutline16, { className: "lks-spin" }) : (0, react.createElement)(IconThinkOutline16, null), busy ? "正在准备学习区…" : "开始学习"), error !== null ? (0, react.createElement)("span", {
|
|
1196
|
+
}, busy ? (0, react.createElement)(IconLoadingOutline16, { className: "lks-spin" }) : (0, react.createElement)(IconThinkOutline16, null), busy ? "正在准备学习区…" : data?.active === true ? "进入学习" : "开始学习"), error !== null ? (0, react.createElement)("span", {
|
|
1173
1197
|
className: "lks-propcard-err",
|
|
1174
1198
|
style: { marginTop: 0 }
|
|
1175
1199
|
}, error) : null);
|