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.
Files changed (206) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +111 -98
  3. package/.next/app-path-routes-manifest.json +9 -9
  4. package/.next/build-manifest.json +5 -5
  5. package/.next/prerender-manifest.json +4 -54
  6. package/.next/react-loadable-manifest.json +1 -1
  7. package/.next/server/app/_not-found/page.js +1 -1
  8. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  9. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  10. package/.next/server/app/agents/[agentId]/page.js +3 -3
  11. package/.next/server/app/agents/[agentId]/page.js.nft.json +1 -1
  12. package/.next/server/app/agents/[agentId]/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/api/agents/[agentId]/respond/route.js +2 -2
  14. package/.next/server/app/api/agents/[agentId]/respond/route.js.nft.json +1 -1
  15. package/.next/server/app/api/agents/[agentId]/respond/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/images/[rootId]/[file]/route_client-reference-manifest.js +1 -1
  17. package/.next/server/app/api/oauth/callback/route.js +3 -3
  18. package/.next/server/app/api/oauth/callback/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/oauth/start/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/roots/[id]/attachments/route.js +0 -0
  21. package/.next/server/app/api/roots/[id]/attachments/route_client-reference-manifest.js +1 -1
  22. package/.next/server/app/api/roots/[id]/chat/[topicId]/send/route.js +2 -2
  23. package/.next/server/app/api/roots/[id]/chat/[topicId]/send/route.js.nft.json +1 -1
  24. package/.next/server/app/api/roots/[id]/chat/[topicId]/send/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/roots/[id]/chat/[topicId]/stop/route.js +2 -2
  26. package/.next/server/app/api/roots/[id]/chat/[topicId]/stop/route.js.nft.json +1 -1
  27. package/.next/server/app/api/roots/[id]/chat/[topicId]/stop/route_client-reference-manifest.js +1 -1
  28. package/.next/server/app/api/roots/[id]/chat/[topicId]/stream/route.js +2 -2
  29. package/.next/server/app/api/roots/[id]/chat/[topicId]/stream/route.js.nft.json +1 -1
  30. package/.next/server/app/api/roots/[id]/chat/[topicId]/stream/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/roots/[id]/dashboard/route.js +1 -1
  32. package/.next/server/app/api/roots/[id]/dashboard/route.js.nft.json +1 -1
  33. package/.next/server/app/api/roots/[id]/dashboard/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/roots/[id]/suggestions/route.js +1 -1
  35. package/.next/server/app/api/roots/[id]/suggestions/route.js.nft.json +1 -1
  36. package/.next/server/app/api/roots/[id]/suggestions/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/utilities/[scope]/[id]/bundle.js/route_client-reference-manifest.js +1 -1
  38. package/.next/server/app/api/utilities/[scope]/[id]/host/route.js +2 -2
  39. package/.next/server/app/api/utilities/[scope]/[id]/host/route.js.nft.json +1 -1
  40. package/.next/server/app/api/utilities/[scope]/[id]/host/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/utilities/[scope]/[id]/host-api.mjs/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/utilities/[scope]/[id]/host-ui.mjs/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/utilities/[scope]/[id]/iframe/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/utilities/[scope]/[id]/style.css/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/audit/page.js +2 -2
  46. package/.next/server/app/audit/page.js.nft.json +1 -1
  47. package/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/onboarding/page.js +4 -4
  49. package/.next/server/app/onboarding/page.js.nft.json +1 -1
  50. package/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  51. package/.next/server/app/page.js +2 -2
  52. package/.next/server/app/page.js.nft.json +1 -1
  53. package/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/.next/server/app/roots/[id]/chat/[topicId]/page.js +2 -6
  55. package/.next/server/app/roots/[id]/chat/[topicId]/page.js.nft.json +1 -1
  56. package/.next/server/app/roots/[id]/chat/[topicId]/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/roots/[id]/kb/[...slug]/page.js +2 -6
  58. package/.next/server/app/roots/[id]/kb/[...slug]/page.js.nft.json +1 -1
  59. package/.next/server/app/roots/[id]/kb/[...slug]/page_client-reference-manifest.js +1 -1
  60. package/.next/server/app/roots/[id]/page.js +3 -3
  61. package/.next/server/app/roots/[id]/page.js.nft.json +1 -1
  62. package/.next/server/app/roots/[id]/page_client-reference-manifest.js +1 -1
  63. package/.next/server/app/roots/[id]/workflows/[wfId]/page.js +2 -2
  64. package/.next/server/app/roots/[id]/workflows/[wfId]/page.js.nft.json +1 -1
  65. package/.next/server/app/roots/[id]/workflows/[wfId]/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/roots/[id]/workflows/page.js +2 -2
  67. package/.next/server/app/roots/[id]/workflows/page.js.nft.json +1 -1
  68. package/.next/server/app/roots/[id]/workflows/page_client-reference-manifest.js +1 -1
  69. package/.next/server/app/roots/new/page.js +4 -2
  70. package/.next/server/app/roots/new/page.js.nft.json +1 -1
  71. package/.next/server/app/roots/new/page_client-reference-manifest.js +1 -1
  72. package/.next/server/app/settings/page.js +6 -6
  73. package/.next/server/app/settings/page.js.nft.json +1 -1
  74. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/share/[id]/file/page.js +2 -2
  76. package/.next/server/app/share/[id]/file/page.js.nft.json +1 -1
  77. package/.next/server/app/share/[id]/file/page_client-reference-manifest.js +1 -1
  78. package/.next/server/app/share/[id]/page.js +2 -2
  79. package/.next/server/app/share/[id]/page.js.nft.json +1 -1
  80. package/.next/server/app/share/[id]/page_client-reference-manifest.js +1 -1
  81. package/.next/server/app/utilities/[scope]/[id]/page.js +2 -2
  82. package/.next/server/app/utilities/[scope]/[id]/page.js.nft.json +1 -1
  83. package/.next/server/app/utilities/[scope]/[id]/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/utilities/page.js +2 -17
  85. package/.next/server/app/utilities/page.js.nft.json +1 -1
  86. package/.next/server/app/utilities/page_client-reference-manifest.js +1 -1
  87. package/.next/server/app-paths-manifest.json +9 -9
  88. package/.next/server/chunks/1223.js +1 -1
  89. package/.next/server/chunks/133.js +1 -490
  90. package/.next/server/chunks/1888.js +1 -1
  91. package/.next/server/chunks/{9739.js → 1988.js} +13 -9
  92. package/.next/server/chunks/2433.js +1 -1
  93. package/.next/server/chunks/2503.js +1 -1
  94. package/.next/server/chunks/285.js +471 -0
  95. package/.next/server/chunks/2959.js +1 -0
  96. package/.next/server/chunks/2995.js +1 -0
  97. package/.next/server/chunks/3240.js +1 -1
  98. package/.next/server/chunks/3332.js +1 -1
  99. package/.next/server/chunks/3657.js +1 -1
  100. package/.next/server/chunks/4066.js +1 -1
  101. package/.next/server/chunks/4438.js +1 -0
  102. package/.next/server/chunks/4514.js +3 -0
  103. package/.next/server/chunks/4553.js +1 -1
  104. package/.next/server/chunks/4812.js +179 -0
  105. package/.next/server/chunks/4925.js +1 -1
  106. package/.next/server/chunks/{3953.js → 5068.js} +2 -2
  107. package/.next/server/chunks/5319.js +1 -1
  108. package/.next/server/chunks/569.js +1 -1
  109. package/.next/server/chunks/6730.js +1 -1
  110. package/.next/server/chunks/6909.js +142 -161
  111. package/.next/server/chunks/8262.js +1 -1
  112. package/.next/server/chunks/9098.js +1 -1
  113. package/.next/server/chunks/94.js +1 -1
  114. package/.next/server/chunks/9427.js +1 -0
  115. package/.next/server/chunks/9538.js +1 -0
  116. package/.next/server/chunks/963.js +1 -0
  117. package/.next/server/chunks/9835.js +1 -1
  118. package/.next/server/middleware-build-manifest.js +1 -1
  119. package/.next/server/middleware-manifest.json +5 -5
  120. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  121. package/.next/server/pages/500.html +1 -1
  122. package/.next/server/pages-manifest.json +1 -2
  123. package/.next/server/server-reference-manifest.js +1 -1
  124. package/.next/server/server-reference-manifest.json +1 -1
  125. package/.next/static/chunks/2865-134f546f21ca4330.js +1 -0
  126. package/.next/static/chunks/4108.5abdb7812a13eafd.js +1 -0
  127. package/.next/static/chunks/5254-4196f25e56270de5.js +1 -0
  128. package/.next/static/chunks/5521-cbc665104c7e59d3.js +1 -0
  129. package/.next/static/chunks/6445-99824866a51b582a.js +1 -0
  130. package/.next/static/chunks/8855-9b941d2b78f398ce.js +1 -0
  131. package/.next/static/chunks/8871-2948840b33c0863d.js +1 -0
  132. package/.next/static/chunks/9411-af5f758c57741929.js +3 -0
  133. package/.next/static/chunks/app/agents/[agentId]/page-5d6f4cb16b42d02b.js +1 -0
  134. package/.next/static/chunks/app/layout-d4cf24375db6d793.js +1 -0
  135. package/.next/static/chunks/app/onboarding/page-7303664b62ccc24a.js +1 -0
  136. package/.next/static/chunks/app/page-97d312db91d569f7.js +1 -0
  137. package/.next/static/chunks/app/roots/[id]/chat/[topicId]/page-123f60a544619a3c.js +1 -0
  138. package/.next/static/chunks/app/roots/[id]/kb/[...slug]/page-e253597edb1b2440.js +1 -0
  139. package/.next/static/chunks/app/roots/[id]/page-91a8de6a1c79f8a3.js +1 -0
  140. package/.next/static/chunks/app/roots/[id]/workflows/[wfId]/page-4300a52e163883df.js +1 -0
  141. package/.next/static/chunks/app/roots/new/page-6b104aad46a38173.js +1 -0
  142. package/.next/static/chunks/app/settings/page-afe1b80f7f45c5eb.js +1 -0
  143. package/.next/static/chunks/app/share/[id]/page-a5fb565bd892d4df.js +1 -0
  144. package/.next/static/chunks/app/utilities/[scope]/[id]/page-eb713a2b5209942c.js +1 -0
  145. package/.next/static/chunks/app/utilities/page-b7f30c151c42a27c.js +1 -0
  146. package/.next/static/chunks/{webpack-5fca180586957874.js → webpack-bddc3babcbc30dd7.js} +1 -1
  147. package/.next/static/css/60e9b6cdf1283e83.css +1 -0
  148. package/.next/trace +47 -46
  149. package/dist/lib/reflex/agents/prompts.js +46 -46
  150. package/dist/lib/reflex/agents/prompts.js.map +1 -1
  151. package/dist/lib/reflex/prompts/defaults.js +102 -102
  152. package/next.config.ts +4 -1
  153. package/package.json +2 -2
  154. package/.next/server/app/_not-found.html +0 -1
  155. package/.next/server/app/_not-found.meta +0 -8
  156. package/.next/server/app/_not-found.rsc +0 -18
  157. package/.next/server/app/index.html +0 -1
  158. package/.next/server/app/index.meta +0 -9
  159. package/.next/server/app/index.rsc +0 -19
  160. package/.next/server/chunks/1410.js +0 -1
  161. package/.next/server/chunks/1986.js +0 -1
  162. package/.next/server/chunks/2448.js +0 -3
  163. package/.next/server/chunks/5754.js +0 -3
  164. package/.next/server/chunks/7097.js +0 -1
  165. package/.next/server/chunks/7782.js +0 -1
  166. package/.next/server/chunks/7987.js +0 -1
  167. package/.next/server/chunks/810.js +0 -1
  168. package/.next/server/chunks/8843.js +0 -1
  169. package/.next/server/chunks/9328.js +0 -179
  170. package/.next/server/pages/404.html +0 -1
  171. package/.next/static/chunks/2488-c9590facb4b9f184.js +0 -1
  172. package/.next/static/chunks/2684-257d38989ef53935.js +0 -1
  173. package/.next/static/chunks/4108.fb9f99a9c899ef54.js +0 -1
  174. package/.next/static/chunks/6231-d83c1544bbea8424.js +0 -1
  175. package/.next/static/chunks/9045-731ff0865352dd95.js +0 -1
  176. package/.next/static/chunks/9496-75ccd3fadb294fba.js +0 -1
  177. package/.next/static/chunks/992-4e7b7f722c629e21.js +0 -1
  178. package/.next/static/chunks/app/agents/[agentId]/page-0b5c2838354d0eba.js +0 -1
  179. package/.next/static/chunks/app/layout-9a59ed07c18cb786.js +0 -1
  180. package/.next/static/chunks/app/onboarding/page-79f07a813ea2abfe.js +0 -1
  181. package/.next/static/chunks/app/page-27f4b98b02ac4f79.js +0 -1
  182. package/.next/static/chunks/app/roots/[id]/chat/[topicId]/page-8db2d0b75cd333c8.js +0 -1
  183. package/.next/static/chunks/app/roots/[id]/kb/[...slug]/page-873b131eec3a2f30.js +0 -1
  184. package/.next/static/chunks/app/roots/[id]/page-270d0d49eb668784.js +0 -1
  185. package/.next/static/chunks/app/roots/[id]/workflows/[wfId]/page-7c1f10dbe0bcb9ad.js +0 -1
  186. package/.next/static/chunks/app/roots/new/page-ac1a9f6379710ca2.js +0 -1
  187. package/.next/static/chunks/app/settings/page-81cb1393e817dfc3.js +0 -1
  188. package/.next/static/chunks/app/share/[id]/page-2d123f0a99e1606f.js +0 -1
  189. package/.next/static/chunks/app/utilities/[scope]/[id]/page-0bbb8d17af80c1da.js +0 -1
  190. package/.next/static/chunks/app/utilities/page-e6ce673b9357bf1f.js +0 -1
  191. package/.next/static/css/87e01f779d555d04.css +0 -1
  192. package/packages/utilities/learn-anything/README.md +0 -41
  193. package/packages/utilities/learn-anything/actions/_json.ts +0 -191
  194. package/packages/utilities/learn-anything/actions/_store.ts +0 -248
  195. package/packages/utilities/learn-anything/actions/buildModule.ts +0 -487
  196. package/packages/utilities/learn-anything/actions/explainSelection.ts +0 -64
  197. package/packages/utilities/learn-anything/actions/generateOutline.ts +0 -170
  198. package/packages/utilities/learn-anything/actions/generateQuiz.ts +0 -72
  199. package/packages/utilities/learn-anything/actions/generateTrainer.ts +0 -106
  200. package/packages/utilities/learn-anything/actions/refreshCourseCard.ts +0 -76
  201. package/packages/utilities/learn-anything/actions/tutorAsk.ts +0 -93
  202. package/packages/utilities/learn-anything/article-view.tsx +0 -464
  203. package/packages/utilities/learn-anything/manifest.json +0 -42
  204. package/packages/utilities/learn-anything/ui.tsx +0 -1589
  205. /package/.next/static/{og_wC7UPkGtJDiapaTgBr → IGuuMcet1qtGZQCP2MEn4}/_buildManifest.js +0 -0
  206. /package/.next/static/{og_wC7UPkGtJDiapaTgBr → IGuuMcet1qtGZQCP2MEn4}/_ssgManifest.js +0 -0
