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 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
- const lang = document.documentElement.lang;
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
- return ctx.locale?.subscribe(read);
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 boundId = boundSessionOf(lesson !== null ? lesson.lessonId : null) ?? threadGroupOf(lesson !== null ? lesson.lessonId : null)?.active ?? (lesson !== null ? data?.lessonSessions[lesson.lessonId] ?? null : null);
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
- send(prompt);
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
- send(tr("prompt.import", { url: "https://github.com/microsoft/AI-For-Beginners" }));
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")