dsh-plugin-lookatstudy 0.24.0 → 0.25.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 +79 -8
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +14 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -362,8 +362,8 @@ window.__ModuleLoader__.load({
|
|
|
362
362
|
.lks-ui .lks14-railbody{position:absolute;inset:0;overflow:hidden;z-index:10}
|
|
363
363
|
.lks-ui .lks14-railtrack{display:flex;height:100%;width:200%;transition:transform .3s var(--ease-out-expo)}
|
|
364
364
|
.lks-ui .lks14-railpane{width:50%;height:100%;position:relative}
|
|
365
|
-
.lks-ui .lks14-railscroll{height:100%;overflow-y:auto;overflow-x:hidden;padding:112px 8px 16px}
|
|
366
|
-
.lks-ui .lks14-railpane-import{height:100%;overflow-y:auto;padding:64px 12px 12px}
|
|
365
|
+
.lks-ui .lks14-railscroll{height:100%;overflow-y:auto;overflow-x:hidden;padding:112px 8px 16px;box-sizing:border-box}
|
|
366
|
+
.lks-ui .lks14-railpane-import{height:100%;overflow-y:auto;padding:64px 12px 12px;box-sizing:border-box}
|
|
367
367
|
.lks-ui .lks14-raillist{display:flex;flex-direction:column;gap:8px;margin-bottom:10px}
|
|
368
368
|
.lks-ui .lks14-railcourse{text-align:left;padding:10px 12px;border-radius:12px;background:rgb(255 255 255/0.05);display:flex;flex-direction:column;gap:2px}
|
|
369
369
|
.lks-ui .lks14-railcourse:hover{background:rgb(255 255 255/0.1)}
|
|
@@ -2258,6 +2258,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
2258
2258
|
"toast.close": "关闭",
|
|
2259
2259
|
"composer.busy": "导师正在回复…",
|
|
2260
2260
|
"rail.empty.demo": "导入示例课程",
|
|
2261
|
+
"import.session.title": "课程导入",
|
|
2261
2262
|
"import.cta": "导入新课程",
|
|
2262
2263
|
"import.tab.url": "URL",
|
|
2263
2264
|
"import.tab.md": "MD",
|
|
@@ -2629,6 +2630,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
2629
2630
|
"toast.close": "Close",
|
|
2630
2631
|
"composer.busy": "The tutor is replying…",
|
|
2631
2632
|
"rail.empty.demo": "Import the demo course",
|
|
2633
|
+
"import.session.title": "Course import",
|
|
2632
2634
|
"import.cta": "Import a new course",
|
|
2633
2635
|
"import.tab.url": "URL",
|
|
2634
2636
|
"import.tab.md": "MD",
|
|
@@ -3747,6 +3749,17 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3747
3749
|
})()).replaceAll("**", "");
|
|
3748
3750
|
}
|
|
3749
3751
|
/**
|
|
3752
|
+
* The dedicated import session's host-list title (0.24.1): imports mint a
|
|
3753
|
+
* course-less one-shot session that never rides a lesson thread — a uniform
|
|
3754
|
+
* label keeps the host session list legible instead of auto-title noise.
|
|
3755
|
+
* Label slugified to one line, whole title capped. Pure.
|
|
3756
|
+
*/
|
|
3757
|
+
function importSessionTitle(label, now, t = tr) {
|
|
3758
|
+
const flat = label.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim().slice(0, 24);
|
|
3759
|
+
const date = `${String(now.getFullYear()).slice(2)}.${String(now.getMonth() + 1).padStart(2, "0")}.${String(now.getDate()).padStart(2, "0")}`;
|
|
3760
|
+
return `${flat === "" ? t("import.session.title") : flat} · ${date}`;
|
|
3761
|
+
}
|
|
3762
|
+
/**
|
|
3750
3763
|
* Tooltip text for one course-tree status glyph. Pure (the translator is an
|
|
3751
3764
|
* optional parameter defaulting to the active one, evaluated per call).
|
|
3752
3765
|
*/
|
|
@@ -7719,11 +7732,19 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
7719
7732
|
const { data, activate, setMode, setFocus, searchLessons, deleteCourse, deleteNote, bindLessonSession, lessonThreadOp, setCourseScope, uploadAttachment, setInterfaceLang } = useStudy();
|
|
7720
7733
|
(0, react.useEffect)(() => {
|
|
7721
7734
|
const read = () => {
|
|
7722
|
-
|
|
7735
|
+
let lang = document.documentElement.lang;
|
|
7736
|
+
if (!/^[a-z]{2,3}(-[A-Za-z0-9]+)*$/.test(lang)) lang = navigator.language;
|
|
7723
7737
|
if (/^[a-z]{2,3}(-[A-Za-z0-9]+)*$/.test(lang)) setInterfaceLang(lang).catch(() => {});
|
|
7724
7738
|
};
|
|
7725
7739
|
read();
|
|
7726
|
-
|
|
7740
|
+
const t1 = setTimeout(read, 600);
|
|
7741
|
+
const t2 = setTimeout(read, 2400);
|
|
7742
|
+
const off = ctx.locale?.subscribe(read);
|
|
7743
|
+
return () => {
|
|
7744
|
+
clearTimeout(t1);
|
|
7745
|
+
clearTimeout(t2);
|
|
7746
|
+
off?.();
|
|
7747
|
+
};
|
|
7727
7748
|
}, []);
|
|
7728
7749
|
(0, react.useEffect)(() => {
|
|
7729
7750
|
wireCodeBlockCopy();
|
|
@@ -7925,7 +7946,43 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
7925
7946
|
} else celebrate(ev.kind);
|
|
7926
7947
|
prevCelebrationRef.current = snapshot;
|
|
7927
7948
|
}, [data]);
|
|
7928
|
-
const
|
|
7949
|
+
const [importSessionId, setImportSessionId] = (0, react.useState)(null);
|
|
7950
|
+
const sendImport = async (prompt, label) => {
|
|
7951
|
+
if (data?.active !== true) await activate(true);
|
|
7952
|
+
let sessionId = importSessionId;
|
|
7953
|
+
if (sessionId === null || !sessionKnown(ctx, sessionId)) {
|
|
7954
|
+
const area = await fetch("/lookatstudy/api/study-workspace");
|
|
7955
|
+
if (!area.ok) throw new Error(`study area unavailable (HTTP ${String(area.status)})`);
|
|
7956
|
+
const { path } = await area.json();
|
|
7957
|
+
const workspace = await ctx.workspaces.create({ path });
|
|
7958
|
+
sessionId = await ctx.sessions.create({ workspaceId: workspace.workspaceId });
|
|
7959
|
+
setImportSessionId(sessionId);
|
|
7960
|
+
}
|
|
7961
|
+
const shell = PANEL_SHELL.current;
|
|
7962
|
+
if (shell !== null) shell.suppressHandBack(() => {
|
|
7963
|
+
ctx.sessions.open(sessionId);
|
|
7964
|
+
});
|
|
7965
|
+
else ctx.sessions.open(sessionId);
|
|
7966
|
+
const actx = ctx.sessions.scope(sessionId);
|
|
7967
|
+
const face = actx === void 0 ? void 0 : ctx.sessions.sessionOf(actx);
|
|
7968
|
+
if (face === void 0) throw new Error("import session is not addressable yet");
|
|
7969
|
+
activeFace.current = face;
|
|
7970
|
+
try {
|
|
7971
|
+
await face.rename?.(importSessionTitle(label, /* @__PURE__ */ new Date()));
|
|
7972
|
+
} catch {}
|
|
7973
|
+
const result = await face.prompt([{
|
|
7974
|
+
type: "text",
|
|
7975
|
+
text: prompt
|
|
7976
|
+
}], "queue");
|
|
7977
|
+
if (!result.ok) throw new Error(`prompt rejected: ${result.error.code}: ${result.error.message}`);
|
|
7978
|
+
};
|
|
7979
|
+
/** Forget the import session — called from every funnel exit. Unpinning
|
|
7980
|
+
* re-runs the binding effect, which restages the focused lesson's thread. */
|
|
7981
|
+
const endImportSession = (0, react.useCallback)(() => {
|
|
7982
|
+
setImportSessionId(null);
|
|
7983
|
+
activeFace.current = null;
|
|
7984
|
+
}, []);
|
|
7985
|
+
const boundId = importSessionId ?? boundSessionOf(lesson !== null ? lesson.lessonId : null) ?? threadGroupOf(lesson !== null ? lesson.lessonId : null)?.active ?? (lesson !== null ? data?.lessonSessions[lesson.lessonId] ?? null : null);
|
|
7929
7986
|
const nudged = (0, react.useRef)(false);
|
|
7930
7987
|
(0, react.useEffect)(() => {
|
|
7931
7988
|
if (nudged.current || (data?.dueCount ?? 0) === 0) return;
|
|
@@ -8167,6 +8224,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8167
8224
|
}, [send]);
|
|
8168
8225
|
const switchLessonThread = (sessionId) => {
|
|
8169
8226
|
if (lesson === null) return;
|
|
8227
|
+
if (importSessionId !== null) return;
|
|
8170
8228
|
if (localBound.current?.lessonId === lesson.lessonId && localBound.current.sessionId === sessionId) return;
|
|
8171
8229
|
localBound.current = {
|
|
8172
8230
|
lessonId: lesson.lessonId,
|
|
@@ -8181,6 +8239,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8181
8239
|
};
|
|
8182
8240
|
const startNewThread = () => {
|
|
8183
8241
|
if (lesson === null) return;
|
|
8242
|
+
if (importSessionId !== null) return;
|
|
8184
8243
|
localBound.current = null;
|
|
8185
8244
|
bindLessonSession(lesson.lessonId, null);
|
|
8186
8245
|
setRows([]);
|
|
@@ -8243,6 +8302,8 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8243
8302
|
turnActive: feedGen,
|
|
8244
8303
|
importProgress,
|
|
8245
8304
|
stop,
|
|
8305
|
+
sendImport,
|
|
8306
|
+
endImportSession,
|
|
8246
8307
|
paletteBus
|
|
8247
8308
|
}), (0, react.createElement)(PaneResizeHandle, {
|
|
8248
8309
|
side: "rail",
|
|
@@ -8654,7 +8715,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8654
8715
|
PANEL_SHELL.current = shell;
|
|
8655
8716
|
}
|
|
8656
8717
|
/** 左栏:course picker, tree, review box, import. */
|
|
8657
|
-
function CourseRail({ data, activate, setFocus, searchLessons, deleteCourse, send, onJumped, streamingLessonId, turnActive, importProgress, stop, paletteBus }) {
|
|
8718
|
+
function CourseRail({ data, activate, setFocus, searchLessons, deleteCourse, send, onJumped, streamingLessonId, turnActive, importProgress, stop, paletteBus, sendImport, endImportSession }) {
|
|
8658
8719
|
const [selectedCourse, setSelectedCourse] = (0, react.useState)("");
|
|
8659
8720
|
const [query, setQuery] = (0, react.useState)("");
|
|
8660
8721
|
const [searchRows, setSearchRows] = (0, react.useState)([]);
|
|
@@ -8694,13 +8755,21 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8694
8755
|
startedAt: Date.now(),
|
|
8695
8756
|
baselineIds: new Set((data?.courses ?? []).map((c) => c.courseId))
|
|
8696
8757
|
});
|
|
8697
|
-
|
|
8758
|
+
sendImport(prompt, label).catch((err) => {
|
|
8759
|
+
setImportJob(null);
|
|
8760
|
+
endImportSession();
|
|
8761
|
+
setImportResult({
|
|
8762
|
+
ok: false,
|
|
8763
|
+
msg: err instanceof Error ? err.message : String(err)
|
|
8764
|
+
});
|
|
8765
|
+
});
|
|
8698
8766
|
};
|
|
8699
8767
|
(0, react.useEffect)(() => {
|
|
8700
8768
|
if (importJob === null || data === null) return;
|
|
8701
8769
|
const fresh = data.courses.find((c) => !importJob.baselineIds.has(c.courseId));
|
|
8702
8770
|
if (fresh !== void 0) {
|
|
8703
8771
|
setImportJob(null);
|
|
8772
|
+
endImportSession();
|
|
8704
8773
|
setImportResult({
|
|
8705
8774
|
ok: true,
|
|
8706
8775
|
msg: tr("import.success")
|
|
@@ -8711,6 +8780,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8711
8780
|
}
|
|
8712
8781
|
if (importJobDead(importJob, turnActive, Date.now())) {
|
|
8713
8782
|
setImportJob(null);
|
|
8783
|
+
endImportSession();
|
|
8714
8784
|
setImportResult({
|
|
8715
8785
|
ok: false,
|
|
8716
8786
|
msg: tr("import.error.turn")
|
|
@@ -8908,7 +8978,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8908
8978
|
}))))) : null, data.courses.length === 0 ? (0, react.createElement)("div", { className: "lks14-empty" }, tr("rail.empty.title")) : null, (0, react.createElement)("button", {
|
|
8909
8979
|
className: "lks-btn ghost lks14-raildemo",
|
|
8910
8980
|
onClick: () => {
|
|
8911
|
-
|
|
8981
|
+
startImport(tr("import.tab.url"), tr("prompt.import", { url: "https://github.com/microsoft/AI-For-Beginners" }));
|
|
8912
8982
|
}
|
|
8913
8983
|
}, (0, react.createElement)(IconDownloadOutline16, null), tr("rail.empty.demo")), (0, react.createElement)(ImportPanel, {
|
|
8914
8984
|
job: importJob,
|
|
@@ -8919,6 +8989,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
8919
8989
|
onCancel: () => {
|
|
8920
8990
|
stop();
|
|
8921
8991
|
setImportJob(null);
|
|
8992
|
+
endImportSession();
|
|
8922
8993
|
setImportResult({
|
|
8923
8994
|
ok: false,
|
|
8924
8995
|
msg: tr("import.cancelled")
|