@@ -1,170 +0,0 @@
1
- import { reflex } from "@host/api";
2
- import type { TutorQA } from "./tutorAsk";
3
- import { callJsonAgent, snippet } from "./_json";
4
- import { writeCourse } from "./_store";
5
-
6
- /**
7
- * Build a course outline based on topic + wizard answers. Returns a
8
- * list of 5-9 modules each with a short title, a 1-sentence objective,
9
- * and an estimated duration. The course record is persisted as a KB
10
- * entry of kind="course"; subsequent buildModule calls hang content
11
- * off it module-by-module.
12
- */
13
-
14
- export interface OutlineModule {
15
- id: string;
16
- title: string;
17
- objective: string;
18
- estMinutes: number;
19
- }
20
-
21
- export interface GenerateOutlineArgs {
22
- topic: string;
23
- history: TutorQA[];
24
- }
25
-
26
- export interface CourseRecord {
27
- courseId: string;
28
- topic: string;
29
- modules: OutlineModule[];
30
- relPath: string;
31
- createdAt: string;
32
- }
33
-
34
- export default async function generateOutline(
35
- args: GenerateOutlineArgs,
36
- ): Promise<CourseRecord> {
37
- const prior = args.history
38
- .map((qa) => `Q: ${qa.question}\nA: ${qa.answer}`)
39
- .join("\n\n");
40
- const prompt = [
41
- "Составь учебный курс по теме «${TOPIC}» под пользователя с такими ответами:".replace(
42
- "${TOPIC}",
43
- args.topic,
44
- ),
45
- "",
46
- prior || "(нет ответов)",
47
- "",
48
- "Требования:",
49
- " • 5-9 модулей, от базовых к продвинутым.",
50
- " • Каждый модуль самодостаточен (можно открыть и пройти за один присест).",
51
- " • objective — одна точная фраза «к концу модуля ты сможешь …».",
52
- " • estMinutes — реалистичная оценка чистого времени учёбы (15-60).",
53
- " • id модуля — kebab-case, латиница+цифры.",
54
- "Верни ТОЛЬКО JSON одной строкой, без markdown:",
55
- ` {"modules":[{"id":"intro","title":"…","objective":"…","estMinutes":30}, ...]}`,
56
- ].join("\n");
57
-
58
- const result = await callJsonAgent<OutlineModule[]>({
59
- prompt,
60
- invoke: (p) => reflex.agent.invoke({ prompt: p, timeoutMs: 4 * 60_000 }),
61
- maxAttempts: 4,
62
- shapeHint:
63
- `{"modules":[{"id":"intro","title":"Введение","objective":"к концу модуля сможешь …","estMinutes":30}, ...]}\n` +
64
- `Минимум 5 модулей. id — kebab-case, без пробелов и кириллицы.`,
65
- validate: (parsed) => {
66
- const v = parsed as { modules?: unknown };
67
- const modules = sanitizeModules(v?.modules);
68
- return modules.length > 0 ? modules : null;
69
- },
70
- });
71
-
72
- if (!result.ok) {
73
- throw new Error(
74
- `Не удалось собрать программу курса за ${result.attempts} попыток (${result.reason}). ` +
75
- `Последний ответ агента: «${snippet(result.lastText, 200)}». Попробуй переформулировать тему.`,
76
- );
77
- }
78
- const modules = result.value;
79
-
80
- const courseId = slugify(args.topic) || `course-${Date.now()}`;
81
- const today = new Date().toISOString().slice(0, 10);
82
- const body = [
83
- `# ${args.topic}`,
84
- "",
85
- "## Программа",
86
- "",
87
- ...modules.map(
88
- (mod, i) =>
89
- `${i + 1}. **${mod.title}** — ${mod.objective} (~${mod.estMinutes} мин)`,
90
- ),
91
- ].join("\n");
92
-
93
- const nowIso = new Date().toISOString();
94
- // Source of truth: sandboxed JSON. Lookup via fs.list — no YAML
95
- // round-tripping headaches.
96
- await writeCourse({
97
- courseId,
98
- topic: args.topic,
99
- modules,
100
- progress: {},
101
- wizardAnswers: args.history,
102
- createdAt: nowIso,
103
- updatedAt: nowIso,
104
- });
105
-
106
- // KB entry is for human visibility (showing up in the KB tree with a
107
- // "createdBy: utility" badge). Short meta only — no JSON-stringified
108
- // blobs, so multi-line YAML never happens.
109
- const saved = await reflex.kb.add({
110
- kind: "course",
111
- title: args.topic,
112
- body,
113
- meta: {
114
- courseId,
115
- topic: args.topic,
116
- modulesCount: modules.length,
117
- createdAt: today,
118
- },
119
- slug: courseId,
120
- date: today,
121
- });
122
-
123
- await reflex.audit.log({
124
- type: "course-outlined",
125
- payload: { topic: args.topic, modulesCount: modules.length },
126
- });
127
-
128
- return {
129
- courseId,
130
- topic: args.topic,
131
- modules,
132
- relPath: saved.relPath,
133
- createdAt: today,
134
- };
135
- }
136
-
137
- function sanitizeModules(raw: unknown): OutlineModule[] {
138
- if (!Array.isArray(raw)) return [];
139
- const seen = new Set<string>();
140
- const out: OutlineModule[] = [];
141
- for (const m of raw) {
142
- if (typeof m !== "object" || m === null) continue;
143
- const o = m as Partial<OutlineModule>;
144
- const id =
145
- typeof o.id === "string" && /^[a-z0-9][a-z0-9-]*$/.test(o.id)
146
- ? o.id
147
- : slugify(typeof o.title === "string" ? o.title : "");
148
- if (!id || seen.has(id)) continue;
149
- seen.add(id);
150
- out.push({
151
- id,
152
- title: typeof o.title === "string" ? o.title : id,
153
- objective: typeof o.objective === "string" ? o.objective : "",
154
- estMinutes:
155
- typeof o.estMinutes === "number" && Number.isFinite(o.estMinutes)
156
- ? Math.max(5, Math.min(180, Math.round(o.estMinutes)))
157
- : 30,
158
- });
159
- }
160
- return out;
161
- }
162
-
163
- function slugify(s: string): string {
164
- return s
165
- .normalize("NFKD")
166
- .toLowerCase()
167
- .replace(/[^\p{L}\p{N}]+/gu, "-")
168
- .replace(/^-+|-+$/g, "")
169
- .slice(0, 60);
170
- }
@@ -1,72 +0,0 @@
1
- import { reflex } from "@host/api";
2
- import { extractJson } from "./_json";
3
-
4
- /**
5
- * Generate a 5-question multiple-choice quiz for a module. Each item:
6
- * stem (question), 4 options, one correct index, explanation. Quiz
7
- * isn't persisted as a separate KB entry — it's transient state the UI
8
- * holds while the user takes it; the score lands in course.meta.progress
9
- * after completion.
10
- */
11
-
12
- export interface QuizQuestion {
13
- stem: string;
14
- options: string[];
15
- correctIndex: number;
16
- explanation: string;
17
- }
18
-
19
- export interface GenerateQuizArgs {
20
- moduleTitle: string;
21
- moduleObjective: string;
22
- /** Article body — gives the LLM something to draw questions from. */
23
- article: string;
24
- }
25
-
26
- export default async function generateQuiz(
27
- args: GenerateQuizArgs,
28
- ): Promise<{ questions: QuizQuestion[] }> {
29
- const trimmed = (args.article ?? "").slice(0, 6000);
30
- const prompt = [
31
- `Модуль: «${args.moduleTitle}» — ${args.moduleObjective}.`,
32
- "Составь короткий тест-проверку из 5 вопросов с 4 вариантами ответа.",
33
- "Правила:",
34
- " • Вопросы — на понимание, не на запоминание мелочей.",
35
- " • Только один правильный ответ; остальные правдоподобные.",
36
- " • Объяснение почему правильный — 1-2 фразы.",
37
- "Верни ТОЛЬКО JSON одной строкой:",
38
- ` {"questions":[{"stem":"...","options":["a","b","c","d"],"correctIndex":0,"explanation":"..."}, ...]}`,
39
- "",
40
- `## Материал модуля\n${trimmed}`,
41
- ].join("\n");
42
-
43
- const r = await reflex.llm.complete({ task: "quick", prompt });
44
- const parsed = extractJson<{ questions?: unknown[] }>(r.text);
45
- if (!parsed) return { questions: [] };
46
- try {
47
- const out: QuizQuestion[] = [];
48
- for (const q of parsed.questions ?? []) {
49
- if (typeof q !== "object" || q === null) continue;
50
- const o = q as Partial<QuizQuestion>;
51
- if (
52
- typeof o.stem !== "string" ||
53
- !Array.isArray(o.options) ||
54
- o.options.length < 2 ||
55
- typeof o.correctIndex !== "number" ||
56
- o.correctIndex < 0 ||
57
- o.correctIndex >= o.options.length
58
- ) {
59
- continue;
60
- }
61
- out.push({
62
- stem: o.stem,
63
- options: o.options.map((x) => String(x)).slice(0, 6),
64
- correctIndex: o.correctIndex,
65
- explanation: typeof o.explanation === "string" ? o.explanation : "",
66
- });
67
- }
68
- return { questions: out.slice(0, 8) };
69
- } catch {
70
- return { questions: [] };
71
- }
72
- }
@@ -1,106 +0,0 @@
1
- import { reflex } from "@host/api";
2
-
3
- /**
4
- * Ask the agent to write an interactive trainer (small standalone HTML
5
- * page with inline JS) that practices a specific skill from a module —
6
- * e.g. simulation of a binary tree, drag-shapes-on-canvas exercise,
7
- * flashcard drill. The HTML runs inside a nested sandboxed iframe in
8
- * the utility UI; it has NO host-RPC access — strictly client-only.
9
- *
10
- * Persisted as KB kind="course-trainer" so it survives between sessions
11
- * and the user can re-open.
12
- */
13
-
14
- export interface TrainerSpec {
15
- trainerId: string;
16
- courseId: string;
17
- moduleId: string;
18
- title: string;
19
- /** Self-contained HTML — head/body/script all inline. Loaded via
20
- * iframe srcdoc inside the utility. */
21
- html: string;
22
- relPath: string;
23
- }
24
-
25
- export interface GenerateTrainerArgs {
26
- courseId: string;
27
- moduleId: string;
28
- moduleTitle: string;
29
- moduleObjective: string;
30
- prompt?: string; // user idea
31
- }
32
-
33
- export default async function generateTrainer(
34
- args: GenerateTrainerArgs,
35
- ): Promise<TrainerSpec> {
36
- const userBrief =
37
- (args.prompt ?? "").trim() ||
38
- `Спроектируй тренажёр, который помогает закрепить «${args.moduleObjective}».`;
39
- const prompt = [
40
- `Курс/модуль: «${args.moduleTitle}».`,
41
- "Сделай интерактивный тренажёр — отдельный HTML-файл с inline JS.",
42
- "Требования:",
43
- " • Один <!doctype html> файл целиком: <head>, <body>, <script>, опционально <style>.",
44
- " • НИКАКИХ внешних ресурсов: ни CDN-скриптов, ни картинок по URL, ни fetch'ей. Всё inline.",
45
- " • Используй <canvas> где это уместно (рисование, физика, геометрия).",
46
- " • Размер ≈ 600×400 px (адаптивно).",
47
- " • Чёткий interface: что показано, что делать, мгновенный feedback (правильно/нет, score).",
48
- " • Без navigator/window глобалов которые ломаются в sandbox iframe (без localStorage, без parent).",
49
- " • КОД ДОЛЖЕН РАБОТАТЬ. Никаких placeholder-функций.",
50
- "Верни ТОЛЬКО HTML (без обёртки JSON и без markdown-fence).",
51
- "",
52
- `## Идея тренажёра\n${userBrief}`,
53
- ].join("\n");
54
-
55
- // Trainer HTML is long — give the agent a generous 7 minutes; worker
56
- // timeout (8 min in manifest) is the outer guardrail.
57
- const r = await reflex.agent.invoke({ prompt, timeoutMs: 7 * 60_000 });
58
- const text = r.text ?? "";
59
- const html = extractHtml(text);
60
- if (!html) {
61
- throw new Error("Агент не вернул валидный HTML — попробуй ещё раз");
62
- }
63
-
64
- const trainerId = `${args.moduleId}-${Date.now().toString(36)}`;
65
- const saved = await reflex.kb.add({
66
- kind: "course-trainer",
67
- title: `Тренажёр · ${args.moduleTitle}`,
68
- body: "```html\n" + html.slice(0, 30_000) + "\n```",
69
- meta: {
70
- courseId: args.courseId,
71
- moduleId: args.moduleId,
72
- trainerId,
73
- title: args.moduleTitle,
74
- // Full HTML in meta is bulky but allows quick re-open without re-parsing.
75
- // Capped at ~60KB to keep frontmatter sane.
76
- html: html.slice(0, 60_000),
77
- },
78
- slug: `trainer-${trainerId}`,
79
- });
80
-
81
- return {
82
- trainerId,
83
- courseId: args.courseId,
84
- moduleId: args.moduleId,
85
- title: args.moduleTitle,
86
- html,
87
- relPath: saved.relPath,
88
- };
89
- }
90
-
91
- /** Pluck HTML from possibly-fenced agent output. */
92
- function extractHtml(text: string): string {
93
- // Fence-bound: ```html ... ```
94
- const fenced = /```(?:html)?\s*([\s\S]+?)\s*```/.exec(text);
95
- if (fenced) {
96
- const inner = fenced[1]!.trim();
97
- if (looksLikeHtml(inner)) return inner;
98
- }
99
- if (looksLikeHtml(text)) return text.trim();
100
- return "";
101
- }
102
-
103
- function looksLikeHtml(s: string): boolean {
104
- const lower = s.toLowerCase();
105
- return lower.includes("<!doctype html") || lower.includes("<html");
106
- }
@@ -1,76 +0,0 @@
1
- import { reflex } from "@host/api";
2
-
3
- /**
4
- * Update the dashboard card: number of active courses + average
5
- * progress %. Called after key events (course created, module
6
- * completed, quiz passed). Idempotent.
7
- */
8
- export default async function refreshCourseCard(): Promise<{ courses: number; avg: number }> {
9
- const list = (await reflex.kb.list({ kind: "course" })) ?? [];
10
- let total = 0;
11
- let avgSum = 0;
12
- for (const c of list) {
13
- try {
14
- const { content } = await reflex.kb.read({ relPath: c.relPath });
15
- const m = /^---\n([\s\S]*?)\n---/.exec(content);
16
- if (!m) continue;
17
- const meta = parseFrontmatter(m[1]!);
18
- const modules = jsonOf(meta.modules);
19
- const progress = jsonOf(meta.progress);
20
- if (!Array.isArray(modules) || modules.length === 0) continue;
21
- total++;
22
- const done = Object.values(
23
- progress as Record<string, { completed?: boolean }>,
24
- ).filter((p) => p && (p as { completed?: boolean }).completed).length;
25
- avgSum += done / modules.length;
26
- } catch {
27
- /* skip */
28
- }
29
- }
30
- const courses = total;
31
- const avg = total > 0 ? Math.round((avgSum / total) * 100) : 0;
32
- await reflex.cards.update({
33
- snapshot: {
34
- kind: "kpi",
35
- title: "🎓 Учусь",
36
- data: {
37
- items: [
38
- { label: "Активных курсов", value: String(courses) },
39
- {
40
- label: "Прогресс",
41
- value: courses === 0 ? "—" : `${avg}%`,
42
- hint: "среднее по курсам",
43
- ...(avg >= 60
44
- ? ({ delta: "up" } as const)
45
- : avg < 30 && courses > 0
46
- ? ({ delta: "down" } as const)
47
- : ({ delta: "flat" } as const)),
48
- },
49
- ],
50
- },
51
- },
52
- });
53
- return { courses, avg };
54
- }
55
-
56
- function jsonOf(v: unknown): unknown {
57
- if (typeof v !== "string") return v;
58
- try {
59
- return JSON.parse(v);
60
- } catch {
61
- return null;
62
- }
63
- }
64
-
65
- function parseFrontmatter(s: string): Record<string, unknown> {
66
- const out: Record<string, unknown> = {};
67
- for (const line of s.split("\n")) {
68
- const i = line.indexOf(":");
69
- if (i < 0) continue;
70
- out[line.slice(0, i).trim()] = line
71
- .slice(i + 1)
72
- .trim()
73
- .replace(/^["']|["']$/g, "");
74
- }
75
- return out;
76
- }
@@ -1,93 +0,0 @@
1
- import { reflex } from "@host/api";
2
- import { extractJson } from "./_json";
3
-
4
- /**
5
- * Wizard step. Given the topic + prior Q&A, the agent decides whether
6
- * it needs more context or has enough to design a course. Returns
7
- * either the next question (open-ended or multiple-choice) or
8
- * `{done:true}` so the UI advances to outline generation.
9
- *
10
- * Keeping the wizard agent-driven means we don't hardcode "3 questions
11
- * about level/focus/format" — for "хочу научиться рисовать карандашом"
12
- * the agent will ask different questions than for "хочу выучить
13
- * Python". The UI just renders whatever comes back.
14
- */
15
-
16
- export interface TutorQA {
17
- question: string;
18
- answer: string;
19
- }
20
-
21
- export interface TutorAskArgs {
22
- topic: string;
23
- history: TutorQA[];
24
- /** Force-finish even if the agent wants more. UI uses this when the
25
- * user clicks "Хватит вопросов — давай уже курс". */
26
- forceFinish?: boolean;
27
- }
28
-
29
- export interface TutorAskResult {
30
- done: boolean;
31
- /** Free-form question; UI renders a textarea. */
32
- question?: string;
33
- /** Optional pre-baked choices for a multiple-choice ask. */
34
- choices?: string[];
35
- /** Hint to UI: short label like "уровень", "формат", "цель". */
36
- header?: string;
37
- }
38
-
39
- const MAX_TURNS = 5;
40
-
41
- export default async function tutorAsk(
42
- args: TutorAskArgs,
43
- ): Promise<TutorAskResult> {
44
- const turns = args.history.length;
45
- if (args.forceFinish || turns >= MAX_TURNS) {
46
- return { done: true };
47
- }
48
-
49
- const prior = args.history
50
- .map((qa, i) => `Q${i + 1}: ${qa.question}\nA${i + 1}: ${qa.answer}`)
51
- .join("\n\n");
52
-
53
- const prompt = [
54
- "Ты — наставник, который собирается составить персональный курс для пользователя.",
55
- `Тема: «${args.topic}».`,
56
- "Тебе нужно собрать минимум информации, чтобы хорошо подобрать программу: уровень, цель, время, формат, специфика.",
57
- "Задавай по ОДНОМУ вопросу за раз. Решай сам какой именно — то что важнее всего сейчас, исходя из темы и предыдущих ответов.",
58
- "Если есть смысл предложить варианты ответа — добавь до 5 коротких choices.",
59
- "Если ты уже знаешь достаточно (обычно после 2-4 вопросов) — верни done=true.",
60
- "Ответь ТОЛЬКО JSON одной строкой, без markdown:",
61
- ` {"done":false,"question":"...","header":"уровень|цель|время|формат|...","choices":["...","..."]}`,
62
- " или",
63
- ` {"done":true}`,
64
- "",
65
- prior ? `## Предыдущие ответы\n${prior}` : "## Это первый вопрос",
66
- `\nЗадано вопросов до сих пор: ${turns} (максимум ${MAX_TURNS}).`,
67
- ].join("\n");
68
-
69
- const r = await reflex.llm.complete({ task: "quick", prompt });
70
- const parsed = extractJson<TutorAskResult>(r.text);
71
- if (!parsed) return { done: true };
72
- try {
73
- if (parsed.done) return { done: true };
74
- if (typeof parsed.question !== "string" || !parsed.question.trim()) {
75
- return { done: true };
76
- }
77
- return {
78
- done: false,
79
- question: parsed.question.trim(),
80
- ...(parsed.header ? { header: parsed.header } : {}),
81
- ...(Array.isArray(parsed.choices) && parsed.choices.length > 0
82
- ? {
83
- choices: parsed.choices
84
- .map((c) => String(c).trim())
85
- .filter(Boolean)
86
- .slice(0, 5),
87
- }
88
- : {}),
89
- };
90
- } catch {
91
- return { done: true };
92
- }
93
- }