reflex-agent 0.2.4 → 0.3.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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +111 -98
- package/.next/app-path-routes-manifest.json +9 -9
- package/.next/build-manifest.json +5 -5
- package/.next/prerender-manifest.json +4 -54
- package/.next/react-loadable-manifest.json +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/agents/[agentId]/page.js +3 -3
- package/.next/server/app/agents/[agentId]/page.js.nft.json +1 -1
- package/.next/server/app/agents/[agentId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agents/[agentId]/respond/route.js +2 -2
- package/.next/server/app/api/agents/[agentId]/respond/route.js.nft.json +1 -1
- package/.next/server/app/api/agents/[agentId]/respond/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/images/[rootId]/[file]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/oauth/callback/route.js +3 -3
- package/.next/server/app/api/oauth/callback/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/oauth/start/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/roots/[id]/attachments/route.js +0 -0
- package/.next/server/app/api/roots/[id]/attachments/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/roots/[id]/chat/[topicId]/send/route.js +2 -2
- package/.next/server/app/api/roots/[id]/chat/[topicId]/send/route.js.nft.json +1 -1
- package/.next/server/app/api/roots/[id]/chat/[topicId]/send/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/roots/[id]/chat/[topicId]/stop/route.js +2 -2
- package/.next/server/app/api/roots/[id]/chat/[topicId]/stop/route.js.nft.json +1 -1
- package/.next/server/app/api/roots/[id]/chat/[topicId]/stop/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/roots/[id]/chat/[topicId]/stream/route.js +2 -2
- package/.next/server/app/api/roots/[id]/chat/[topicId]/stream/route.js.nft.json +1 -1
- package/.next/server/app/api/roots/[id]/chat/[topicId]/stream/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/roots/[id]/dashboard/route.js +1 -1
- package/.next/server/app/api/roots/[id]/dashboard/route.js.nft.json +1 -1
- package/.next/server/app/api/roots/[id]/dashboard/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/roots/[id]/suggestions/route.js +1 -1
- package/.next/server/app/api/roots/[id]/suggestions/route.js.nft.json +1 -1
- package/.next/server/app/api/roots/[id]/suggestions/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/utilities/[scope]/[id]/bundle.js/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/utilities/[scope]/[id]/host/route.js +2 -2
- package/.next/server/app/api/utilities/[scope]/[id]/host/route.js.nft.json +1 -1
- package/.next/server/app/api/utilities/[scope]/[id]/host/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/utilities/[scope]/[id]/host-api.mjs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/utilities/[scope]/[id]/host-ui.mjs/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/utilities/[scope]/[id]/iframe/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/utilities/[scope]/[id]/style.css/route_client-reference-manifest.js +1 -1
- package/.next/server/app/audit/page.js +2 -2
- package/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/server/app/onboarding/page.js +4 -4
- package/.next/server/app/onboarding/page.js.nft.json +1 -1
- package/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page.js +2 -2
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/roots/[id]/chat/[topicId]/page.js +2 -6
- package/.next/server/app/roots/[id]/chat/[topicId]/page.js.nft.json +1 -1
- package/.next/server/app/roots/[id]/chat/[topicId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/roots/[id]/kb/[...slug]/page.js +2 -6
- package/.next/server/app/roots/[id]/kb/[...slug]/page.js.nft.json +1 -1
- package/.next/server/app/roots/[id]/kb/[...slug]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/roots/[id]/page.js +3 -3
- package/.next/server/app/roots/[id]/page.js.nft.json +1 -1
- package/.next/server/app/roots/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/roots/[id]/workflows/[wfId]/page.js +2 -2
- package/.next/server/app/roots/[id]/workflows/[wfId]/page.js.nft.json +1 -1
- package/.next/server/app/roots/[id]/workflows/[wfId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/roots/[id]/workflows/page.js +2 -2
- package/.next/server/app/roots/[id]/workflows/page.js.nft.json +1 -1
- package/.next/server/app/roots/[id]/workflows/page_client-reference-manifest.js +1 -1
- package/.next/server/app/roots/new/page.js +4 -2
- package/.next/server/app/roots/new/page.js.nft.json +1 -1
- package/.next/server/app/roots/new/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/page.js +6 -6
- package/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/share/[id]/file/page.js +2 -2
- package/.next/server/app/share/[id]/file/page.js.nft.json +1 -1
- package/.next/server/app/share/[id]/file/page_client-reference-manifest.js +1 -1
- package/.next/server/app/share/[id]/page.js +2 -2
- package/.next/server/app/share/[id]/page.js.nft.json +1 -1
- package/.next/server/app/share/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/utilities/[scope]/[id]/page.js +2 -2
- package/.next/server/app/utilities/[scope]/[id]/page.js.nft.json +1 -1
- package/.next/server/app/utilities/[scope]/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/utilities/page.js +2 -17
- package/.next/server/app/utilities/page.js.nft.json +1 -1
- package/.next/server/app/utilities/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +9 -9
- package/.next/server/chunks/1223.js +1 -1
- package/.next/server/chunks/133.js +1 -490
- package/.next/server/chunks/1888.js +1 -1
- package/.next/server/chunks/{9739.js → 1988.js} +13 -9
- package/.next/server/chunks/2433.js +1 -1
- package/.next/server/chunks/2503.js +1 -1
- package/.next/server/chunks/285.js +471 -0
- package/.next/server/chunks/2959.js +1 -0
- package/.next/server/chunks/2995.js +1 -0
- package/.next/server/chunks/3240.js +1 -1
- package/.next/server/chunks/3332.js +1 -1
- package/.next/server/chunks/3657.js +1 -1
- package/.next/server/chunks/4066.js +1 -1
- package/.next/server/chunks/4438.js +1 -0
- package/.next/server/chunks/4514.js +3 -0
- package/.next/server/chunks/4553.js +1 -1
- package/.next/server/chunks/4812.js +179 -0
- package/.next/server/chunks/4925.js +1 -1
- package/.next/server/chunks/{3953.js → 5068.js} +2 -2
- package/.next/server/chunks/5319.js +1 -1
- package/.next/server/chunks/569.js +1 -1
- package/.next/server/chunks/6730.js +1 -1
- package/.next/server/chunks/6909.js +142 -161
- package/.next/server/chunks/8262.js +1 -1
- package/.next/server/chunks/9098.js +1 -1
- package/.next/server/chunks/94.js +1 -1
- package/.next/server/chunks/9427.js +1 -0
- package/.next/server/chunks/9538.js +1 -0
- package/.next/server/chunks/963.js +1 -0
- package/.next/server/chunks/9835.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages-manifest.json +1 -2
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/2865-134f546f21ca4330.js +1 -0
- package/.next/static/chunks/4108.5abdb7812a13eafd.js +1 -0
- package/.next/static/chunks/5254-4196f25e56270de5.js +1 -0
- package/.next/static/chunks/5521-cbc665104c7e59d3.js +1 -0
- package/.next/static/chunks/6445-99824866a51b582a.js +1 -0
- package/.next/static/chunks/8855-9b941d2b78f398ce.js +1 -0
- package/.next/static/chunks/8871-2948840b33c0863d.js +1 -0
- package/.next/static/chunks/9411-af5f758c57741929.js +3 -0
- package/.next/static/chunks/app/agents/[agentId]/page-5d6f4cb16b42d02b.js +1 -0
- package/.next/static/chunks/app/layout-d4cf24375db6d793.js +1 -0
- package/.next/static/chunks/app/onboarding/page-7303664b62ccc24a.js +1 -0
- package/.next/static/chunks/app/page-97d312db91d569f7.js +1 -0
- package/.next/static/chunks/app/roots/[id]/chat/[topicId]/page-123f60a544619a3c.js +1 -0
- package/.next/static/chunks/app/roots/[id]/kb/[...slug]/page-e253597edb1b2440.js +1 -0
- package/.next/static/chunks/app/roots/[id]/page-91a8de6a1c79f8a3.js +1 -0
- package/.next/static/chunks/app/roots/[id]/workflows/[wfId]/page-4300a52e163883df.js +1 -0
- package/.next/static/chunks/app/roots/new/page-6b104aad46a38173.js +1 -0
- package/.next/static/chunks/app/settings/page-afe1b80f7f45c5eb.js +1 -0
- package/.next/static/chunks/app/share/[id]/page-a5fb565bd892d4df.js +1 -0
- package/.next/static/chunks/app/utilities/[scope]/[id]/page-eb713a2b5209942c.js +1 -0
- package/.next/static/chunks/app/utilities/page-b7f30c151c42a27c.js +1 -0
- package/.next/static/chunks/{webpack-5fca180586957874.js → webpack-bddc3babcbc30dd7.js} +1 -1
- package/.next/static/css/60e9b6cdf1283e83.css +1 -0
- package/.next/trace +47 -46
- package/dist/lib/reflex/agents/prompts.js +46 -46
- package/dist/lib/reflex/agents/prompts.js.map +1 -1
- package/dist/lib/reflex/prompts/defaults.js +102 -102
- package/next.config.ts +4 -1
- package/package.json +2 -2
- package/.next/server/app/_not-found.html +0 -1
- package/.next/server/app/_not-found.meta +0 -8
- package/.next/server/app/_not-found.rsc +0 -18
- package/.next/server/app/index.html +0 -1
- package/.next/server/app/index.meta +0 -9
- package/.next/server/app/index.rsc +0 -19
- package/.next/server/chunks/1410.js +0 -1
- package/.next/server/chunks/1986.js +0 -1
- package/.next/server/chunks/2448.js +0 -3
- package/.next/server/chunks/5754.js +0 -3
- package/.next/server/chunks/7097.js +0 -1
- package/.next/server/chunks/7782.js +0 -1
- package/.next/server/chunks/7987.js +0 -1
- package/.next/server/chunks/810.js +0 -1
- package/.next/server/chunks/8843.js +0 -1
- package/.next/server/chunks/9328.js +0 -179
- package/.next/server/pages/404.html +0 -1
- package/.next/static/chunks/2488-c9590facb4b9f184.js +0 -1
- package/.next/static/chunks/2684-257d38989ef53935.js +0 -1
- package/.next/static/chunks/4108.fb9f99a9c899ef54.js +0 -1
- package/.next/static/chunks/6231-d83c1544bbea8424.js +0 -1
- package/.next/static/chunks/9045-731ff0865352dd95.js +0 -1
- package/.next/static/chunks/9496-75ccd3fadb294fba.js +0 -1
- package/.next/static/chunks/992-4e7b7f722c629e21.js +0 -1
- package/.next/static/chunks/app/agents/[agentId]/page-0b5c2838354d0eba.js +0 -1
- package/.next/static/chunks/app/layout-9a59ed07c18cb786.js +0 -1
- package/.next/static/chunks/app/onboarding/page-79f07a813ea2abfe.js +0 -1
- package/.next/static/chunks/app/page-27f4b98b02ac4f79.js +0 -1
- package/.next/static/chunks/app/roots/[id]/chat/[topicId]/page-8db2d0b75cd333c8.js +0 -1
- package/.next/static/chunks/app/roots/[id]/kb/[...slug]/page-873b131eec3a2f30.js +0 -1
- package/.next/static/chunks/app/roots/[id]/page-270d0d49eb668784.js +0 -1
- package/.next/static/chunks/app/roots/[id]/workflows/[wfId]/page-7c1f10dbe0bcb9ad.js +0 -1
- package/.next/static/chunks/app/roots/new/page-ac1a9f6379710ca2.js +0 -1
- package/.next/static/chunks/app/settings/page-81cb1393e817dfc3.js +0 -1
- package/.next/static/chunks/app/share/[id]/page-2d123f0a99e1606f.js +0 -1
- package/.next/static/chunks/app/utilities/[scope]/[id]/page-0bbb8d17af80c1da.js +0 -1
- package/.next/static/chunks/app/utilities/page-e6ce673b9357bf1f.js +0 -1
- package/.next/static/css/87e01f779d555d04.css +0 -1
- package/packages/utilities/learn-anything/README.md +0 -41
- package/packages/utilities/learn-anything/actions/_json.ts +0 -191
- package/packages/utilities/learn-anything/actions/_store.ts +0 -248
- package/packages/utilities/learn-anything/actions/buildModule.ts +0 -487
- package/packages/utilities/learn-anything/actions/explainSelection.ts +0 -64
- package/packages/utilities/learn-anything/actions/generateOutline.ts +0 -170
- package/packages/utilities/learn-anything/actions/generateQuiz.ts +0 -72
- package/packages/utilities/learn-anything/actions/generateTrainer.ts +0 -106
- package/packages/utilities/learn-anything/actions/refreshCourseCard.ts +0 -76
- package/packages/utilities/learn-anything/actions/tutorAsk.ts +0 -93
- package/packages/utilities/learn-anything/article-view.tsx +0 -464
- package/packages/utilities/learn-anything/manifest.json +0 -42
- package/packages/utilities/learn-anything/ui.tsx +0 -1589
- /package/.next/static/{og_wC7UPkGtJDiapaTgBr → IGuuMcet1qtGZQCP2MEn4}/_buildManifest.js +0 -0
- /package/.next/static/{og_wC7UPkGtJDiapaTgBr → IGuuMcet1qtGZQCP2MEn4}/_ssgManifest.js +0 -0
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { reflex } from "@host/api";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Shared course state store. Lives in the utility's `data/` sandbox
|
|
5
|
-
* (one JSON file per course at `courses/<courseId>.json`) — bypasses
|
|
6
|
-
* the frontmatter round-tripping problem (long JSON strings → multi-line
|
|
7
|
-
* YAML → our naive client-side parser drops them). The companion KB
|
|
8
|
-
* entry of `kind:"course"` stays for human visibility / provenance
|
|
9
|
-
* badges, but it's NOT the source of truth.
|
|
10
|
-
*
|
|
11
|
-
* The body markdown rendered into the KB entry is regenerated from the
|
|
12
|
-
* full state on every write — so editing in a KB viewer would be
|
|
13
|
-
* overwritten next save (matches "agent-managed" data model).
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
export interface CourseState {
|
|
17
|
-
courseId: string;
|
|
18
|
-
topic: string;
|
|
19
|
-
modules: Array<{
|
|
20
|
-
id: string;
|
|
21
|
-
title: string;
|
|
22
|
-
objective: string;
|
|
23
|
-
estMinutes: number;
|
|
24
|
-
}>;
|
|
25
|
-
progress: Record<string, { completed?: boolean; quizScore?: number }>;
|
|
26
|
-
wizardAnswers: Array<{ question: string; answer: string }>;
|
|
27
|
-
createdAt: string;
|
|
28
|
-
/** Bumped on every write to drive list-view re-renders. */
|
|
29
|
-
updatedAt: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const DIR = "courses";
|
|
33
|
-
|
|
34
|
-
export async function writeCourse(state: CourseState): Promise<void> {
|
|
35
|
-
const path = `${DIR}/${state.courseId}.json`;
|
|
36
|
-
await reflex.fs.write({
|
|
37
|
-
path,
|
|
38
|
-
content: JSON.stringify(state, null, 2),
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function readCourse(courseId: string): Promise<CourseState | null> {
|
|
43
|
-
try {
|
|
44
|
-
const { content } = await reflex.fs.read({
|
|
45
|
-
path: `${DIR}/${courseId}.json`,
|
|
46
|
-
});
|
|
47
|
-
return JSON.parse(content) as CourseState;
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function listCourses(): Promise<CourseState[]> {
|
|
54
|
-
const out: CourseState[] = [];
|
|
55
|
-
try {
|
|
56
|
-
const { entries } = await reflex.fs.list({ path: DIR });
|
|
57
|
-
for (const e of entries) {
|
|
58
|
-
if (e.isDir) continue;
|
|
59
|
-
if (!e.name.endsWith(".json")) continue;
|
|
60
|
-
const id = e.name.slice(0, -5);
|
|
61
|
-
const c = await readCourse(id);
|
|
62
|
-
if (c) out.push(c);
|
|
63
|
-
}
|
|
64
|
-
} catch {
|
|
65
|
-
/* dir not created yet → empty list */
|
|
66
|
-
}
|
|
67
|
-
// Migration fallback: pre-0.2 versions stored course state in KB
|
|
68
|
-
// frontmatter (which broke on long multi-line YAML). Pick up anything
|
|
69
|
-
// KB has that fs doesn't, hydrate from there, and persist to fs so
|
|
70
|
-
// the next open is fast and complete.
|
|
71
|
-
try {
|
|
72
|
-
const seen = new Set(out.map((c) => c.courseId));
|
|
73
|
-
const kb = (await reflex.kb.list({ kind: "course" })) ?? [];
|
|
74
|
-
for (const k of kb) {
|
|
75
|
-
try {
|
|
76
|
-
const { content } = await reflex.kb.read({ relPath: k.relPath });
|
|
77
|
-
const fm = parseFrontmatter(content);
|
|
78
|
-
const courseId = stringField(fm, "courseId") || baseSlug(k.relPath);
|
|
79
|
-
if (!courseId || seen.has(courseId)) continue;
|
|
80
|
-
const topic =
|
|
81
|
-
stringField(fm, "topic") || k.title || courseId;
|
|
82
|
-
const modulesFm = fm?.modules;
|
|
83
|
-
// Best-effort: parse JSON-string frontmatter OR rebuild a stub
|
|
84
|
-
// outline from the body's "1. **Title** — objective" lines.
|
|
85
|
-
const modules =
|
|
86
|
-
parseModulesField(modulesFm) ?? parseModulesFromBody(content);
|
|
87
|
-
if (modules.length === 0) continue;
|
|
88
|
-
const progress =
|
|
89
|
-
parseProgressField(fm?.progress) ?? {};
|
|
90
|
-
const wizardAnswers =
|
|
91
|
-
parseWizardField(fm?.wizardAnswers) ?? [];
|
|
92
|
-
const createdAt =
|
|
93
|
-
stringField(fm, "createdAt") || k.modifiedAt || new Date().toISOString();
|
|
94
|
-
const migrated: CourseState = {
|
|
95
|
-
courseId,
|
|
96
|
-
topic,
|
|
97
|
-
modules,
|
|
98
|
-
progress,
|
|
99
|
-
wizardAnswers,
|
|
100
|
-
createdAt,
|
|
101
|
-
updatedAt: new Date().toISOString(),
|
|
102
|
-
};
|
|
103
|
-
out.push(migrated);
|
|
104
|
-
seen.add(courseId);
|
|
105
|
-
// Persist so we don't pay the KB scan next time.
|
|
106
|
-
await writeCourse(migrated);
|
|
107
|
-
} catch {
|
|
108
|
-
/* skip malformed entry */
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
} catch {
|
|
112
|
-
/* kb.list unavailable — fine, just no migration */
|
|
113
|
-
}
|
|
114
|
-
return out;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// KB migration helpers — only used by the fallback above.
|
|
119
|
-
|
|
120
|
-
function parseFrontmatter(raw: string): Record<string, unknown> | null {
|
|
121
|
-
const m = /^---\n([\s\S]*?)\n---/.exec(raw);
|
|
122
|
-
if (!m) return null;
|
|
123
|
-
const out: Record<string, unknown> = {};
|
|
124
|
-
// Crude but tolerant: collect multi-line values when indented or
|
|
125
|
-
// explicitly quoted with single quotes. Sufficient for old courses
|
|
126
|
-
// that wrapped a long JSON string under a single key.
|
|
127
|
-
const lines = m[1]!.split("\n");
|
|
128
|
-
let curKey: string | null = null;
|
|
129
|
-
let curBuf = "";
|
|
130
|
-
const flush = () => {
|
|
131
|
-
if (curKey == null) return;
|
|
132
|
-
let v = curBuf.trim();
|
|
133
|
-
// Strip outer quotes if present.
|
|
134
|
-
v = v.replace(/^['"]|['"]$/g, "");
|
|
135
|
-
out[curKey] = v;
|
|
136
|
-
curKey = null;
|
|
137
|
-
curBuf = "";
|
|
138
|
-
};
|
|
139
|
-
for (const line of lines) {
|
|
140
|
-
if (/^\S/.test(line) && line.includes(":")) {
|
|
141
|
-
flush();
|
|
142
|
-
const i = line.indexOf(":");
|
|
143
|
-
curKey = line.slice(0, i).trim();
|
|
144
|
-
curBuf = line.slice(i + 1);
|
|
145
|
-
} else if (curKey != null) {
|
|
146
|
-
curBuf += "\n" + line;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
flush();
|
|
150
|
-
return out;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function stringField(fm: Record<string, unknown> | null, key: string): string {
|
|
154
|
-
return fm && typeof fm[key] === "string" ? (fm[key] as string) : "";
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function parseModulesField(v: unknown): CourseState["modules"] | null {
|
|
158
|
-
if (typeof v !== "string") return null;
|
|
159
|
-
try {
|
|
160
|
-
const parsed = JSON.parse(v);
|
|
161
|
-
if (!Array.isArray(parsed)) return null;
|
|
162
|
-
return parsed
|
|
163
|
-
.filter(
|
|
164
|
-
(x): x is { id: string; title: string; objective?: string } =>
|
|
165
|
-
x &&
|
|
166
|
-
typeof x === "object" &&
|
|
167
|
-
typeof (x as { id?: unknown }).id === "string",
|
|
168
|
-
)
|
|
169
|
-
.map((x) => ({
|
|
170
|
-
id: x.id,
|
|
171
|
-
title:
|
|
172
|
-
typeof (x as { title?: unknown }).title === "string"
|
|
173
|
-
? (x as { title: string }).title
|
|
174
|
-
: x.id,
|
|
175
|
-
objective:
|
|
176
|
-
typeof (x as { objective?: unknown }).objective === "string"
|
|
177
|
-
? (x as { objective: string }).objective
|
|
178
|
-
: "",
|
|
179
|
-
estMinutes:
|
|
180
|
-
typeof (x as { estMinutes?: unknown }).estMinutes === "number"
|
|
181
|
-
? (x as { estMinutes: number }).estMinutes
|
|
182
|
-
: 30,
|
|
183
|
-
}));
|
|
184
|
-
} catch {
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function parseProgressField(v: unknown): CourseState["progress"] | null {
|
|
190
|
-
if (typeof v !== "string") return null;
|
|
191
|
-
try {
|
|
192
|
-
const p = JSON.parse(v);
|
|
193
|
-
return p && typeof p === "object" ? (p as CourseState["progress"]) : null;
|
|
194
|
-
} catch {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function parseWizardField(v: unknown): CourseState["wizardAnswers"] | null {
|
|
200
|
-
if (typeof v !== "string") return null;
|
|
201
|
-
try {
|
|
202
|
-
const p = JSON.parse(v);
|
|
203
|
-
return Array.isArray(p) ? (p as CourseState["wizardAnswers"]) : null;
|
|
204
|
-
} catch {
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function parseModulesFromBody(raw: string): CourseState["modules"] {
|
|
210
|
-
// Body shape from old generateOutline: "1. **Title** — objective (~N мин)"
|
|
211
|
-
const out: CourseState["modules"] = [];
|
|
212
|
-
const re = /^\s*(\d+)\.\s+\*\*(.+?)\*\*\s*[—-]\s*(.*?)(?:\s*\(~?(\d+)\s*мин\))?$/gm;
|
|
213
|
-
let m: RegExpExecArray | null;
|
|
214
|
-
while ((m = re.exec(raw))) {
|
|
215
|
-
out.push({
|
|
216
|
-
id: slug(m[2]!) || `m${m[1]}`,
|
|
217
|
-
title: m[2]!,
|
|
218
|
-
objective: m[3] ?? "",
|
|
219
|
-
estMinutes: m[4] ? Number(m[4]) : 30,
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
return out;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function slug(s: string): string {
|
|
226
|
-
return s
|
|
227
|
-
.normalize("NFKD")
|
|
228
|
-
.toLowerCase()
|
|
229
|
-
.replace(/[^\p{L}\p{N}]+/gu, "-")
|
|
230
|
-
.replace(/^-+|-+$/g, "")
|
|
231
|
-
.slice(0, 60);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function baseSlug(rel: string): string {
|
|
235
|
-
return (rel.split("/").pop() ?? rel)
|
|
236
|
-
.replace(/\.md$/, "")
|
|
237
|
-
.replace(/^\d{4}-\d{2}-\d{2}-/, "");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export async function deleteCourse(courseId: string): Promise<void> {
|
|
241
|
-
// fs.delete isn't exposed yet; overwrite with empty marker for now.
|
|
242
|
-
// Listing skips entries that fail to parse so this effectively hides
|
|
243
|
-
// the course until a proper delete RPC ships.
|
|
244
|
-
await reflex.fs.write({
|
|
245
|
-
path: `${DIR}/${courseId}.json`,
|
|
246
|
-
content: "// deleted",
|
|
247
|
-
});
|
|
248
|
-
}
|