@khorsheed/dsh-library 0.0.0-stage → 0.1.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/CHANGELOG.md +14 -0
- package/README.en.md +49 -0
- package/README.i18n.yaml +6 -0
- package/README.md +48 -2
- package/cordis.patch.yml +17 -0
- package/lib/client.js +66276 -0
- package/lib/index.js +3318 -0
- package/lib/profiles-BXM8xNUf.js +1608 -0
- package/lib/profiles-BrbeM-bi.js +1635 -0
- package/lib/profiles-CFrPQdfO.js +1692 -0
- package/lib/profiles-DxtzIUvd.js +1632 -0
- package/lib/profiles-O0ddEit3.js +1631 -0
- package/lib/profiles-Oj-zBAY9.js +1692 -0
- package/lib/profiles-gIEviEuO.js +1610 -0
- package/lib/store-CIcZgTsg.js +1456 -0
- package/lib/store-DfYOmS44.js +1418 -0
- package/lib/tool.js +1597 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +3811 -0
- package/lib/typert.remote-client.d.ts +137 -0
- package/lib/typert.remote-client.js +3077 -0
- package/lib/types/cards.d.ts +41 -0
- package/lib/types/cards.js +111 -0
- package/lib/types/client/LibraryPane.d.ts +26 -0
- package/lib/types/client/LibraryPane.js +117 -0
- package/lib/types/client/bind/BindChip.d.ts +33 -0
- package/lib/types/client/bind/BindChip.js +102 -0
- package/lib/types/client/contract.d.ts +164 -0
- package/lib/types/client/contract.js +1 -0
- package/lib/types/client/covers.d.ts +36 -0
- package/lib/types/client/covers.js +66 -0
- package/lib/types/client/definition.d.ts +27 -0
- package/lib/types/client/definition.js +32 -0
- package/lib/types/client/icons-local.d.ts +41 -0
- package/lib/types/client/icons-local.js +25 -0
- package/lib/types/client/icons.d.ts +27 -0
- package/lib/types/client/icons.js +11 -0
- package/lib/types/client/index.d.ts +35 -0
- package/lib/types/client/index.js +219 -0
- package/lib/types/client/judge.d.ts +70 -0
- package/lib/types/client/judge.js +262 -0
- package/lib/types/client/locales.d.ts +553 -0
- package/lib/types/client/locales.js +1076 -0
- package/lib/types/client/look.d.ts +63 -0
- package/lib/types/client/look.js +64 -0
- package/lib/types/client/math.d.ts +24 -0
- package/lib/types/client/math.js +47 -0
- package/lib/types/client/mistakes.d.ts +21 -0
- package/lib/types/client/mistakes.js +28 -0
- package/lib/types/client/preset-visibility.d.ts +109 -0
- package/lib/types/client/preset-visibility.js +123 -0
- package/lib/types/client/quiz-drafts.d.ts +29 -0
- package/lib/types/client/quiz-drafts.js +50 -0
- package/lib/types/client/store.d.ts +109 -0
- package/lib/types/client/store.js +44 -0
- package/lib/types/client/titles.d.ts +17 -0
- package/lib/types/client/titles.js +23 -0
- package/lib/types/client/ui.d.ts +96 -0
- package/lib/types/client/ui.js +124 -0
- package/lib/types/client/views/book.d.ts +57 -0
- package/lib/types/client/views/book.js +164 -0
- package/lib/types/client/views/cards.d.ts +17 -0
- package/lib/types/client/views/cards.js +53 -0
- package/lib/types/client/views/home.d.ts +26 -0
- package/lib/types/client/views/home.js +263 -0
- package/lib/types/client/views/lesson.d.ts +42 -0
- package/lib/types/client/views/lesson.js +146 -0
- package/lib/types/client/views/path.d.ts +32 -0
- package/lib/types/client/views/path.js +64 -0
- package/lib/types/client/views/quiz.d.ts +42 -0
- package/lib/types/client/views/quiz.js +366 -0
- package/lib/types/client/views/station-quiz.d.ts +43 -0
- package/lib/types/client/views/station-quiz.js +203 -0
- package/lib/types/client/views/station.d.ts +18 -0
- package/lib/types/client/views/station.js +142 -0
- package/lib/types/image-refs.d.ts +35 -0
- package/lib/types/image-refs.js +71 -0
- package/lib/types/index.d.ts +51 -0
- package/lib/types/index.js +31 -0
- package/lib/types/mastery.d.ts +81 -0
- package/lib/types/mastery.js +110 -0
- package/lib/types/profiles.d.ts +31 -0
- package/lib/types/profiles.js +25 -0
- package/lib/types/remote.d.ts +355 -0
- package/lib/types/remote.js +332 -0
- package/lib/types/service/assess.d.ts +144 -0
- package/lib/types/service/assess.js +773 -0
- package/lib/types/service/bindings.d.ts +92 -0
- package/lib/types/service/bindings.js +198 -0
- package/lib/types/service/cards.d.ts +85 -0
- package/lib/types/service/cards.js +320 -0
- package/lib/types/service/core.d.ts +124 -0
- package/lib/types/service/core.js +169 -0
- package/lib/types/service/pages.d.ts +60 -0
- package/lib/types/service/pages.js +125 -0
- package/lib/types/service/roadmap.d.ts +99 -0
- package/lib/types/service/roadmap.js +284 -0
- package/lib/types/service/rules.d.ts +63 -0
- package/lib/types/service/rules.js +245 -0
- package/lib/types/service/shelf.d.ts +136 -0
- package/lib/types/service/shelf.js +422 -0
- package/lib/types/service.d.ts +42 -0
- package/lib/types/service.js +41 -0
- package/lib/types/store.d.ts +257 -0
- package/lib/types/store.js +1376 -0
- package/lib/types/tool-definition.d.ts +49 -0
- package/lib/types/tool-definition.js +49 -0
- package/lib/types/tool.d.ts +71 -0
- package/lib/types/tool.js +174 -0
- package/lib/types/tools/assess.d.ts +11 -0
- package/lib/types/tools/assess.js +82 -0
- package/lib/types/tools/binding.d.ts +11 -0
- package/lib/types/tools/binding.js +46 -0
- package/lib/types/tools/cards.d.ts +11 -0
- package/lib/types/tools/cards.js +100 -0
- package/lib/types/tools/kit.d.ts +215 -0
- package/lib/types/tools/kit.js +281 -0
- package/lib/types/tools/notes.d.ts +11 -0
- package/lib/types/tools/notes.js +40 -0
- package/lib/types/tools/page.d.ts +11 -0
- package/lib/types/tools/page.js +59 -0
- package/lib/types/tools/project.d.ts +11 -0
- package/lib/types/tools/project.js +67 -0
- package/lib/types/tools/quiz.d.ts +11 -0
- package/lib/types/tools/quiz.js +74 -0
- package/lib/types/tools/read.d.ts +11 -0
- package/lib/types/tools/read.js +127 -0
- package/lib/types/tools/resource.d.ts +11 -0
- package/lib/types/tools/resource.js +147 -0
- package/lib/types/tools/roadmap.d.ts +11 -0
- package/lib/types/tools/roadmap.js +102 -0
- package/lib/types/types.d.ts +752 -0
- package/lib/types/types.js +159 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +135 -3
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_project` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/project
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { jsonOutput, pickString, unwrap } from "./kit.js";
|
|
9
|
+
/** Build the `library_project` tool over the shared kit. */
|
|
10
|
+
export function libraryProjectTool(kit) {
|
|
11
|
+
const { service, autoBind } = kit;
|
|
12
|
+
return defineTool({
|
|
13
|
+
name: 'library_project',
|
|
14
|
+
description: 'Create a learning project (one goal), or edit one (projectId + what changes; goal "" clears). template: subject | language (marks are words) | custom. Next: shelve its textbooks (library_textbook).',
|
|
15
|
+
parameters: {
|
|
16
|
+
projectId: { type: 'string', description: 'Edit this project; omit to create (ids are minted).' },
|
|
17
|
+
name: { type: 'string' },
|
|
18
|
+
template: { type: 'string', enum: ['subject', 'language', 'custom'] },
|
|
19
|
+
goal: { type: 'string', description: 'One line.' },
|
|
20
|
+
},
|
|
21
|
+
output: jsonOutput(),
|
|
22
|
+
isConcurrencySafe: () => false,
|
|
23
|
+
async execute(args, exec) {
|
|
24
|
+
// The edit target's id: projectId now, `id` from a model that memorized
|
|
25
|
+
// the old name (read off the typed surface). An id that resolves to
|
|
26
|
+
// nothing plus a name is a CREATE the model dressed up with a slug —
|
|
27
|
+
// create and say so, never fail it.
|
|
28
|
+
const legacyId = args.id;
|
|
29
|
+
const editId = pickString(args.projectId, legacyId);
|
|
30
|
+
if (editId !== undefined) {
|
|
31
|
+
const patch = {};
|
|
32
|
+
if (typeof args.name === 'string')
|
|
33
|
+
patch.name = args.name;
|
|
34
|
+
if (typeof args.template === 'string')
|
|
35
|
+
patch.template = args.template;
|
|
36
|
+
if (typeof args.goal === 'string')
|
|
37
|
+
patch.goal = args.goal;
|
|
38
|
+
if (Object.keys(patch).length === 0)
|
|
39
|
+
throw new Error('editing a project takes projectId plus the fields to change (name? / goal? / template?) — omit projectId to CREATE one (name + template)');
|
|
40
|
+
const edited = await service.updateProject({ id: editId, patch: patch });
|
|
41
|
+
if (edited.ok) {
|
|
42
|
+
await autoBind(exec, { projectId: edited.project.id });
|
|
43
|
+
return edited;
|
|
44
|
+
}
|
|
45
|
+
if (typeof args.name !== 'string' || args.name.trim() === '')
|
|
46
|
+
throw new Error(edited.message);
|
|
47
|
+
const created = unwrap(await service.createProject({
|
|
48
|
+
name: args.name,
|
|
49
|
+
...(typeof args.template === 'string' ? { template: args.template } : {}),
|
|
50
|
+
...(typeof args.goal === 'string' ? { goal: args.goal } : {}),
|
|
51
|
+
}));
|
|
52
|
+
await autoBind(exec, { projectId: created.project.id });
|
|
53
|
+
return { ...created, note: `no project ${JSON.stringify(editId)} existed — created a new one instead (ids are minted (p_…), never chosen by the caller; omit projectId to create)` };
|
|
54
|
+
}
|
|
55
|
+
if (typeof args.name !== 'string' || args.name.trim() === '') {
|
|
56
|
+
throw new Error('creating a project takes name (+ template: "language" | "subject" | "custom", goal?) — e.g. { name: "数学", template: "subject" }');
|
|
57
|
+
}
|
|
58
|
+
const created = unwrap(await service.createProject({
|
|
59
|
+
name: args.name,
|
|
60
|
+
...(typeof args.template === 'string' ? { template: args.template } : {}),
|
|
61
|
+
...(typeof args.goal === 'string' ? { goal: args.goal } : {}),
|
|
62
|
+
}));
|
|
63
|
+
await autoBind(exec, { projectId: created.project.id });
|
|
64
|
+
return created;
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_quiz` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/quiz
|
|
6
|
+
*/
|
|
7
|
+
import { type ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { type ToolKit } from './kit.ts';
|
|
9
|
+
/** Build the `library_quiz` tool over the shared kit. */
|
|
10
|
+
export declare function libraryQuizTool(kit: ToolKit): ToolDefinition;
|
|
11
|
+
//# sourceMappingURL=quiz.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_quiz` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/quiz
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { normalizeQuizOptions } from "../store.js";
|
|
9
|
+
import { arrival, jsonOutput, normalizeQuestionAxes, payloadList, pickString, QUESTIONS_SCHEMA, unwrap, } from "./kit.js";
|
|
10
|
+
/** Build the `library_quiz` tool over the shared kit. */
|
|
11
|
+
export function libraryQuizTool(kit) {
|
|
12
|
+
const { service, bindingOf, projectOf, autoBind, scopeParams } = kit;
|
|
13
|
+
return defineTool({
|
|
14
|
+
name: 'library_quiz',
|
|
15
|
+
description: 'Store a quiz, then reply with one line — the learner answers in the sidebar. node = the station it tests (omit: a whole-textbook quiz; resource null: a free quiz). '
|
|
16
|
+
+ 'focus is the learner\'s choice: mine = only their cards; book = the chapter only; mixed = their cards, then the chapter. '
|
|
17
|
+
+ 'Every question names its point; quiz weak and untested points first, then practicing ones. '
|
|
18
|
+
+ 'Solve each question again before storing; fix any disagreement. choice: answer = the right option verbatim. fill: a short value (1/2), several blanks comma-separated, alternatives " / ". free: say what a good answer covers. '
|
|
19
|
+
+ 'Math only in $…$. The prompt carries no kind label or chapter reference. A material textbook needs quote (the page text). Replace voided (「这题有问题」) questions on the same points.',
|
|
20
|
+
parameters: {
|
|
21
|
+
...scopeParams,
|
|
22
|
+
resource: { type: 'json', description: 'The textbook (defaults to the binding\'s); null = a free quiz.' },
|
|
23
|
+
node: { type: 'string', description: 'The quiz-gated station it gates.' },
|
|
24
|
+
focus: { type: 'string', enum: ['mine', 'book', 'mixed'] },
|
|
25
|
+
questions: {
|
|
26
|
+
...QUESTIONS_SCHEMA,
|
|
27
|
+
required: true,
|
|
28
|
+
description: 'The questions.',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
output: jsonOutput(),
|
|
32
|
+
isConcurrencySafe: () => false,
|
|
33
|
+
async execute(args, exec) {
|
|
34
|
+
const questions = payloadList(args.questions);
|
|
35
|
+
if (questions === undefined) {
|
|
36
|
+
throw new Error(`library_quiz takes questions: [{ point, kind: "choice"|"fill"|"free", prompt, options?, answer, explanation?, quote? }] (+ node?, focus?) — ${arrival('questions', args.questions)}`);
|
|
37
|
+
}
|
|
38
|
+
const node = typeof args.node === 'string' && args.node !== '' ? args.node : undefined;
|
|
39
|
+
// An explicit null is a free quiz on no textbook; absent falls back to the binding's.
|
|
40
|
+
const argsAny = args;
|
|
41
|
+
const resourceId = args.resource === null || argsAny.book === null ? undefined : pickString(args.resource, argsAny.book) ?? bindingOf(exec)?.resourceId;
|
|
42
|
+
// Options may arrive as { label } objects or a letter-keyed map —
|
|
43
|
+
// the stored form is plain strings (the same tolerance reads them
|
|
44
|
+
// back); the two axes default (topic + knowledge) and take the flat
|
|
45
|
+
// cardIds / quote / url / pageId shorthands.
|
|
46
|
+
const cleaned = questions.map((raw, index) => {
|
|
47
|
+
// The schema no longer rejects a question missing its id / kind / prompt /
|
|
48
|
+
// answer (that refusal named no question); blanks reach the service's
|
|
49
|
+
// per-question check, which names each one.
|
|
50
|
+
const text = (value) => typeof value === 'string' ? value : typeof value === 'number' ? String(value) : '';
|
|
51
|
+
// ids are numbered when the coach leaves them out (q1, q2…).
|
|
52
|
+
let next = { ...raw, id: text(raw.id) === '' ? `q${index + 1}` : text(raw.id), kind: text(raw.kind), prompt: text(raw.prompt), answer: text(raw.answer) };
|
|
53
|
+
if (next.options !== undefined) {
|
|
54
|
+
const options = normalizeQuizOptions(next.options);
|
|
55
|
+
if (options !== undefined)
|
|
56
|
+
next = { ...next, options };
|
|
57
|
+
}
|
|
58
|
+
return normalizeQuestionAxes(next);
|
|
59
|
+
});
|
|
60
|
+
const projectId = projectOf(args.project, exec, resourceId);
|
|
61
|
+
const written = unwrap(await service.putQuiz({
|
|
62
|
+
projectId,
|
|
63
|
+
quiz: {
|
|
64
|
+
...(args.focus === 'mine' || args.focus === 'book' || args.focus === 'mixed' ? { focus: args.focus } : {}),
|
|
65
|
+
scope: { ...(resourceId === undefined ? {} : { resourceId }), ...(node === undefined ? {} : { nodeId: node }) },
|
|
66
|
+
questions: cleaned,
|
|
67
|
+
...(Array.isArray(argsAny.addPoints) ? { addPoints: argsAny.addPoints } : {}),
|
|
68
|
+
},
|
|
69
|
+
}));
|
|
70
|
+
await autoBind(exec, { projectId, ...(resourceId === undefined ? {} : { resourceId }) });
|
|
71
|
+
return written;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_read` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/read
|
|
6
|
+
*/
|
|
7
|
+
import { type ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { type ToolKit } from './kit.ts';
|
|
9
|
+
/** Build the `library_read` tool over the shared kit. */
|
|
10
|
+
export declare function libraryReadTool(kit: ToolKit): ToolDefinition;
|
|
11
|
+
//# sourceMappingURL=read.d.ts.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_read` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/read
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { jsonOutput, pickString, READ_WHATS, unwrap, } from "./kit.js";
|
|
9
|
+
/**
|
|
10
|
+
* A path as the coach needs it: each station's id, title, state, rows and
|
|
11
|
+
* points, and whether its 讲义 exist — never the 讲义 text itself (a read of
|
|
12
|
+
* an 11-station path once returned 7,300 characters).
|
|
13
|
+
*/
|
|
14
|
+
function compactRoadmap(read) {
|
|
15
|
+
const stations = (roadmap) => roadmap.nodes.map(node => ({
|
|
16
|
+
id: node.id,
|
|
17
|
+
title: node.title,
|
|
18
|
+
state: node.state,
|
|
19
|
+
...(node.gate === 'quiz' ? {} : { gate: node.gate }),
|
|
20
|
+
rows: node.covers.flatMap(cover => cover.locator === undefined ? [] : [cover.locator]).join(',') || undefined,
|
|
21
|
+
points: (node.points ?? []).map(point => `${point.id}: ${point.title}`),
|
|
22
|
+
...(node.goals.length === 0 ? {} : { goals: node.goals }),
|
|
23
|
+
...(node.acceptance === undefined || node.acceptance.length === 0 ? {} : { acceptance: node.acceptance }),
|
|
24
|
+
...(node.notes === undefined ? {} : { notes: `${node.notes.text.length} chars, revision ${node.notes.revisions}` }),
|
|
25
|
+
...(node.quizStats === undefined ? {} : { best: node.quizStats.bestRatio }),
|
|
26
|
+
}));
|
|
27
|
+
return {
|
|
28
|
+
...(read.roadmap === undefined ? { adopted: null } : { adopted: { stations: stations(read.roadmap) } }),
|
|
29
|
+
...(read.draft === undefined ? {} : { draft: { stations: stations(read.draft), note: read.draft.note } }),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Build the `library_read` tool over the shared kit. */
|
|
33
|
+
export function libraryReadTool(kit) {
|
|
34
|
+
const { service, bindingOf, projectOf, resourceOf } = kit;
|
|
35
|
+
return defineTool({
|
|
36
|
+
name: 'library_read',
|
|
37
|
+
description: 'Read the library: projects (all, with this session\'s binding) | resources (one project\'s textbooks in study order) | toc (a textbook\'s contents rows) | roadmap (a textbook\'s path and pending draft) | pages (photographed pages) | cards (the learner\'s cards, weakest first) | quiz (one quiz, or the list). project and resource default to the binding.',
|
|
38
|
+
parameters: {
|
|
39
|
+
what: { type: 'string', description: 'One of the reads above.' },
|
|
40
|
+
project: { type: 'string' },
|
|
41
|
+
resource: { type: 'string' },
|
|
42
|
+
node: { type: 'string', description: 'A station (pages / cards).' },
|
|
43
|
+
quiz: { type: 'string', description: 'A quiz id.' },
|
|
44
|
+
query: { type: 'string', description: 'cards: search term, gloss, sentences.' },
|
|
45
|
+
due: { type: 'boolean', description: 'cards: only those due for review.' },
|
|
46
|
+
},
|
|
47
|
+
output: jsonOutput(),
|
|
48
|
+
isConcurrencySafe: () => true,
|
|
49
|
+
async execute(args, exec) {
|
|
50
|
+
const argsAny = args;
|
|
51
|
+
// A read that forgot what: the arguments it did pass say which one it meant.
|
|
52
|
+
const what = typeof args.what === 'string' && args.what !== '' ? args.what
|
|
53
|
+
: args.quiz !== undefined ? 'quiz' : args.resource !== undefined || args.node !== undefined ? 'roadmap' : 'projects';
|
|
54
|
+
switch (what) {
|
|
55
|
+
case 'projects': {
|
|
56
|
+
const listed = await service.listProjects();
|
|
57
|
+
return { ...listed, binding: bindingOf(exec) ?? null };
|
|
58
|
+
}
|
|
59
|
+
case 'resources':
|
|
60
|
+
case 'books': { // the v4 spelling
|
|
61
|
+
const detail = unwrap(await service.getProject({ id: projectOf(args.project, exec) }));
|
|
62
|
+
return { project: detail.project, resources: detail.resources };
|
|
63
|
+
}
|
|
64
|
+
case 'toc': {
|
|
65
|
+
const resource = resourceOf(args, exec);
|
|
66
|
+
return unwrap(await service.getResource({ projectId: projectOf(args.project, exec, resource), resourceId: resource })).resource;
|
|
67
|
+
}
|
|
68
|
+
case 'roadmap': {
|
|
69
|
+
const resource = resourceOf(args, exec);
|
|
70
|
+
return compactRoadmap(await service.getRoadmap({ projectId: projectOf(args.project, exec, resource), resourceId: resource }));
|
|
71
|
+
}
|
|
72
|
+
case 'pages': {
|
|
73
|
+
const resource = resourceOf(args, exec);
|
|
74
|
+
return (await service.listPages({
|
|
75
|
+
projectId: projectOf(args.project, exec, resource),
|
|
76
|
+
resourceId: resource,
|
|
77
|
+
...(args.node === undefined ? {} : { nodeId: args.node }),
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
case 'cards': {
|
|
81
|
+
const resource = pickString(args.resource, args.book);
|
|
82
|
+
const result = await service.listCards({
|
|
83
|
+
projectId: projectOf(args.project, exec, resource),
|
|
84
|
+
...(resource === undefined ? {} : { resourceId: resource }),
|
|
85
|
+
...(args.node === undefined ? {} : { nodeId: args.node }),
|
|
86
|
+
...(args.query === undefined ? {} : { query: args.query }),
|
|
87
|
+
...(argsAny.kind === 'word' || argsAny.kind === 'concept' ? { kind: argsAny.kind } : {}),
|
|
88
|
+
...(['photo', 'chat', 'quiz', 'manual'].includes(String(argsAny.source ?? '')) ? { source: argsAny.source } : {}),
|
|
89
|
+
...(typeof argsAny.weakMin === 'number' ? { weakMin: argsAny.weakMin } : {}),
|
|
90
|
+
...(args.due === true ? { due: true } : {}),
|
|
91
|
+
limit: typeof argsAny.limit === 'number' ? argsAny.limit : 30,
|
|
92
|
+
});
|
|
93
|
+
// The model needs the card, not its whole ledger: sources compress to counts.
|
|
94
|
+
return {
|
|
95
|
+
total: result.total,
|
|
96
|
+
cards: result.cards.map(card => ({
|
|
97
|
+
id: card.id,
|
|
98
|
+
kind: card.kind,
|
|
99
|
+
term: card.term,
|
|
100
|
+
...(card.aliases.length === 0 ? {} : { aliases: card.aliases }),
|
|
101
|
+
gloss: card.gloss,
|
|
102
|
+
...(card.userGloss === undefined ? {} : { userGloss: card.userGloss }),
|
|
103
|
+
...(card.resourceId === undefined ? {} : { resource: card.resourceId }),
|
|
104
|
+
contexts: card.contexts.slice(0, 2).map(context => context.sentence),
|
|
105
|
+
weakness: card.weakness,
|
|
106
|
+
box: card.mastery.box,
|
|
107
|
+
dueAt: card.mastery.dueAt,
|
|
108
|
+
sources: card.sources.map(source => source.note === undefined ? source.type : `${source.type}: ${source.note}`).slice(-4),
|
|
109
|
+
})),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
case 'quiz': {
|
|
113
|
+
const resource = pickString(args.resource, args.book) ?? bindingOf(exec)?.resourceId;
|
|
114
|
+
const project = projectOf(args.project, exec, resource);
|
|
115
|
+
if (args.quiz === undefined || args.quiz.trim() === '') {
|
|
116
|
+
return (await service.listAssessments({ projectId: project, type: 'quiz', ...(resource === undefined ? {} : { resourceId: resource }) }));
|
|
117
|
+
}
|
|
118
|
+
return unwrap(await service.getAssessment({ projectId: project, assessmentId: args.quiz.trim() })).assessment;
|
|
119
|
+
}
|
|
120
|
+
case 'stats':
|
|
121
|
+
return (await service.stats());
|
|
122
|
+
default:
|
|
123
|
+
throw new Error(`what is one of ${READ_WHATS.join(' | ')} — got ${JSON.stringify(args.what)}`);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_textbook` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/resource
|
|
6
|
+
*/
|
|
7
|
+
import { type ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { type ToolKit } from './kit.ts';
|
|
9
|
+
/** Build the `library_textbook` tool over the shared kit (the file keeps its v5 name). */
|
|
10
|
+
export declare function libraryResourceTool(kit: ToolKit): ToolDefinition;
|
|
11
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_textbook` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/resource
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { isThickResource, LIBRARY_RESOURCE_KINDS } from "../types.js";
|
|
9
|
+
import { arrival, jsonOutput, payload, payloadList, pickString, splitTocText, unwrap, } from "./kit.js";
|
|
10
|
+
/** Build the `library_textbook` tool over the shared kit (the file keeps its v5 name). */
|
|
11
|
+
export function libraryResourceTool(kit) {
|
|
12
|
+
const { service, projectOf, autoBind, scopeParams } = kit;
|
|
13
|
+
return defineTool({
|
|
14
|
+
name: 'library_textbook',
|
|
15
|
+
description: 'Shelve, correct, reorder or remove a textbook (anything with a contents) — in study order. origin: learner (their own) | picked (a book or course you chose; its path takes only the chapters the goal needs) | authored (you write it: one chapter per lesson, your 讲义 are its text). '
|
|
16
|
+
+ 'A real book: verify the edition and contents online and cite the page (tocSource { by: "web", url }); never ask for an upload. A correction (resourceId) sends only what changes. A web page a station cites is no textbook — the path names its url.',
|
|
17
|
+
parameters: {
|
|
18
|
+
...scopeParams,
|
|
19
|
+
op: { type: 'string', required: true, description: 'add | update | move | remove.' },
|
|
20
|
+
resourceId: { type: 'string', description: 'The textbook to update / move / remove.' },
|
|
21
|
+
title: { type: 'string', description: 'The exact edition\'s title.' },
|
|
22
|
+
origin: { type: 'string', enum: ['learner', 'picked', 'authored'] },
|
|
23
|
+
toc: { type: 'json', description: 'The contents: one chapter per line in one string, or ["…", "…"].' },
|
|
24
|
+
tocSource: { type: 'json', description: '{ by: "web" | "model" | "photo", url? } — web needs the url.' },
|
|
25
|
+
seq: { type: 'integer', description: 'Its place in the study order, from 1.' },
|
|
26
|
+
bookKind: { type: 'string', enum: ['knowledge', 'material'], description: 'material only when the exact text is what is tested (exam papers, literature).' },
|
|
27
|
+
edition: { type: 'string' },
|
|
28
|
+
author: { type: 'string' },
|
|
29
|
+
url: { type: 'string', description: 'A digital textbook\'s home page.' },
|
|
30
|
+
note: { type: 'string', description: 'One line on why it is here.' },
|
|
31
|
+
},
|
|
32
|
+
output: jsonOutput(),
|
|
33
|
+
isConcurrencySafe: () => false,
|
|
34
|
+
async execute(args, exec) {
|
|
35
|
+
// The v4 spellings ride along off the untyped surface (bookId → resourceId).
|
|
36
|
+
const argsAny = args;
|
|
37
|
+
const namedId = pickString(args.resourceId, argsAny.bookId);
|
|
38
|
+
// add / update are the advertised spellings; create (either one) still parses.
|
|
39
|
+
const op = args.op === 'add' || args.op === 'update' ? 'create' : args.op;
|
|
40
|
+
if (op === 'remove') {
|
|
41
|
+
if (namedId === undefined)
|
|
42
|
+
throw new Error('library_textbook · op=remove takes the resourceId field — library_read · resources lists the ids');
|
|
43
|
+
return unwrap(await service.removeResource({ projectId: projectOf(args.project, exec, namedId), resourceId: namedId }));
|
|
44
|
+
}
|
|
45
|
+
if (op === 'move') {
|
|
46
|
+
if (namedId === undefined || typeof args.seq !== 'number')
|
|
47
|
+
throw new Error('library_textbook · op=move takes resourceId and seq (the textbook\'s new place, from 1)');
|
|
48
|
+
const moved = unwrap(await service.moveResource({ projectId: projectOf(args.project, exec, namedId), resourceId: namedId, seq: args.seq }));
|
|
49
|
+
return { order: moved.resources.filter(resource => resource.seq !== undefined).map(resource => `${resource.seq}. ${resource.id} 《${resource.title}》`) };
|
|
50
|
+
}
|
|
51
|
+
if (op !== 'create') {
|
|
52
|
+
throw new Error(`library_textbook needs op: add | update | move | remove — got ${JSON.stringify(args.op)}`);
|
|
53
|
+
}
|
|
54
|
+
// origin is the v6 field; the older addedBy still parses (coach → picked).
|
|
55
|
+
const originArg = argsAny.origin ?? (argsAny.addedBy === 'learner' ? 'learner' : argsAny.addedBy === 'coach' ? 'picked' : undefined);
|
|
56
|
+
const origin = originArg === 'learner' || originArg === 'picked' || originArg === 'authored' ? { origin: originArg } : {};
|
|
57
|
+
const projectId = projectOf(args.project, exec, namedId);
|
|
58
|
+
// Cloning from another project is no longer advertised but still parses.
|
|
59
|
+
const from = payload(argsAny.from);
|
|
60
|
+
if (from !== undefined) {
|
|
61
|
+
const fromProject = pickString(from.project, from.projectId);
|
|
62
|
+
const fromResource = pickString(from.resource, from.resourceId, from.book, from.bookId);
|
|
63
|
+
if (fromProject === undefined || fromResource === undefined)
|
|
64
|
+
throw new Error('from is { project, resource } — the OTHER project\'s id and the entry\'s id there');
|
|
65
|
+
const cloned = unwrap(await service.putResource({
|
|
66
|
+
projectId,
|
|
67
|
+
title: '',
|
|
68
|
+
kind: 'article',
|
|
69
|
+
...(namedId === undefined ? {} : { resourceId: namedId }),
|
|
70
|
+
from: { projectId: fromProject, resourceId: fromResource },
|
|
71
|
+
}));
|
|
72
|
+
await autoBind(exec, { projectId, resourceId: cloned.resource.id });
|
|
73
|
+
return cloned;
|
|
74
|
+
}
|
|
75
|
+
// A whole contents may arrive as one newline-joined string — the
|
|
76
|
+
// biggest single payload, and the one models mangle most as JSON.
|
|
77
|
+
const tocList = payloadList(args.toc);
|
|
78
|
+
const sentRows = typeof args.toc === 'string' && tocList === undefined
|
|
79
|
+
? splitTocText(args.toc)
|
|
80
|
+
: (tocList ?? []);
|
|
81
|
+
// Correcting a shelved textbook (its note, kind, edition…) sends only
|
|
82
|
+
// what changes: the title and the contents come from the shelf.
|
|
83
|
+
const shelved = namedId !== undefined && sentRows.length === 0 && pickString(args.url) === undefined
|
|
84
|
+
? await service.getResource({ projectId, resourceId: namedId })
|
|
85
|
+
: undefined;
|
|
86
|
+
const kept = shelved?.ok === true && isThickResource(shelved.resource) ? shelved.resource : undefined;
|
|
87
|
+
const tocRows = sentRows.length > 0 ? sentRows : (kept?.toc ?? []).map(entry => ({ ...entry }));
|
|
88
|
+
if (kept !== undefined) {
|
|
89
|
+
if (typeof args.title !== 'string' || args.title.trim() === '')
|
|
90
|
+
argsAny.title = kept.title;
|
|
91
|
+
if (argsAny.kind === undefined)
|
|
92
|
+
argsAny.kind = kept.kind;
|
|
93
|
+
}
|
|
94
|
+
// A thin link is the hidden v5 case: only an explicit link kind (article, video…) takes that branch.
|
|
95
|
+
const linkKind = typeof argsAny.kind === 'string' && argsAny.kind !== 'book' && argsAny.kind !== 'course' && LIBRARY_RESOURCE_KINDS.includes(argsAny.kind);
|
|
96
|
+
const thick = tocRows.length > 0 || !linkKind;
|
|
97
|
+
if (thick) {
|
|
98
|
+
if (typeof args.title !== 'string' || args.title.trim() === '' || tocRows.length === 0) {
|
|
99
|
+
throw new Error(`a textbook takes: title, toc (one chapter per line), tocSource { by, url? }, origin (+ seq?, bookKind?, edition?, author?; resourceId to update one) — ${arrival('toc', args.toc)}`);
|
|
100
|
+
}
|
|
101
|
+
const toc = tocRows.map(row => typeof row === 'string' ? { title: row } : row);
|
|
102
|
+
const written = unwrap(await service.putResource({
|
|
103
|
+
projectId,
|
|
104
|
+
...(namedId === undefined ? {} : { resourceId: namedId }),
|
|
105
|
+
title: args.title,
|
|
106
|
+
kind: argsAny.kind === 'course' ? 'course' : 'book',
|
|
107
|
+
...(typeof args.url === 'string' ? { url: args.url } : {}),
|
|
108
|
+
...(typeof args.note === 'string' ? { note: args.note } : {}),
|
|
109
|
+
...origin,
|
|
110
|
+
...(typeof args.seq === 'number' ? { seq: args.seq } : {}),
|
|
111
|
+
toc,
|
|
112
|
+
tocSource: (payload(args.tocSource) ?? kept?.tocSource ?? { by: 'model' }),
|
|
113
|
+
...(args.bookKind === 'knowledge' || args.bookKind === 'material' ? { bookKind: args.bookKind } : {}),
|
|
114
|
+
...(typeof argsAny.short === 'string' ? { short: argsAny.short } : {}),
|
|
115
|
+
...(typeof args.author === 'string' ? { author: args.author } : {}),
|
|
116
|
+
...(typeof args.edition === 'string' ? { edition: args.edition } : {}),
|
|
117
|
+
...(typeof argsAny.isbn === 'string' ? { isbn: argsAny.isbn } : {}),
|
|
118
|
+
...(Array.isArray(payloadList(argsAny.extras)) ? { extras: payloadList(argsAny.extras) } : {}),
|
|
119
|
+
}));
|
|
120
|
+
await autoBind(exec, { projectId, resourceId: written.resource.id });
|
|
121
|
+
// A real book with a handful of rows was almost always joined wrong — say so before a path is drafted against it.
|
|
122
|
+
const rows = written.resource.toc?.length ?? 0;
|
|
123
|
+
return {
|
|
124
|
+
...written,
|
|
125
|
+
contentsRows: rows,
|
|
126
|
+
...(rows < 3 ? { warning: `the contents has only ${rows} row(s) — one row per chapter is expected (station chapters count these rows). If the chapters got joined, resend toc with one chapter per line and resourceId ${written.resource.id}` } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const title = pickString(args.title);
|
|
130
|
+
const url = pickString(args.url);
|
|
131
|
+
if (title === undefined || url === undefined) {
|
|
132
|
+
throw new Error(`a link takes: title, url, kind ("article"|"video"|"repo"|"tool"|"hardware"), note? — got title ${title === undefined ? '✗ MISSING' : '✓'} · url ${url === undefined ? '✗ MISSING' : '✓'} (a textbook takes kind "book" | "course" + toc)`);
|
|
133
|
+
}
|
|
134
|
+
const added = unwrap(await service.putResource({
|
|
135
|
+
projectId,
|
|
136
|
+
...(namedId === undefined ? {} : { resourceId: namedId }),
|
|
137
|
+
title,
|
|
138
|
+
kind: linkKind ? argsAny.kind : 'article',
|
|
139
|
+
url,
|
|
140
|
+
...(typeof args.note === 'string' ? { note: args.note } : {}),
|
|
141
|
+
...origin,
|
|
142
|
+
}));
|
|
143
|
+
await autoBind(exec, { projectId });
|
|
144
|
+
return added;
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_roadmap` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/roadmap
|
|
6
|
+
*/
|
|
7
|
+
import { type ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { type ToolKit } from './kit.ts';
|
|
9
|
+
/** Build the `library_roadmap` tool over the shared kit. */
|
|
10
|
+
export declare function libraryRoadmapTool(kit: ToolKit): ToolDefinition;
|
|
11
|
+
//# sourceMappingURL=roadmap.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_roadmap` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/roadmap
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { formatLocator } from "../types.js";
|
|
9
|
+
import { arrival, chaptersLocator, jsonOutput, normalizeCoverArg, NODES_SCHEMA, payload, payloadList, pickString, unwrap, } from "./kit.js";
|
|
10
|
+
/** Build the `library_roadmap` tool over the shared kit. */
|
|
11
|
+
export function libraryRoadmapTool(kit) {
|
|
12
|
+
const { service, projectOf, autoBind, textbookList } = kit;
|
|
13
|
+
return defineTool({
|
|
14
|
+
name: 'library_roadmap',
|
|
15
|
+
description: 'Draft one textbook\'s path (replaces its previous draft); the learner adopts it in the sidebar. '
|
|
16
|
+
+ 'One station = one lesson: 3–5 knowledge points, simple → hard. A real book\'s long chapter splits into several stations; a textbook you authored teaches one chapter per station.',
|
|
17
|
+
parameters: {
|
|
18
|
+
resource: { type: 'string', required: true, description: 'The textbook.' },
|
|
19
|
+
nodes: { ...NODES_SCHEMA, required: true, description: 'The stations, in study order.' },
|
|
20
|
+
note: { type: 'string', description: 'One line of pacing for the draft card.' },
|
|
21
|
+
},
|
|
22
|
+
output: jsonOutput(),
|
|
23
|
+
isConcurrencySafe: () => false,
|
|
24
|
+
async execute(args, exec) {
|
|
25
|
+
const list = payloadList(args.nodes);
|
|
26
|
+
if (list === undefined) {
|
|
27
|
+
throw new Error(`library_roadmap takes resource and nodes: [{ id, title, chapters: "3-4", points: [{ id, title }] (3–5), goals?, acceptance? }] — ${arrival('nodes', args.nodes)}`);
|
|
28
|
+
}
|
|
29
|
+
const argsAny = args;
|
|
30
|
+
const resourceId = pickString(args.resource, argsAny.book);
|
|
31
|
+
if (resourceId === undefined)
|
|
32
|
+
throw new Error(`a path belongs to one textbook — pass resource${textbookList(exec) || ' (shelve the textbook first with library_textbook)'}`);
|
|
33
|
+
const adjusted = [];
|
|
34
|
+
const missingPoints = [];
|
|
35
|
+
const nodes = list.map(node => {
|
|
36
|
+
const { chapters, also, chapterRanges, covers, kind, gate, points, ...rest } = node;
|
|
37
|
+
// gate is the v5 spelling; the old kind still parses (practice → acceptance).
|
|
38
|
+
// Unsaid, every station is a quiz one: its check is the chapter quiz, never a
|
|
39
|
+
// rubric the learner cannot tell how to meet (10-26) — stray acceptance
|
|
40
|
+
// lines become goals.
|
|
41
|
+
const gateValue = typeof gate === 'string' ? gate : kind === 'practice' ? 'acceptance' : typeof kind === 'string' ? kind : 'quiz';
|
|
42
|
+
if (gateValue === 'quiz' && Array.isArray(rest.acceptance) && rest.acceptance.length > 0) {
|
|
43
|
+
const lines = rest.acceptance.flatMap(line => typeof line === 'string' ? [line] : typeof payload(line)?.line === 'string' ? [String(payload(line).line)] : []);
|
|
44
|
+
const goals = Array.isArray(rest.goals) ? rest.goals : typeof rest.goals === 'string' ? [rest.goals] : [];
|
|
45
|
+
rest.goals = [...goals, ...lines];
|
|
46
|
+
delete rest.acceptance;
|
|
47
|
+
}
|
|
48
|
+
if (gateValue === 'quiz' && (!Array.isArray(points) || points.length === 0)) {
|
|
49
|
+
missingPoints.push(String(rest.id ?? rest.title ?? '?'));
|
|
50
|
+
}
|
|
51
|
+
// Points belong to quiz stations; on any other gate they are dropped, not refused.
|
|
52
|
+
const keepPoints = gateValue === 'quiz' || points === undefined || (Array.isArray(points) && points.length === 0);
|
|
53
|
+
if (!keepPoints)
|
|
54
|
+
adjusted.push(`points dropped on ${String(rest.title ?? rest.id)} — a ${String(gateValue)} station has no quiz; what it checks belongs in ${gateValue === 'acceptance' ? 'acceptance lines' : 'goals'}`);
|
|
55
|
+
const main = [];
|
|
56
|
+
const spelled = typeof chapters === 'string' && chapters.trim() !== '' ? chaptersLocator(chapters) : undefined;
|
|
57
|
+
if (spelled?.rest !== undefined)
|
|
58
|
+
adjusted.push(`${String(rest.title ?? rest.id)}: "${spelled.rest}" in chapters is not a row number — kept as a side reference (topic: ${spelled.rest})`);
|
|
59
|
+
const locator = typeof chapters === 'number' ? String(chapters)
|
|
60
|
+
: spelled !== undefined ? spelled.locator
|
|
61
|
+
: Array.isArray(chapters) ? chapters.filter(entry => typeof entry === 'number' || typeof entry === 'string').join(',')
|
|
62
|
+
: Array.isArray(chapterRanges) ? formatLocator(chapterRanges.flatMap(range => {
|
|
63
|
+
const row = payload(range);
|
|
64
|
+
return typeof row?.from === 'number' && typeof row.to === 'number' ? [{ from: row.from, to: row.to }] : [];
|
|
65
|
+
})) : undefined;
|
|
66
|
+
if (locator !== undefined && locator !== '')
|
|
67
|
+
main.push({ ref: resourceId, locator });
|
|
68
|
+
const side = [...(Array.isArray(covers) ? covers : []), ...(Array.isArray(also) ? also : typeof also === 'string' ? [also] : []), ...(spelled?.rest === undefined ? [] : [`topic: ${spelled.rest}`])]
|
|
69
|
+
.map(cover => normalizeCoverArg(cover) ?? cover);
|
|
70
|
+
// A station naming neither rows nor references rests on the textbook whole.
|
|
71
|
+
const all = main.length === 0 && side.length === 0 ? [{ ref: resourceId }] : [...main, ...side];
|
|
72
|
+
return { ...rest, ...(gateValue === undefined ? {} : { gate: gateValue }), ...(keepPoints && points !== undefined ? { points } : {}), covers: all };
|
|
73
|
+
});
|
|
74
|
+
// Every station is checked by its chapter quiz, and the quiz asks its points.
|
|
75
|
+
if (missingPoints.length > 0) {
|
|
76
|
+
throw new Error(`every station needs its 3–5 knowledge points (points: [{ id, title }]) — the chapter quiz asks them: ${missingPoints.join(', ')}`);
|
|
77
|
+
}
|
|
78
|
+
const projectId = projectOf(argsAny.project, exec, resourceId);
|
|
79
|
+
const before = unwrap(await service.getProject({ id: projectId }));
|
|
80
|
+
const replaced = before.project.resources.find(row => row.id === resourceId)?.hasDraft === true;
|
|
81
|
+
const drafted = unwrap(await service.draftRoadmap({
|
|
82
|
+
projectId,
|
|
83
|
+
resourceId,
|
|
84
|
+
nodes: nodes,
|
|
85
|
+
...(typeof argsAny.passRatio === 'number' ? { passRatio: argsAny.passRatio } : {}),
|
|
86
|
+
...(typeof args.note === 'string' ? { note: args.note } : {}),
|
|
87
|
+
}));
|
|
88
|
+
await autoBind(exec, { projectId, resourceId });
|
|
89
|
+
const after = unwrap(await service.getProject({ id: projectId }));
|
|
90
|
+
const textbook = after.project.resources.find(row => row.id === resourceId);
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
placedOn: `${textbook?.seq ?? '-'}. 《${textbook?.title ?? resourceId}》 (${resourceId})`,
|
|
94
|
+
stations: drafted.draft.nodes.length,
|
|
95
|
+
...(replaced ? { replacedDraft: true } : {}),
|
|
96
|
+
waitingForAdoption: after.project.resources.filter(row => row.hasDraft).map(row => `${row.seq ?? '-'}. 《${row.title}》 ${row.draftTotal ?? 0} stations`),
|
|
97
|
+
...(adjusted.length === 0 ? {} : { adjusted }),
|
|
98
|
+
next: 'Describe the path to the learner in two lines and ask them to review and adopt it in the sidebar (采用). Then stop: before they adopt it, write no 讲义, teach nothing, set no quiz.',
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|