reflex-agent 0.2.4 → 0.3.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.
Files changed (206) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +103 -90
  3. package/.next/app-path-routes-manifest.json +8 -8
  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 +3 -3
  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 +5 -3
  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 +5 -5
  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 +8 -8
  88. package/.next/server/chunks/1.js +3 -0
  89. package/.next/server/chunks/1223.js +1 -1
  90. package/.next/server/chunks/133.js +1 -490
  91. package/.next/server/chunks/1888.js +1 -1
  92. package/.next/server/chunks/{9739.js → 1988.js} +13 -9
  93. package/.next/server/chunks/2192.js +1 -0
  94. package/.next/server/chunks/2433.js +1 -1
  95. package/.next/server/chunks/2503.js +1 -1
  96. package/.next/server/chunks/285.js +471 -0
  97. package/.next/server/chunks/2995.js +1 -0
  98. package/.next/server/chunks/3240.js +1 -1
  99. package/.next/server/chunks/3332.js +1 -1
  100. package/.next/server/chunks/{3953.js → 3512.js} +2 -2
  101. package/.next/server/chunks/3657.js +1 -1
  102. package/.next/server/chunks/4066.js +1 -1
  103. package/.next/server/chunks/4438.js +1 -0
  104. package/.next/server/chunks/4553.js +1 -1
  105. package/.next/server/chunks/4812.js +179 -0
  106. package/.next/server/chunks/4925.js +1 -1
  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/6734.js +1 -0
  111. package/.next/server/chunks/6909.js +142 -161
  112. package/.next/server/chunks/7215.js +1 -0
  113. package/.next/server/chunks/8262.js +1 -1
  114. package/.next/server/chunks/9098.js +1 -1
  115. package/.next/server/chunks/94.js +1 -1
  116. package/.next/server/chunks/9835.js +1 -1
  117. package/.next/server/chunks/9944.js +1 -0
  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/1082-326e649fb24d4945.js +1 -0
  126. package/.next/static/chunks/3736-f4e42d6d38be50b0.js +1 -0
  127. package/.next/static/chunks/4108.ca0bdf3cbf3c56cc.js +1 -0
  128. package/.next/static/chunks/6445-99824866a51b582a.js +1 -0
  129. package/.next/static/chunks/7482-7ef26030a10ce14f.js +1 -0
  130. package/.next/static/chunks/8944-c4f2406ecd61094f.js +1 -0
  131. package/.next/static/chunks/9411-af5f758c57741929.js +3 -0
  132. package/.next/static/chunks/9415-eb6b5d4c2de3a7c0.js +1 -0
  133. package/.next/static/chunks/app/agents/[agentId]/page-5d6f4cb16b42d02b.js +1 -0
  134. package/.next/static/chunks/app/layout-85eb1fd21dab0895.js +1 -0
  135. package/.next/static/chunks/app/onboarding/page-2013bd8124b9162e.js +1 -0
  136. package/.next/static/chunks/app/page-558a224e13ffb52c.js +1 -0
  137. package/.next/static/chunks/app/roots/[id]/chat/[topicId]/page-b42f03fd58669d12.js +1 -0
  138. package/.next/static/chunks/app/roots/[id]/kb/[...slug]/page-7d17b4e6a5231f56.js +1 -0
  139. package/.next/static/chunks/app/roots/[id]/page-4aab5266f432e37e.js +1 -0
  140. package/.next/static/chunks/app/roots/[id]/workflows/[wfId]/page-1ee3320bf5744efc.js +1 -0
  141. package/.next/static/chunks/app/roots/new/page-df8d2c1f0c64c37a.js +1 -0
  142. package/.next/static/chunks/app/settings/page-fdba798d9e243ad3.js +1 -0
  143. package/.next/static/chunks/app/share/[id]/page-818a451d05e08d26.js +1 -0
  144. package/.next/static/chunks/app/utilities/[scope]/[id]/page-2cee09cc2ab9b5e8.js +1 -0
  145. package/.next/static/chunks/app/utilities/page-44a51522b347f13e.js +1 -0
  146. package/.next/static/chunks/{webpack-5fca180586957874.js → webpack-2b0eab4ccdf44f63.js} +1 -1
  147. package/.next/static/css/4b367c1d0fa99b78.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 -1
  154. package/packages/utilities/learn-anything/README.md +29 -29
  155. package/packages/utilities/learn-anything/actions/_json.ts +11 -11
  156. package/packages/utilities/learn-anything/actions/_store.ts +2 -2
  157. package/packages/utilities/learn-anything/actions/buildModule.ts +60 -59
  158. package/packages/utilities/learn-anything/actions/explainSelection.ts +14 -13
  159. package/packages/utilities/learn-anything/actions/generateOutline.ts +15 -15
  160. package/packages/utilities/learn-anything/actions/generateQuiz.ts +8 -8
  161. package/packages/utilities/learn-anything/actions/generateTrainer.ts +15 -15
  162. package/packages/utilities/learn-anything/actions/refreshCourseCard.ts +4 -4
  163. package/packages/utilities/learn-anything/actions/tutorAsk.ts +15 -15
  164. package/packages/utilities/learn-anything/article-view.tsx +4 -4
  165. package/packages/utilities/learn-anything/manifest.json +5 -5
  166. package/packages/utilities/learn-anything/ui.tsx +57 -57
  167. package/.next/server/app/_not-found.html +0 -1
  168. package/.next/server/app/_not-found.meta +0 -8
  169. package/.next/server/app/_not-found.rsc +0 -18
  170. package/.next/server/app/index.html +0 -1
  171. package/.next/server/app/index.meta +0 -9
  172. package/.next/server/app/index.rsc +0 -19
  173. package/.next/server/chunks/1410.js +0 -1
  174. package/.next/server/chunks/1986.js +0 -1
  175. package/.next/server/chunks/2448.js +0 -3
  176. package/.next/server/chunks/5754.js +0 -3
  177. package/.next/server/chunks/7097.js +0 -1
  178. package/.next/server/chunks/7782.js +0 -1
  179. package/.next/server/chunks/7987.js +0 -1
  180. package/.next/server/chunks/810.js +0 -1
  181. package/.next/server/chunks/8843.js +0 -1
  182. package/.next/server/chunks/9328.js +0 -179
  183. package/.next/server/pages/404.html +0 -1
  184. package/.next/static/chunks/2488-c9590facb4b9f184.js +0 -1
  185. package/.next/static/chunks/2684-257d38989ef53935.js +0 -1
  186. package/.next/static/chunks/4108.fb9f99a9c899ef54.js +0 -1
  187. package/.next/static/chunks/6231-d83c1544bbea8424.js +0 -1
  188. package/.next/static/chunks/9045-731ff0865352dd95.js +0 -1
  189. package/.next/static/chunks/9496-75ccd3fadb294fba.js +0 -1
  190. package/.next/static/chunks/992-4e7b7f722c629e21.js +0 -1
  191. package/.next/static/chunks/app/agents/[agentId]/page-0b5c2838354d0eba.js +0 -1
  192. package/.next/static/chunks/app/layout-9a59ed07c18cb786.js +0 -1
  193. package/.next/static/chunks/app/onboarding/page-79f07a813ea2abfe.js +0 -1
  194. package/.next/static/chunks/app/page-27f4b98b02ac4f79.js +0 -1
  195. package/.next/static/chunks/app/roots/[id]/chat/[topicId]/page-8db2d0b75cd333c8.js +0 -1
  196. package/.next/static/chunks/app/roots/[id]/kb/[...slug]/page-873b131eec3a2f30.js +0 -1
  197. package/.next/static/chunks/app/roots/[id]/page-270d0d49eb668784.js +0 -1
  198. package/.next/static/chunks/app/roots/[id]/workflows/[wfId]/page-7c1f10dbe0bcb9ad.js +0 -1
  199. package/.next/static/chunks/app/roots/new/page-ac1a9f6379710ca2.js +0 -1
  200. package/.next/static/chunks/app/settings/page-81cb1393e817dfc3.js +0 -1
  201. package/.next/static/chunks/app/share/[id]/page-2d123f0a99e1606f.js +0 -1
  202. package/.next/static/chunks/app/utilities/[scope]/[id]/page-0bbb8d17af80c1da.js +0 -1
  203. package/.next/static/chunks/app/utilities/page-e6ce673b9357bf1f.js +0 -1
  204. package/.next/static/css/87e01f779d555d04.css +0 -1
  205. /package/.next/static/{og_wC7UPkGtJDiapaTgBr → fhVNqfmJl5Mdfhyhg6orp}/_buildManifest.js +0 -0
  206. /package/.next/static/{og_wC7UPkGtJDiapaTgBr → fhVNqfmJl5Mdfhyhg6orp}/_ssgManifest.js +0 -0
