dsh-plugin-lookatstudy 0.8.1

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.
@@ -0,0 +1,104 @@
1
+ //#region src/vendor/notebook-parser.ts
2
+ /** 从 kernelspec 推断代码块语言标识(用于 markdown ``` 语法高亮) */
3
+ function inferLanguage(metadata) {
4
+ if (metadata.kernelspec?.language) return metadata.kernelspec.language.toLowerCase();
5
+ if (metadata.language_info?.name) return metadata.language_info.name.toLowerCase();
6
+ const name = metadata.kernelspec?.name ?? "";
7
+ if (name.includes("python")) return "python";
8
+ if (name.includes("ir") || name.includes("r-")) return "r";
9
+ if (name.includes("julia")) return "julia";
10
+ return "python";
11
+ }
12
+ /**
13
+ * 把 source 字段(string 或 string[])统一成 string。
14
+ * nbformat 的 source 可以是数组(每行一个元素)或整体字符串。
15
+ */
16
+ function normalizeSource(source) {
17
+ if (typeof source === "string") return source;
18
+ if (Array.isArray(source)) return source.join("");
19
+ return "";
20
+ }
21
+ /**
22
+ * 从 code cell 的 outputs 里提取图片(base64)。
23
+ * nbformat output 结构:
24
+ * { output_type: "execute_result"|"display_data", data: { "image/png": "base64..." } }
25
+ * { output_type: "stream", text: "..." } — 纯文本流,不含图
26
+ * { output_type: "error", ename, evalue } — 错误,不含图
27
+ */
28
+ function extractOutputImages(outputs, cellIndex) {
29
+ if (!Array.isArray(outputs)) return [];
30
+ const images = [];
31
+ for (const output of outputs) {
32
+ if (!output || typeof output !== "object") continue;
33
+ const data = output.data;
34
+ if (!data) continue;
35
+ const png = data["image/png"];
36
+ if (typeof png === "string" && png.length > 0) images.push({
37
+ base64: png,
38
+ mimeType: "image/png",
39
+ source: "notebook_output",
40
+ cellIndex,
41
+ altText: `Notebook cell ${cellIndex} 输出图`
42
+ });
43
+ const jpeg = data["image/jpeg"];
44
+ if (typeof jpeg === "string" && jpeg.length > 0) images.push({
45
+ base64: jpeg,
46
+ mimeType: "image/jpeg",
47
+ source: "notebook_output",
48
+ cellIndex,
49
+ altText: `Notebook cell ${cellIndex} 输出图`
50
+ });
51
+ }
52
+ return images;
53
+ }
54
+ /**
55
+ * 解析一个 .ipynb JSON 字符串,转成 markdown + 图片。
56
+ *
57
+ * @param jsonText .ipynb 文件的原始 JSON 字符串
58
+ * @returns { markdown, images, language, stats }
59
+ *
60
+ * 纯函数,不依赖 IO,可直接测。
61
+ */
62
+ function parseNotebook(jsonText) {
63
+ let nb;
64
+ try {
65
+ nb = JSON.parse(jsonText);
66
+ } catch {
67
+ throw new Error("parseNotebook: 无效的 JSON(不是合法的 .ipynb 文件)");
68
+ }
69
+ const cells = Array.isArray(nb.cells) ? nb.cells : [];
70
+ const language = inferLanguage(nb.metadata ?? {});
71
+ const mdParts = [];
72
+ const images = [];
73
+ let markdownCells = 0;
74
+ let codeCells = 0;
75
+ for (let i = 0; i < cells.length; i++) {
76
+ const cell = cells[i];
77
+ if (!cell) continue;
78
+ const cellType = cell.cell_type;
79
+ const source = normalizeSource(cell.source);
80
+ if (cellType === "markdown") {
81
+ markdownCells++;
82
+ mdParts.push(source);
83
+ } else if (cellType === "code") {
84
+ codeCells++;
85
+ mdParts.push(`\`\`\`${language}\n${source}\n\`\`\``);
86
+ const cellImages = extractOutputImages(cell.outputs, i);
87
+ images.push(...cellImages);
88
+ }
89
+ }
90
+ return {
91
+ markdown: mdParts.join("\n\n"),
92
+ images,
93
+ language,
94
+ stats: {
95
+ markdownCells,
96
+ codeCells,
97
+ totalCells: cells.length
98
+ }
99
+ };
100
+ }
101
+ //#endregion
102
+ export { parseNotebook };
103
+
104
+ //# sourceMappingURL=notebook-parser-ChbZBIKJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notebook-parser-ChbZBIKJ.mjs","names":["o"],"sources":["../src/vendor/notebook-parser.ts"],"sourcesContent":["// Vendored from LookatStudy src/main/services/pure/notebook-parser.ts (MIT License, https://github.com/kaiji/LookatStudy).\n// Unmodified except this provenance header. PDF/PPTX branches resolve unavailable optional libs and are skipped per upstream try/catch.\n/**\r\n * Jupyter Notebook (.ipynb) 解析器 —— 把 JSON 格式的 notebook 转成 markdown + 图片。\r\n *\r\n * nbformat 4 结构:\r\n * {\r\n * cells: [\r\n * { cell_type: \"markdown\", source: string | string[] },\r\n * { cell_type: \"code\", source: string | string[], outputs: [\r\n * { data: { \"image/png\": \"base64...\", \"text/plain\": \"...\" } }\r\n * ] }\r\n * ],\r\n * metadata: { kernelspec: { display_name, name, language } }\r\n * }\r\n *\r\n * 转换策略:\r\n * - markdown cell → 原样输出(保持 Markdown 格式)\r\n * - code cell → ```{language}\\n{code}\\n``` 代码块\r\n * - code cell 的 image output → 提取为 ScannedImage(buffer 型,source=\"notebook_output\")\r\n * - code cell 的 text output → 附在代码块后作为注释(可选)\r\n *\r\n * 纯函数设计,便于 verify 脚本测(JSON 解构 + cell 分拣,不依赖 IO)。\r\n */\r\n\r\n/** 解析出的图片(notebook output 或 markdown 引用) */\r\nexport interface NotebookImage {\r\n /** base64 数据(不含 data: 前缀) */\r\n base64: string;\r\n mimeType: string;\r\n /** 来源类型 */\r\n source: \"notebook_output\" | \"markdown_ref\";\r\n /** cell 索引(用于溯源) */\r\n cellIndex: number;\r\n /** alt/描述 */\r\n altText: string;\r\n}\r\n\r\n/** notebook 解析结果 */\r\nexport interface ParsedNotebook {\r\n /** 转换后的 markdown 文本(markdown cell + code block) */\r\n markdown: string;\r\n /** 提取的图片(output 内嵌图) */\r\n images: NotebookImage[];\r\n /** 语言(Python / R / Julia 等,从 kernelspec 推断) */\r\n language: string;\r\n /** cell 统计 */\r\n stats: {\r\n markdownCells: number;\r\n codeCells: number;\r\n totalCells: number;\r\n };\r\n}\r\n\r\n/** 从 kernelspec 推断代码块语言标识(用于 markdown ``` 语法高亮) */\r\nexport function inferLanguage(metadata: {\r\n kernelspec?: { name?: string; display_name?: string; language?: string };\r\n language_info?: { name?: string };\r\n}): string {\r\n // kernelspec.language 优先\r\n if (metadata.kernelspec?.language) return metadata.kernelspec.language.toLowerCase();\r\n // language_info.name\r\n if (metadata.language_info?.name) return metadata.language_info.name.toLowerCase();\r\n // kernelspec.name 推断\r\n const name = metadata.kernelspec?.name ?? \"\";\r\n if (name.includes(\"python\")) return \"python\";\r\n if (name.includes(\"ir\") || name.includes(\"r-\")) return \"r\";\r\n if (name.includes(\"julia\")) return \"julia\";\r\n return \"python\"; // 默认 python(Jupyter 最常见)\r\n}\r\n\r\n/**\r\n * 把 source 字段(string 或 string[])统一成 string。\r\n * nbformat 的 source 可以是数组(每行一个元素)或整体字符串。\r\n */\r\nexport function normalizeSource(source: unknown): string {\r\n if (typeof source === \"string\") return source;\r\n if (Array.isArray(source)) return source.join(\"\");\r\n return \"\";\r\n}\r\n\r\n/**\r\n * 从 code cell 的 outputs 里提取图片(base64)。\r\n * nbformat output 结构:\r\n * { output_type: \"execute_result\"|\"display_data\", data: { \"image/png\": \"base64...\" } }\r\n * { output_type: \"stream\", text: \"...\" } — 纯文本流,不含图\r\n * { output_type: \"error\", ename, evalue } — 错误,不含图\r\n */\r\nexport function extractOutputImages(\r\n outputs: unknown[] | undefined,\r\n cellIndex: number,\r\n): NotebookImage[] {\r\n if (!Array.isArray(outputs)) return [];\r\n const images: NotebookImage[] = [];\r\n for (const output of outputs) {\r\n if (!output || typeof output !== \"object\") continue;\r\n const o = output as Record<string, unknown>;\r\n const data = o.data as Record<string, unknown> | undefined;\r\n if (!data) continue;\r\n // image/png\r\n const png = data[\"image/png\"];\r\n if (typeof png === \"string\" && png.length > 0) {\r\n images.push({\r\n base64: png,\r\n mimeType: \"image/png\",\r\n source: \"notebook_output\",\r\n cellIndex,\r\n altText: `Notebook cell ${cellIndex} 输出图`,\r\n });\r\n }\r\n // image/jpeg\r\n const jpeg = data[\"image/jpeg\"];\r\n if (typeof jpeg === \"string\" && jpeg.length > 0) {\r\n images.push({\r\n base64: jpeg,\r\n mimeType: \"image/jpeg\",\r\n source: \"notebook_output\",\r\n cellIndex,\r\n altText: `Notebook cell ${cellIndex} 输出图`,\r\n });\r\n }\r\n }\r\n return images;\r\n}\r\n\r\n/**\r\n * 解析一个 .ipynb JSON 字符串,转成 markdown + 图片。\r\n *\r\n * @param jsonText .ipynb 文件的原始 JSON 字符串\r\n * @returns { markdown, images, language, stats }\r\n *\r\n * 纯函数,不依赖 IO,可直接测。\r\n */\r\nexport function parseNotebook(jsonText: string): ParsedNotebook {\r\n let nb: Record<string, unknown>;\r\n try {\r\n nb = JSON.parse(jsonText);\r\n } catch {\r\n throw new Error(\"parseNotebook: 无效的 JSON(不是合法的 .ipynb 文件)\");\r\n }\r\n\r\n const cells = Array.isArray(nb.cells) ? (nb.cells as Record<string, unknown>[]) : [];\r\n const metadata = (nb.metadata ?? {}) as ParsedNotebook[\"stats\"] extends never ? never : {\r\n kernelspec?: { name?: string; display_name?: string; language?: string };\r\n language_info?: { name?: string };\r\n };\r\n const language = inferLanguage(metadata as Parameters<typeof inferLanguage>[0]);\r\n\r\n const mdParts: string[] = [];\r\n const images: NotebookImage[] = [];\r\n let markdownCells = 0;\r\n let codeCells = 0;\r\n\r\n for (let i = 0; i < cells.length; i++) {\r\n const cell = cells[i];\r\n if (!cell) continue;\r\n const cellType = cell.cell_type as string | undefined;\r\n const source = normalizeSource(cell.source);\r\n\r\n if (cellType === \"markdown\") {\r\n markdownCells++;\r\n mdParts.push(source);\r\n } else if (cellType === \"code\") {\r\n codeCells++;\r\n // 代码块\r\n mdParts.push(`\\`\\`\\`${language}\\n${source}\\n\\`\\`\\``);\r\n // 提取 output 图片\r\n const cellImages = extractOutputImages(cell.outputs as unknown[] | undefined, i);\r\n images.push(...cellImages);\r\n }\r\n // raw cell / 其他类型跳过\r\n }\r\n\r\n return {\r\n markdown: mdParts.join(\"\\n\\n\"),\r\n images,\r\n language,\r\n stats: {\r\n markdownCells,\r\n codeCells,\r\n totalCells: cells.length,\r\n },\r\n };\r\n}\r\n"],"mappings":";;AAuDA,SAAgB,cAAc,UAGnB;CAET,IAAI,SAAS,YAAY,UAAU,OAAO,SAAS,WAAW,SAAS,YAAY;CAEnF,IAAI,SAAS,eAAe,MAAM,OAAO,SAAS,cAAc,KAAK,YAAY;CAEjF,MAAM,OAAO,SAAS,YAAY,QAAQ;CAC1C,IAAI,KAAK,SAAS,QAAQ,GAAG,OAAO;CACpC,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,OAAO;CACvD,IAAI,KAAK,SAAS,OAAO,GAAG,OAAO;CACnC,OAAO;AACT;;;;;AAMA,SAAgB,gBAAgB,QAAyB;CACvD,IAAI,OAAO,WAAW,UAAU,OAAO;CACvC,IAAI,MAAM,QAAQ,MAAM,GAAG,OAAO,OAAO,KAAK,EAAE;CAChD,OAAO;AACT;;;;;;;;AASA,SAAgB,oBACd,SACA,WACiB;CACjB,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO,CAAC;CACrC,MAAM,SAA0B,CAAC;CACjC,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU;EAE3C,MAAM,OAAOA,OAAE;EACf,IAAI,CAAC,MAAM;EAEX,MAAM,MAAM,KAAK;EACjB,IAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,GAC1C,OAAO,KAAK;GACV,QAAQ;GACR,UAAU;GACV,QAAQ;GACR;GACA,SAAS,iBAAiB,UAAU;EACtC,CAAC;EAGH,MAAM,OAAO,KAAK;EAClB,IAAI,OAAO,SAAS,YAAY,KAAK,SAAS,GAC5C,OAAO,KAAK;GACV,QAAQ;GACR,UAAU;GACV,QAAQ;GACR;GACA,SAAS,iBAAiB,UAAU;EACtC,CAAC;CAEL;CACA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,cAAc,UAAkC;CAC9D,IAAI;CACJ,IAAI;EACF,KAAK,KAAK,MAAM,QAAQ;CAC1B,QAAQ;EACN,MAAM,IAAI,MAAM,0CAA0C;CAC5D;CAEA,MAAM,QAAQ,MAAM,QAAQ,GAAG,KAAK,IAAK,GAAG,QAAsC,CAAC;CAKnF,MAAM,WAAW,cAJC,GAAG,YAAY,CAAC,CAI4C;CAE9E,MAAM,UAAoB,CAAC;CAC3B,MAAM,SAA0B,CAAC;CACjC,IAAI,gBAAgB;CACpB,IAAI,YAAY;CAEhB,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM;EACnB,IAAI,CAAC,MAAM;EACX,MAAM,WAAW,KAAK;EACtB,MAAM,SAAS,gBAAgB,KAAK,MAAM;EAE1C,IAAI,aAAa,YAAY;GAC3B;GACA,QAAQ,KAAK,MAAM;EACrB,OAAO,IAAI,aAAa,QAAQ;GAC9B;GAEA,QAAQ,KAAK,SAAS,SAAS,IAAI,OAAO,SAAS;GAEnD,MAAM,aAAa,oBAAoB,KAAK,SAAkC,CAAC;GAC/E,OAAO,KAAK,GAAG,UAAU;EAC3B;CAEF;CAEA,OAAO;EACL,UAAU,QAAQ,KAAK,MAAM;EAC7B;EACA;EACA,OAAO;GACL;GACA;GACA,YAAY,MAAM;EACpB;CACF;AACF"}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "dsh-plugin-lookatstudy",
3
+ "version": "0.8.1",
4
+ "description": "Turn any markdown, local folder, or GitHub learning repo into a guided course inside DeepSeek Harness: gated skill-tree progression, BKT mastery tracking, SM-2 spaced repetition.",
5
+ "type": "module",
6
+ "main": "lib/index.mjs",
7
+ "types": "lib/index.d.mts",
8
+ "files": [
9
+ "lib",
10
+ "cordis.patch.yml",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsdown --config tsdown.config.ts",
15
+ "test": "node --import tsx --test tests/*.test.ts",
16
+ "verify": "node scripts/verify.mjs"
17
+ },
18
+ "keywords": [
19
+ "dsh-plugin",
20
+ "deepseek-harness",
21
+ "learning",
22
+ "tutor",
23
+ "spaced-repetition"
24
+ ],
25
+ "license": "MIT",
26
+ "dsh": {
27
+ "bundle": {
28
+ "patch": "./cordis.patch.yml"
29
+ },
30
+ "client": {
31
+ "inject": [
32
+ "slots",
33
+ "workspaces",
34
+ "sessions"
35
+ ],
36
+ "platform": "web"
37
+ }
38
+ },
39
+ "peerDependencies": {
40
+ "@deepseek-ai/cordis": ">=0.1.0-rc.0",
41
+ "@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.0",
42
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.1.0-rc.0",
43
+ "@deepseek-ai/dsh-tools": ">=0.1.0-rc.0",
44
+ "@deepseek-ai/schemastery": ">=0.1.0-rc.0",
45
+ "react": ">=18"
46
+ },
47
+ "exports": {
48
+ ".": {
49
+ "types": "./lib/index.d.mts",
50
+ "default": "./lib/index.mjs"
51
+ },
52
+ "./client": {
53
+ "default": "./lib/client.js"
54
+ },
55
+ "./package.json": "./package.json"
56
+ },
57
+ "devDependencies": {
58
+ "@types/react": "~18.3.31",
59
+ "react": "^18.3.1"
60
+ },
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "git+https://github.com/Kaiji-Z/dsh-plugin-lookatstudy.git"
64
+ },
65
+ "bugs": {
66
+ "url": "https://github.com/Kaiji-Z/dsh-plugin-lookatstudy/issues"
67
+ },
68
+ "homepage": "https://github.com/Kaiji-Z/dsh-plugin-lookatstudy#readme",
69
+ "engines": {
70
+ "node": ">=22.19"
71
+ }
72
+ }