@@ -91,77 +91,77 @@ export default async function buildModule(
91
91
  // the LLM tells us WHAT to look for / draw; Reflex resolves it via
92
92
  // reflex.images.search (Brave when available) + reflex.images.generate.
93
93
  const prompt = [
94
- `Курс: «${args.topic}». Модуль: «${args.moduleTitle}» — ${args.moduleObjective}.`,
95
- "Подготовь учебный материал. Структура JSON-ответа:",
94
+ `Course: "${args.topic}". Module: "${args.moduleTitle}" — ${args.moduleObjective}.`,
95
+ "Produce the learning material. JSON reply shape:",
96
96
  "{",
97
- ` "article": "длинный markdown 800-2000 слов; используй # ## ### и плейсхолдеры [[IMG:<id>]] для inline-картинок",`,
97
+ ` "article": "long markdown 800-2000 words; use # ## ### and [[IMG:<id>]] placeholders for inline images",`,
98
98
  ` "videos": [{"title":"...","url":"https://youtube.com/...","note":"..."}],`,
99
99
  ` "links": [{"title":"...","url":"...","snippet":"..."}],`,
100
- ` "imageQueries": [{"id":"i1","alt":"...","query":"короткий английский поисковый запрос"}],`,
101
- ` "generatedFigures": [{"id":"f1","alt":"...","prompt":"подробное описание для AI-генератора, английский"}],`,
100
+ ` "imageQueries": [{"id":"i1","alt":"...","query":"short English search query"}],`,
101
+ ` "generatedFigures": [{"id":"f1","alt":"...","prompt":"detailed English description for the AI generator"}],`,
102
102
  ` "diagrams": [{"title":"...","mermaid":"graph TD; A-->B;"}],`,
103
103
  ` "homework": ["...","..."]`,
104
104
  "}",
105
105
  "",
106
- "## ВИЗУАЛЬНОЕ СОПРОВОЖДЕНИЕОБЯЗАТЕЛЬНО + INLINE-РАЗМЕЩЕНИЕ",
106
+ "## VISUAL CONTENTMANDATORY + INLINE PLACEMENT",
107
107
  "",
108
- "Любой учебный модуль ОБЯЗАН быть визуально насыщенным. Каждая картинка ставится **inline** в нужном месте текста через плейсхолдер.",
108
+ "Every learning module MUST be visually rich. Each image is placed **inline** at the right spot in the text via a placeholder.",
109
109
  "",
110
- "### Как это работает",
111
- " 1. Каждому элементу в `imageQueries` и `generatedFigures` присваиваешь ШОРТ-ID (`i1`, `i2`, `f1`, `f2`, ...). i = image-search (real photo), f = figure (AI-generated). Уникальный в пределах модуля.",
112
- " 2. В `article` markdown вставляешь плейсхолдер `[[IMG:i1]]` на ОТДЕЛЬНОЙ СТРОКЕ ровно там где должна стоять эта картинка (например, после абзаца который её обсуждает).",
113
- " 3. Reflex автоматически:",
114
- " – ищет реальные фото/схемы через Brave Image Search (по `imageQueries`),",
115
- " – ВИЗУАЛЬНО ОЦЕНИВАЕТ кандидатов (твой chat-агент смотрит на thumbnails через Read tool) — клипарт/off-topic отклоняются по содержимому,",
116
- " – генерирует уникальные иллюстрации через Gemini Nano Banana (по `generatedFigures`),",
117
- " – заменяет `[[IMG:<id>]]` на `![alt](локальный-url)` с атрибуцией,",
118
- " – неотрезолвленные id (картинка не найдена / отклонена) удаляются из текста чисто.",
110
+ "### How it works",
111
+ " 1. Each entry in `imageQueries` and `generatedFigures` gets a SHORT ID (`i1`, `i2`, `f1`, `f2`, ...). i = image-search (real photo), f = figure (AI-generated). Unique within the module.",
112
+ " 2. In the `article` markdown insert the placeholder `[[IMG:i1]]` on its OWN LINE exactly where the image should appear (e.g. after the paragraph that discusses it).",
113
+ " 3. Reflex automatically:",
114
+ " – searches for real photos/diagrams via Brave Image Search (using `imageQueries`),",
115
+ " – VISUALLY EVALUATES candidates (your chat agent inspects thumbnails via the Read tool) — clipart / off-topic items are rejected based on content,",
116
+ " – generates unique illustrations via Gemini Nano Banana (using `generatedFigures`),",
117
+ " – replaces `[[IMG:<id>]]` with `![alt](local-url)` plus attribution,",
118
+ " – unresolved ids (image not found / rejected) are cleanly stripped from the text.",
119
119
  "",
120
- "### Пример",
120
+ "### Example",
121
121
  "```markdown",
122
- "## Зрительная кора",
123
- "Зрительная кора V1 первая зона обработки информации от сетчатки.",
122
+ "## Visual cortex",
123
+ "The primary visual cortex V1 is the first area that processes information from the retina.",
124
124
  "",
125
125
  "[[IMG:i1]]",
126
126
  "",
127
- "Колончатая организация V1 была описана Хьюбелом и Визелом в 1962 году...",
127
+ "The columnar organisation of V1 was described by Hubel and Wiesel in 1962...",
128
128
  "",
129
129
  "[[IMG:f1]]",
130
130
  "```",
131
- "...где `imageQueries: [{id:\"i1\", alt:\"Срез зрительной коры V1\", query:\"primary visual cortex V1 histology\"}]` и `generatedFigures: [{id:\"f1\", alt:\"Схема рецептивного поля\", prompt:\"educational diagram: receptive field of simple cells in V1, ON-OFF regions, labeled in English\"}]`.",
131
+ "...where `imageQueries: [{id:\"i1\", alt:\"Section of the primary visual cortex V1\", query:\"primary visual cortex V1 histology\"}]` and `generatedFigures: [{id:\"f1\", alt:\"Receptive field diagram\", prompt:\"educational diagram: receptive field of simple cells in V1, ON-OFF regions, labeled in English\"}]`.",
132
132
  "",
133
- "Твоя задача заполнить два массива И расставить плейсхолдеры в article:",
133
+ "Your job is to fill both arrays AND place the placeholders in `article`:",
134
134
  "",
135
- "### `imageQueries` (поиск реальных материалов) — МИНИМУМ 2-3 шт.",
136
- " • Для тем где есть реальные референсы (Эйфелева башня, клетка, Гражданская война, лабораторная установка, известная картина, ландшафт, исторический документ) — ВСЕГДА добавляй 2-4 query.",
137
- " • Каждый query короткий АНГЛИЙСКИЙ поисковый запрос (Brave работает лучше на английском): \"Eiffel Tower iron lattice closeup\", \"mitochondria electron microscope\", \"American Civil War Gettysburg battlefield\".",
138
- " • `id` — короткий уникальный идентификатор: `i1`, `i2`, `i3`...",
139
- " • `alt` — короткое описание по-русски, что зритель увидит.",
140
- " • Каждому `id` соответствует ровно один плейсхолдер `[[IMG:i1]]` в `article` — ставь его в тематически подходящем месте.",
135
+ "### `imageQueries` (real-image search) — AT LEAST 2-3 items.",
136
+ " • For topics with real-world references (Eiffel Tower, a cell, the Civil War, lab equipment, a famous painting, a landscape, a historical document) — ALWAYS add 2-4 queries.",
137
+ " • Each `query` is a short ENGLISH search string (Brave works best in English): \"Eiffel Tower iron lattice closeup\", \"mitochondria electron microscope\", \"American Civil War Gettysburg battlefield\".",
138
+ " • `id` — short unique identifier: `i1`, `i2`, `i3`...",
139
+ " • `alt` — short description of what the viewer will see.",
140
+ " • Each `id` corresponds to exactly one `[[IMG:i1]]` placeholder in `article` — put it in a thematically appropriate spot.",
141
141
  "",
142
- "### `generatedFigures` (AI-генерация уникальных схем) — 1-2 шт когда уместно.",
143
- " • Используй для уникальных схем/иллюстраций, которых нет в сети: \"процесс N в виде наглядной схемы\", \"анатомия Х в стиле учебника\", \"таймлайн событий\", \"абстрактная визуализация концепции\".",
144
- " • `id` — короткий уникальный идентификатор: `f1`, `f2`...",
145
- " • `prompt` — подробный АНГЛИЙСКИЙ описательный prompt со стилем (\"minimalist educational diagram, white background, labeled parts in blue, isometric view\" / \"watercolor illustration, soft palette\" / \"photorealistic, studio lighting\").",
146
- " • НЕ дублируй generatedFigures с imageQueries — generate только то, что не найти готовым.",
147
- " • `alt` — короткое описание по-русски.",
148
- " • Поставь плейсхолдер `[[IMG:f1]]` в article ровно где эта схема нужна.",
142
+ "### `generatedFigures` (AI-generated unique figures) — 1-2 items when appropriate.",
143
+ " • Use for unique schemes/illustrations that aren't available online: \"process N as a clear schematic\", \"anatomy of X in textbook style\", \"event timeline\", \"abstract visualisation of a concept\".",
144
+ " • `id` — short unique identifier: `f1`, `f2`...",
145
+ " • `prompt` — detailed ENGLISH descriptive prompt including style (\"minimalist educational diagram, white background, labeled parts in blue, isometric view\" / \"watercolor illustration, soft palette\" / \"photorealistic, studio lighting\").",
146
+ " • DO NOT duplicate generatedFigures with imageQueries — only generate what cannot be found ready-made.",
147
+ " • `alt` — short description.",
148
+ " • Place the `[[IMG:f1]]` placeholder in `article` exactly where the figure is needed.",
149
149
  "",
150
- "### Правила для прочих полей",
151
- " • article — основной текст, 800-2000 слов. Заголовки # ## ###, плотный материал.",
152
- " • Картинки размещаются ТОЛЬКО через `[[IMG:<id>]]` на отдельной строке. Не пиши `[[ИЛЛЮСТРАЦИЯ: ...]]`, `[[СХЕМА: ...]]` — они не работают.",
153
- " • НЕ ВЫДУМЫВАЙ URL картинок. Любые bare URL в article игнорируются.",
154
- " • Каждый id, объявленный в imageQueries/generatedFigures, должен встретиться в article ровно один раз. Каждый `[[IMG:<id>]]` в тексте должен иметь соответствие в одном из массивов.",
155
- " • videos: 1-3 ссылки на youtube/youtu.be — URL юзер сам проверит.",
156
- " • links: 2-5 авторитетных статей.",
157
- " • diagrams (mermaid): только flowchart/sequence/class — где mermaid реально удобнее картинки. Для visual schemes используй generatedFigures.",
158
- " • homework: 3-5 практических заданий с проверяемым результатом.",
150
+ "### Rules for the other fields",
151
+ " • article — main text, 800-2000 words. Headings # ## ###, dense content.",
152
+ " • Images are placed ONLY via `[[IMG:<id>]]` on its own line. Do not write `[[ILLUSTRATION: ...]]`, `[[DIAGRAM: ...]]` — they don't work.",
153
+ " • DO NOT MAKE UP image URLs. Any bare URL in `article` is ignored.",
154
+ " • Every id declared in imageQueries/generatedFigures must appear in `article` exactly once. Every `[[IMG:<id>]]` in the text must have a matching entry in one of the arrays.",
155
+ " • videos: 1-3 youtube/youtu.be links the user verifies URLs themselves.",
156
+ " • links: 2-5 authoritative articles.",
157
+ " • diagrams (mermaid): only flowchart/sequence/class — where mermaid is genuinely more convenient than an image. For visual schemes use generatedFigures.",
158
+ " • homework: 3-5 practical exercises with a verifiable result.",
159
159
  "",
160
- "Верни ТОЛЬКО JSON одной строкой, без markdown-фенс.",
160
+ "Reply with JSON ONLY on a single line, no markdown fences.",
161
161
  "",
162
162
  webContext
163
- ? `## Web-источники для опоры\n${webContext}`
164
- : "## Web-источники недоступныопирайся на свои знания.",
163
+ ? `## Web sources to ground on\n${webContext}`
164
+ : "## Web sources unavailable rely on your own knowledge.",
165
165
  ].join("\n");
166
166
 
167
167
  const result = await callJsonAgent<LlmDraft>({
@@ -170,7 +170,7 @@ export default async function buildModule(
170
170
  maxAttempts: 4,
171
171
  shapeHint:
172
172
  `{"article":"...","videos":[],"links":[],"imageQueries":[],"generatedFigures":[],"diagrams":[],"homework":[]}\n` +
173
- `article — markdown 800-2000 слов. Все массивы обязательно массивы (можно пустые).`,
173
+ `article — markdown 800-2000 words. Every list field must be an array (empty is OK).`,
174
174
  validate: (p) => {
175
175
  const v = p as LlmDraft;
176
176
  return typeof v?.article === "string" && v.article.trim().length > 40
@@ -180,8 +180,8 @@ export default async function buildModule(
180
180
  });
181
181
  if (!result.ok) {
182
182
  throw new Error(
183
- `Не удалось собрать модуль за ${result.attempts} попыток (${result.reason}). ` +
184
- `Последний ответ: «${snippet(result.lastText, 200)}».`,
183
+ `Failed to build the module in ${result.attempts} attempts (${result.reason}). ` +
184
+ `Last reply: "${snippet(result.lastText, 200)}".`,
185
185
  );
186
186
  }
187
187
  const draft = result.value;
@@ -196,8 +196,9 @@ export default async function buildModule(
196
196
 
197
197
  // Inline-place images by substituting [[IMG:<id>]] placeholders in the
198
198
  // article body. Any image whose id wasn't referenced (LLM forgot)
199
- // falls back into the trailing Иллюстрации section so nothing gets
200
- // dropped silently. Residual unknown [[...]] markers are stripped.
199
+ // falls back into the trailing "Additional illustrations" section so
200
+ // nothing gets dropped silently. Residual unknown [[...]] markers are
201
+ // stripped.
201
202
  const allImages = [...searchedImages, ...generatedImages];
202
203
  const { article: articleWithImages, placedIds } = substituteImagePlaceholders(
203
204
  typeof draft.article === "string" ? draft.article : "",
@@ -226,13 +227,13 @@ export default async function buildModule(
226
227
  const body = [
227
228
  content.article,
228
229
  unplaced.length > 0
229
- ? "\n\n## Дополнительные иллюстрации\n" +
230
+ ? "\n\n## Additional illustrations\n" +
230
231
  unplaced
231
232
  .map((im) => `${renderInlineImage(im)}`)
232
233
  .join("\n\n")
233
234
  : "",
234
235
  content.diagrams.length > 0
235
- ? "\n\n## Схемы\n" +
236
+ ? "\n\n## Diagrams\n" +
236
237
  content.diagrams
237
238
  .map(
238
239
  (d) =>
@@ -395,24 +396,24 @@ function renderInlineImage(im: ModuleContent["images"][number]): string {
395
396
  const safeAlt = im.alt.replace(/[\[\]\n]/g, " ").slice(0, 200);
396
397
  const credit =
397
398
  im.source === "search" && im.attribution?.name
398
- ? `\n\n_Источник: [${im.attribution.name}](${im.attribution.link || im.url})_`
399
+ ? `\n\n_Source: [${im.attribution.name}](${im.attribution.link || im.url})_`
399
400
  : im.source === "generated"
400
- ? `\n\n_Сгенерировано AI_`
401
+ ? `\n\n_Generated by AI_`
401
402
  : "";
402
403
  return `![${safeAlt}](${im.url})${credit}`;
403
404
  }
404
405
 
405
406
  /**
406
- * Strip residual `[[ИЛЛЮСТРАЦИЯ: ...]]` / `[[СХЕМА: ...]]` / orphan
407
+ * Strip residual `[[ILLUSTRATION: ...]]` / `[[DIAGRAM: ...]]` / orphan
407
408
  * `[[IMG:<unknown-id>]]` placeholders the LLM might emit despite the
408
409
  * prompt forbidding them OR after `substituteImagePlaceholders` failed
409
410
  * to find the id. Drops the marker, collapses surrounding whitespace.
410
411
  *
411
- * Matches Russian variants + Latin spellings for safety. Case-insensitive.
412
+ * Matches several spelling variants for safety. Case-insensitive.
412
413
  */
413
414
  function stripPlaceholderMarkers(text: string): string {
414
415
  const re =
415
- /\[\[?\s*(?:ИЛЛЮСТРАЦИЯ|СХЕМА|ILLUSTRATION|SCHEME|IMAGE|IMG|DIAGRAM)\b[^\]]*?\]\]?/giu;
416
+ /\[\[?\s*(?:ILLUSTRATION|SCHEME|IMAGE|IMG|DIAGRAM)\b[^\]]*?\]\]?/giu;
416
417
  let out = text.replace(re, "");
417
418
  // Clean up double blank lines + trailing whitespace the removal may
418
419
  // have left behind.
@@ -3,15 +3,16 @@ import { reflex } from "@host/api";
3
3
  /**
4
4
  * "Explain this" feature: user highlights a snippet inside the module
5
5
  * article and asks for a deeper explanation. The agent gets the
6
- * surrounding paragraph as context so it understands what "это" means.
6
+ * surrounding paragraph as context so it understands what "this" means.
7
7
  *
8
8
  * Two modes:
9
9
  * 1. Default — `question` omitted, agent gives a generic 2-5 paragraph
10
10
  * breakdown of the selected fragment.
11
11
  * 2. Custom — `question` supplied (book-style margin annotation), agent
12
12
  * answers that specific question with the selection as the focal
13
- * point. Lets the reader say things like "при чём тут N?" or
14
- * "дай пример" instead of always getting the same boilerplate.
13
+ * point. Lets the reader say things like "what does N have to do
14
+ * with this?" or "give me an example" instead of always getting the
15
+ * same boilerplate.
15
16
  */
16
17
 
17
18
  export interface ExplainSelectionArgs {
@@ -29,31 +30,31 @@ export default async function explainSelection(
29
30
  ): Promise<{ text: string }> {
30
31
  const userQuestion = (args.question ?? "").trim();
31
32
  const promptLines: string[] = [
32
- `Курс: «${args.topic}». Модуль: «${args.moduleTitle}».`,
33
+ `Course: "${args.topic}". Module: "${args.moduleTitle}".`,
33
34
  ];
34
35
 
35
36
  if (userQuestion) {
36
37
  promptLines.push(
37
- "Пользователь выделил фрагмент и задал конкретный вопрос про этот фрагмент.",
38
- "Ответь именно на его вопрос, опираясь на выделение + окружающий контекст. 2-4 абзаца, без воды, по делу.",
39
- "Markdown без заголовков; короткие фразы, пример если уместен.",
38
+ "The user highlighted a fragment and asked a specific question about that fragment.",
39
+ "Answer exactly their question, grounded in the selection + surrounding context. 2-4 paragraphs, no fluff, to the point.",
40
+ "Markdown without headings; short sentences, with an example when appropriate.",
40
41
  );
41
42
  } else {
42
43
  promptLines.push(
43
- "Пользователь выделил фрагмент и просит объяснить подробнее.",
44
- "Дай развёрнутое объяснение в 2-5 абзацах: что это значит, как работает,",
45
- "почему именно так, конкретный пример. Без воды. Markdown без заголовков.",
44
+ "The user highlighted a fragment and is asking for a deeper explanation.",
45
+ "Give a thorough explanation in 2-5 paragraphs: what it means, how it works,",
46
+ "why it works that way, with a concrete example. No fluff. Markdown without headings.",
46
47
  );
47
48
  }
48
49
 
49
50
  promptLines.push(
50
51
  "",
51
- `## Окружающий контекст\n${args.context.slice(0, 1500)}`,
52
+ `## Surrounding context\n${args.context.slice(0, 1500)}`,
52
53
  "",
53
- `## Выделение пользователя\n«${args.selection.slice(0, 800)}»`,
54
+ `## User selection\n"${args.selection.slice(0, 800)}"`,
54
55
  );
55
56
  if (userQuestion) {
56
- promptLines.push("", `## Вопрос пользователя\n${userQuestion.slice(0, 600)}`);
57
+ promptLines.push("", `## User question\n${userQuestion.slice(0, 600)}`);
57
58
  }
58
59
 
59
60
  const r = await reflex.llm.complete({
@@ -38,20 +38,20 @@ export default async function generateOutline(
38
38
  .map((qa) => `Q: ${qa.question}\nA: ${qa.answer}`)
39
39
  .join("\n\n");
40
40
  const prompt = [
41
- "Составь учебный курс по теме «${TOPIC}» под пользователя с такими ответами:".replace(
41
+ "Design a learning course on the topic \"${TOPIC}\" tailored to a user with the following answers:".replace(
42
42
  "${TOPIC}",
43
43
  args.topic,
44
44
  ),
45
45
  "",
46
- prior || "(нет ответов)",
46
+ prior || "(no answers)",
47
47
  "",
48
- "Требования:",
49
- " • 5-9 модулей, от базовых к продвинутым.",
50
- " • Каждый модуль самодостаточен (можно открыть и пройти за один присест).",
51
- " • objective — одна точная фраза «к концу модуля ты сможешь …».",
52
- " • estMinutes — реалистичная оценка чистого времени учёбы (15-60).",
53
- " • id модуля — kebab-case, латиница+цифры.",
54
- "Верни ТОЛЬКО JSON одной строкой, без markdown:",
48
+ "Requirements:",
49
+ " • 5-9 modules, from basic to advanced.",
50
+ " • Each module is self-contained (can be opened and completed in one sitting).",
51
+ " • objective — one precise phrase \"by the end of this module you will be able to …\".",
52
+ " • estMinutes — a realistic estimate of pure study time (15-60).",
53
+ " • module id — kebab-case, latin letters + digits.",
54
+ "Reply with JSON ONLY on a single line, no markdown:",
55
55
  ` {"modules":[{"id":"intro","title":"…","objective":"…","estMinutes":30}, ...]}`,
56
56
  ].join("\n");
57
57
 
@@ -60,8 +60,8 @@ export default async function generateOutline(
60
60
  invoke: (p) => reflex.agent.invoke({ prompt: p, timeoutMs: 4 * 60_000 }),
61
61
  maxAttempts: 4,
62
62
  shapeHint:
63
- `{"modules":[{"id":"intro","title":"Введение","objective":"к концу модуля сможешь …","estMinutes":30}, ...]}\n` +
64
- `Минимум 5 модулей. id — kebab-case, без пробелов и кириллицы.`,
63
+ `{"modules":[{"id":"intro","title":"Introduction","objective":"by the end of this module you will be able to …","estMinutes":30}, ...]}\n` +
64
+ `At least 5 modules. id — kebab-case, no spaces, latin letters only.`,
65
65
  validate: (parsed) => {
66
66
  const v = parsed as { modules?: unknown };
67
67
  const modules = sanitizeModules(v?.modules);
@@ -71,8 +71,8 @@ export default async function generateOutline(
71
71
 
72
72
  if (!result.ok) {
73
73
  throw new Error(
74
- `Не удалось собрать программу курса за ${result.attempts} попыток (${result.reason}). ` +
75
- `Последний ответ агента: «${snippet(result.lastText, 200)}». Попробуй переформулировать тему.`,
74
+ `Failed to build the course outline in ${result.attempts} attempts (${result.reason}). ` +
75
+ `Last agent reply: "${snippet(result.lastText, 200)}". Try rephrasing the topic.`,
76
76
  );
77
77
  }
78
78
  const modules = result.value;
@@ -82,11 +82,11 @@ export default async function generateOutline(
82
82
  const body = [
83
83
  `# ${args.topic}`,
84
84
  "",
85
- "## Программа",
85
+ "## Program",
86
86
  "",
87
87
  ...modules.map(
88
88
  (mod, i) =>
89
- `${i + 1}. **${mod.title}** — ${mod.objective} (~${mod.estMinutes} мин)`,
89
+ `${i + 1}. **${mod.title}** — ${mod.objective} (~${mod.estMinutes} min)`,
90
90
  ),
91
91
  ].join("\n");
92
92
 
@@ -28,16 +28,16 @@ export default async function generateQuiz(
28
28
  ): Promise<{ questions: QuizQuestion[] }> {
29
29
  const trimmed = (args.article ?? "").slice(0, 6000);
30
30
  const prompt = [
31
- `Модуль: «${args.moduleTitle}» — ${args.moduleObjective}.`,
32
- "Составь короткий тест-проверку из 5 вопросов с 4 вариантами ответа.",
33
- "Правила:",
34
- " • Вопросы на понимание, не на запоминание мелочей.",
35
- " • Только один правильный ответ; остальные правдоподобные.",
36
- " • Объяснение почему правильный — 1-2 фразы.",
37
- "Верни ТОЛЬКО JSON одной строкой:",
31
+ `Module: "${args.moduleTitle}" — ${args.moduleObjective}.`,
32
+ "Compose a short knowledge-check quiz of 5 questions with 4 answer options each.",
33
+ "Rules:",
34
+ " • Questions test understanding, not rote memorisation of trivia.",
35
+ " • Exactly one correct answer; the others are plausible.",
36
+ " • Explanation of why the answer is correct — 1-2 sentences.",
37
+ "Reply with JSON ONLY on a single line:",
38
38
  ` {"questions":[{"stem":"...","options":["a","b","c","d"],"correctIndex":0,"explanation":"..."}, ...]}`,
39
39
  "",
40
- `## Материал модуля\n${trimmed}`,
40
+ `## Module material\n${trimmed}`,
41
41
  ].join("\n");
42
42
 
43
43
  const r = await reflex.llm.complete({ task: "quick", prompt });
@@ -35,21 +35,21 @@ export default async function generateTrainer(
35
35
  ): Promise<TrainerSpec> {
36
36
  const userBrief =
37
37
  (args.prompt ?? "").trim() ||
38
- `Спроектируй тренажёр, который помогает закрепить «${args.moduleObjective}».`;
38
+ `Design a trainer that helps reinforce "${args.moduleObjective}".`;
39
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).",
40
+ `Course/module: "${args.moduleTitle}".`,
41
+ "Build an interactive trainer a standalone HTML file with inline JS.",
42
+ "Requirements:",
43
+ " • One full <!doctype html> file: <head>, <body>, <script>, optional <style>.",
44
+ " • NO external resources: no CDN scripts, no image URLs, no fetch calls. Everything inline.",
45
+ " • Use <canvas> where appropriate (drawing, physics, geometry).",
46
+ " • Size ≈ 600×400 px (responsive).",
47
+ " • Clear interface: what is shown, what to do, instant feedback (right/wrong, score).",
48
+ " • No navigator/window globals that break inside a sandbox iframe (no localStorage, no parent).",
49
+ " • THE CODE MUST WORK. No placeholder functions.",
50
+ "Reply with HTML ONLY (no JSON wrapper, no markdown fence).",
51
51
  "",
52
- `## Идея тренажёра\n${userBrief}`,
52
+ `## Trainer idea\n${userBrief}`,
53
53
  ].join("\n");
54
54
 
55
55
  // Trainer HTML is long — give the agent a generous 7 minutes; worker
@@ -58,13 +58,13 @@ export default async function generateTrainer(
58
58
  const text = r.text ?? "";
59
59
  const html = extractHtml(text);
60
60
  if (!html) {
61
- throw new Error("Агент не вернул валидный HTML — попробуй ещё раз");
61
+ throw new Error("Agent did not return valid HTML — please try again");
62
62
  }
63
63
 
64
64
  const trainerId = `${args.moduleId}-${Date.now().toString(36)}`;
65
65
  const saved = await reflex.kb.add({
66
66
  kind: "course-trainer",
67
- title: `Тренажёр · ${args.moduleTitle}`,
67
+ title: `Trainer · ${args.moduleTitle}`,
68
68
  body: "```html\n" + html.slice(0, 30_000) + "\n```",
69
69
  meta: {
70
70
  courseId: args.courseId,
@@ -32,14 +32,14 @@ export default async function refreshCourseCard(): Promise<{ courses: number; av
32
32
  await reflex.cards.update({
33
33
  snapshot: {
34
34
  kind: "kpi",
35
- title: "🎓 Учусь",
35
+ title: "🎓 Learning",
36
36
  data: {
37
37
  items: [
38
- { label: "Активных курсов", value: String(courses) },
38
+ { label: "Active courses", value: String(courses) },
39
39
  {
40
- label: "Прогресс",
40
+ label: "Progress",
41
41
  value: courses === 0 ? "—" : `${avg}%`,
42
- hint: "среднее по курсам",
42
+ hint: "average across courses",
43
43
  ...(avg >= 60
44
44
  ? ({ delta: "up" } as const)
45
45
  : avg < 30 && courses > 0
@@ -8,8 +8,8 @@ import { extractJson } from "./_json";
8
8
  * `{done:true}` so the UI advances to outline generation.
9
9
  *
10
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 "хочу выучить
11
+ * about level/focus/format" — for "I want to learn pencil drawing"
12
+ * the agent will ask different questions than for "I want to learn
13
13
  * Python". The UI just renders whatever comes back.
14
14
  */
15
15
 
@@ -22,7 +22,7 @@ export interface TutorAskArgs {
22
22
  topic: string;
23
23
  history: TutorQA[];
24
24
  /** Force-finish even if the agent wants more. UI uses this when the
25
- * user clicks "Хватит вопросовдавай уже курс". */
25
+ * user clicks "Enough questionsbuild the course already". */
26
26
  forceFinish?: boolean;
27
27
  }
28
28
 
@@ -32,7 +32,7 @@ export interface TutorAskResult {
32
32
  question?: string;
33
33
  /** Optional pre-baked choices for a multiple-choice ask. */
34
34
  choices?: string[];
35
- /** Hint to UI: short label like "уровень", "формат", "цель". */
35
+ /** Hint to UI: short label like "level", "format", "goal". */
36
36
  header?: string;
37
37
  }
38
38
 
@@ -51,19 +51,19 @@ export default async function tutorAsk(
51
51
  .join("\n\n");
52
52
 
53
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
- " или",
54
+ "You are a tutor who is about to design a personalised course for the user.",
55
+ `Topic: "${args.topic}".`,
56
+ "You need to gather the minimum information required to choose a good program: level, goal, time budget, format, specifics.",
57
+ "Ask ONE question at a time. Decide what to ask yourself whatever is most important right now given the topic and prior answers.",
58
+ "If it makes sense to offer answer options add up to 5 short choices.",
59
+ "If you already know enough (usually after 2-4 questions) — return done=true.",
60
+ "Reply with JSON ONLY on a single line, no markdown:",
61
+ ` {"done":false,"question":"...","header":"level|goal|time|format|...","choices":["...","..."]}`,
62
+ " or",
63
63
  ` {"done":true}`,
64
64
  "",
65
- prior ? `## Предыдущие ответы\n${prior}` : "## Это первый вопрос",
66
- `\nЗадано вопросов до сих пор: ${turns} (максимум ${MAX_TURNS}).`,
65
+ prior ? `## Previous answers\n${prior}` : "## This is the first question",
66
+ `\nQuestions asked so far: ${turns} (maximum ${MAX_TURNS}).`,
67
67
  ].join("\n");
68
68
 
69
69
  const r = await reflex.llm.complete({ task: "quick", prompt });
@@ -74,7 +74,7 @@ export function ArticleView({
74
74
  node.style.fontFamily = "inherit";
75
75
  } catch (err) {
76
76
  const msg = err instanceof Error ? err.message : String(err);
77
- node.innerHTML = `<div class="text-xs text-red-600 not-italic">⚠ Не удалось отрисовать схему: ${escapeHtml(msg)}</div><pre class="mt-2 text-[11px] text-slate-700 whitespace-pre-wrap">${escapeHtml(src)}</pre>`;
77
+ node.innerHTML = `<div class="text-xs text-red-600 not-italic">⚠ Failed to render diagram: ${escapeHtml(msg)}</div><pre class="mt-2 text-[11px] text-slate-700 whitespace-pre-wrap">${escapeHtml(src)}</pre>`;
78
78
  node.classList.add("reflex-mermaid-done");
79
79
  }
80
80
  }
@@ -100,7 +100,7 @@ export function ArticleView({
100
100
  <img
101
101
  src={url}
102
102
  alt={safeAlt}
103
- title={safeAlt || "Открыть на весь экран"}
103
+ title={safeAlt || "Open fullscreen"}
104
104
  loading="lazy"
105
105
  onClick={() => setZoom({ src: url, alt: safeAlt })}
106
106
  className="my-5 rounded-lg border bg-white max-w-full h-auto cursor-zoom-in transition hover:opacity-90"
@@ -164,7 +164,7 @@ export function ArticleImageLightbox({
164
164
  <div
165
165
  onClick={onClose}
166
166
  role="dialog"
167
- aria-label={alt || "Просмотр изображения"}
167
+ aria-label={alt || "Image preview"}
168
168
  style={{
169
169
  position: "fixed",
170
170
  inset: 0,
@@ -184,7 +184,7 @@ export function ArticleImageLightbox({
184
184
  e.stopPropagation();
185
185
  onClose();
186
186
  }}
187
- aria-label="Закрыть"
187
+ aria-label="Close"
188
188
  style={{
189
189
  position: "absolute",
190
190
  top: 16,
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "id": "learn-anything",
3
- "name": "🎓 Изучи что угодно",
3
+ "name": "🎓 Learn Anything",
4
4
  "version": "0.4.7",
5
- "description": "Универсальный AI-наставник: курсы под твой уровень с контентом, тестами и тренажёрами",
5
+ "description": "Universal AI tutor: courses tailored to your level with content, quizzes, and trainers",
6
6
  "author": "reflex-org",
7
7
  "category": "study",
8
8
  "ui": "ui.tsx",
9
9
  "icon": "lucide:GraduationCap",
10
10
  "card": {
11
11
  "kind": "kpi",
12
- "title": "🎓 Учусь",
12
+ "title": "🎓 Learning",
13
13
  "data": {
14
14
  "items": [
15
- { "label": "Активных курсов", "value": "0" },
16
- { "label": "Прогресс", "value": "—", "hint": "среднее по курсам" }
15
+ { "label": "Active courses", "value": "0" },
16
+ { "label": "Progress", "value": "—", "hint": "average across courses" }
17
17
  ]
18
18
  }
19
19
  },