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,487 +0,0 @@
|
|
|
1
|
-
import { reflex } from "@host/api";
|
|
2
|
-
import { callJsonAgent, snippet } from "./_json";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Compile a learning module: agent writes a full markdown article, then
|
|
6
|
-
* Reflex resolves the image needs (real photos via search → local
|
|
7
|
-
* download, schematics via generation) and embeds permanent local URLs
|
|
8
|
-
* into the body. Persisted as a `course-module` KB entry, idempotent on
|
|
9
|
-
* (courseId, moduleId).
|
|
10
|
-
*
|
|
11
|
-
* Why image queries/prompts instead of raw URLs:
|
|
12
|
-
* The old contract asked the LLM to suggest image URLs from its
|
|
13
|
-
* training corpus. ~90% of those URLs were dead or hallucinated.
|
|
14
|
-
* Now the LLM emits *intent* (what to find / what to draw) and
|
|
15
|
-
* reflex.images.search + reflex.images.generate do the real work,
|
|
16
|
-
* yielding stable `/api/images/<rootId>/<sha>.<ext>` URLs that move
|
|
17
|
-
* with the project.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
export interface ModuleContent {
|
|
21
|
-
courseId: string;
|
|
22
|
-
moduleId: string;
|
|
23
|
-
title: string;
|
|
24
|
-
/** Long-form markdown body for the article view. */
|
|
25
|
-
article: string;
|
|
26
|
-
videos: Array<{ title: string; url: string; note?: string }>;
|
|
27
|
-
links: Array<{ title: string; url: string; snippet?: string }>;
|
|
28
|
-
/** Resolved images: each has a permanent `/api/images/...` URL. */
|
|
29
|
-
images: Array<{
|
|
30
|
-
/** Stable id from the LLM draft; used to substitute inline `[[IMG:<id>]]` placeholders. */
|
|
31
|
-
id: string;
|
|
32
|
-
alt: string;
|
|
33
|
-
url: string;
|
|
34
|
-
source: "search" | "generated";
|
|
35
|
-
attribution?: { name: string; link: string };
|
|
36
|
-
}>;
|
|
37
|
-
/** Mermaid diagrams (kept for structural schemas like flowcharts). */
|
|
38
|
-
diagrams: Array<{ title: string; mermaid: string }>;
|
|
39
|
-
homework: string[];
|
|
40
|
-
relPath: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface BuildModuleArgs {
|
|
44
|
-
courseId: string;
|
|
45
|
-
moduleId: string;
|
|
46
|
-
moduleTitle: string;
|
|
47
|
-
moduleObjective: string;
|
|
48
|
-
topic: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface LlmDraft {
|
|
52
|
-
article?: string;
|
|
53
|
-
videos?: Array<{ title: string; url: string; note?: string }>;
|
|
54
|
-
links?: Array<{ title: string; url: string; snippet?: string }>;
|
|
55
|
-
/**
|
|
56
|
-
* "Find a real photo of X". Each carries an `id` so the LLM can drop
|
|
57
|
-
* an inline `[[IMG:<id>]]` placeholder into `article` and we know
|
|
58
|
-
* where to embed the resolved image.
|
|
59
|
-
*/
|
|
60
|
-
imageQueries?: Array<{ id: string; alt: string; query: string }>;
|
|
61
|
-
/** Same id-based contract as `imageQueries`, but the resolver calls
|
|
62
|
-
* reflex.images.generate instead of search. */
|
|
63
|
-
generatedFigures?: Array<{ id: string; alt: string; prompt: string }>;
|
|
64
|
-
/** Mermaid code for diagrams that genuinely need flow/sequence syntax. */
|
|
65
|
-
diagrams?: Array<{ title: string; mermaid: string }>;
|
|
66
|
-
homework?: string[];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export default async function buildModule(
|
|
70
|
-
args: BuildModuleArgs,
|
|
71
|
-
): Promise<ModuleContent> {
|
|
72
|
-
// 1. Pull a few web sources to ground the article — saves the agent from
|
|
73
|
-
// hallucinating sources. Best-effort; failures are silent.
|
|
74
|
-
let webContext = "";
|
|
75
|
-
try {
|
|
76
|
-
const search = await reflex.web.search({
|
|
77
|
-
query: `${args.topic} ${args.moduleTitle}`,
|
|
78
|
-
});
|
|
79
|
-
const top = (search.results ?? []).slice(0, 4);
|
|
80
|
-
webContext = top
|
|
81
|
-
.map(
|
|
82
|
-
(r, i) =>
|
|
83
|
-
`[${i + 1}] ${r.title}\n ${r.url}\n ${r.snippet ?? ""}`,
|
|
84
|
-
)
|
|
85
|
-
.join("\n");
|
|
86
|
-
} catch {
|
|
87
|
-
/* offline / no search — agent will rely on training data */
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// 2. Ask the agent to draft the module. Note: NO bare image URLs —
|
|
91
|
-
// the LLM tells us WHAT to look for / draw; Reflex resolves it via
|
|
92
|
-
// reflex.images.search (Brave when available) + reflex.images.generate.
|
|
93
|
-
const prompt = [
|
|
94
|
-
`Курс: «${args.topic}». Модуль: «${args.moduleTitle}» — ${args.moduleObjective}.`,
|
|
95
|
-
"Подготовь учебный материал. Структура JSON-ответа:",
|
|
96
|
-
"{",
|
|
97
|
-
` "article": "длинный markdown 800-2000 слов; используй # ## ### и плейсхолдеры [[IMG:<id>]] для inline-картинок",`,
|
|
98
|
-
` "videos": [{"title":"...","url":"https://youtube.com/...","note":"..."}],`,
|
|
99
|
-
` "links": [{"title":"...","url":"...","snippet":"..."}],`,
|
|
100
|
-
` "imageQueries": [{"id":"i1","alt":"...","query":"короткий английский поисковый запрос"}],`,
|
|
101
|
-
` "generatedFigures": [{"id":"f1","alt":"...","prompt":"подробное описание для AI-генератора, английский"}],`,
|
|
102
|
-
` "diagrams": [{"title":"...","mermaid":"graph TD; A-->B;"}],`,
|
|
103
|
-
` "homework": ["...","..."]`,
|
|
104
|
-
"}",
|
|
105
|
-
"",
|
|
106
|
-
"## ВИЗУАЛЬНОЕ СОПРОВОЖДЕНИЕ — ОБЯЗАТЕЛЬНО + INLINE-РАЗМЕЩЕНИЕ",
|
|
107
|
-
"",
|
|
108
|
-
"Любой учебный модуль ОБЯЗАН быть визуально насыщенным. Каждая картинка ставится **inline** в нужном месте текста через плейсхолдер.",
|
|
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>]]` на `` с атрибуцией,",
|
|
118
|
-
" – неотрезолвленные id (картинка не найдена / отклонена) удаляются из текста чисто.",
|
|
119
|
-
"",
|
|
120
|
-
"### Пример",
|
|
121
|
-
"```markdown",
|
|
122
|
-
"## Зрительная кора",
|
|
123
|
-
"Зрительная кора V1 — первая зона обработки информации от сетчатки.",
|
|
124
|
-
"",
|
|
125
|
-
"[[IMG:i1]]",
|
|
126
|
-
"",
|
|
127
|
-
"Колончатая организация V1 была описана Хьюбелом и Визелом в 1962 году...",
|
|
128
|
-
"",
|
|
129
|
-
"[[IMG:f1]]",
|
|
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\"}]`.",
|
|
132
|
-
"",
|
|
133
|
-
"Твоя задача — заполнить два массива И расставить плейсхолдеры в article:",
|
|
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` — ставь его в тематически подходящем месте.",
|
|
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 ровно где эта схема нужна.",
|
|
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 практических заданий с проверяемым результатом.",
|
|
159
|
-
"",
|
|
160
|
-
"Верни ТОЛЬКО JSON одной строкой, без markdown-фенс.",
|
|
161
|
-
"",
|
|
162
|
-
webContext
|
|
163
|
-
? `## Web-источники для опоры\n${webContext}`
|
|
164
|
-
: "## Web-источники недоступны — опирайся на свои знания.",
|
|
165
|
-
].join("\n");
|
|
166
|
-
|
|
167
|
-
const result = await callJsonAgent<LlmDraft>({
|
|
168
|
-
prompt,
|
|
169
|
-
invoke: (p) => reflex.agent.invoke({ prompt: p, timeoutMs: 7 * 60_000 }),
|
|
170
|
-
maxAttempts: 4,
|
|
171
|
-
shapeHint:
|
|
172
|
-
`{"article":"...","videos":[],"links":[],"imageQueries":[],"generatedFigures":[],"diagrams":[],"homework":[]}\n` +
|
|
173
|
-
`article — markdown 800-2000 слов. Все массивы — обязательно массивы (можно пустые).`,
|
|
174
|
-
validate: (p) => {
|
|
175
|
-
const v = p as LlmDraft;
|
|
176
|
-
return typeof v?.article === "string" && v.article.trim().length > 40
|
|
177
|
-
? v
|
|
178
|
-
: null;
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
if (!result.ok) {
|
|
182
|
-
throw new Error(
|
|
183
|
-
`Не удалось собрать модуль за ${result.attempts} попыток (${result.reason}). ` +
|
|
184
|
-
`Последний ответ: «${snippet(result.lastText, 200)}».`,
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
const draft = result.value;
|
|
188
|
-
|
|
189
|
-
// 3. Resolve image needs in parallel. Each call is best-effort —
|
|
190
|
-
// a failed search or generation just drops that image from the module
|
|
191
|
-
// (we don't want one flaky API to fail the whole build).
|
|
192
|
-
const [searchedImages, generatedImages] = await Promise.all([
|
|
193
|
-
resolveSearches(draft.imageQueries ?? [], args.topic, args.moduleTitle),
|
|
194
|
-
resolveGenerations(draft.generatedFigures ?? []),
|
|
195
|
-
]);
|
|
196
|
-
|
|
197
|
-
// Inline-place images by substituting [[IMG:<id>]] placeholders in the
|
|
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.
|
|
201
|
-
const allImages = [...searchedImages, ...generatedImages];
|
|
202
|
-
const { article: articleWithImages, placedIds } = substituteImagePlaceholders(
|
|
203
|
-
typeof draft.article === "string" ? draft.article : "",
|
|
204
|
-
allImages,
|
|
205
|
-
);
|
|
206
|
-
const articleClean = stripPlaceholderMarkers(articleWithImages);
|
|
207
|
-
const unplaced = allImages.filter((im) => !placedIds.has(im.id));
|
|
208
|
-
|
|
209
|
-
const content: Omit<ModuleContent, "relPath"> = {
|
|
210
|
-
courseId: args.courseId,
|
|
211
|
-
moduleId: args.moduleId,
|
|
212
|
-
title: args.moduleTitle,
|
|
213
|
-
article: articleClean,
|
|
214
|
-
videos: sanitizeArr(draft.videos, ["title", "url"]) as ModuleContent["videos"],
|
|
215
|
-
links: sanitizeArr(draft.links, ["title", "url"]) as ModuleContent["links"],
|
|
216
|
-
images: allImages,
|
|
217
|
-
diagrams: sanitizeArr(draft.diagrams, ["title", "mermaid"]) as ModuleContent["diagrams"],
|
|
218
|
-
homework: Array.isArray(draft.homework)
|
|
219
|
-
? draft.homework.map(String).filter(Boolean).slice(0, 8)
|
|
220
|
-
: [],
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// 4. Persist as a KB entry. Frontmatter holds the structured bits;
|
|
224
|
-
// body = article with images already inlined + optional fallback
|
|
225
|
-
// section for images the LLM declared but never referenced.
|
|
226
|
-
const body = [
|
|
227
|
-
content.article,
|
|
228
|
-
unplaced.length > 0
|
|
229
|
-
? "\n\n## Дополнительные иллюстрации\n" +
|
|
230
|
-
unplaced
|
|
231
|
-
.map((im) => `${renderInlineImage(im)}`)
|
|
232
|
-
.join("\n\n")
|
|
233
|
-
: "",
|
|
234
|
-
content.diagrams.length > 0
|
|
235
|
-
? "\n\n## Схемы\n" +
|
|
236
|
-
content.diagrams
|
|
237
|
-
.map(
|
|
238
|
-
(d) =>
|
|
239
|
-
`### ${d.title}\n\n\`\`\`mermaid\n${d.mermaid}\n\`\`\``,
|
|
240
|
-
)
|
|
241
|
-
.join("\n\n")
|
|
242
|
-
: "",
|
|
243
|
-
]
|
|
244
|
-
.filter(Boolean)
|
|
245
|
-
.join("");
|
|
246
|
-
|
|
247
|
-
const saved = await reflex.kb.add({
|
|
248
|
-
kind: "course-module",
|
|
249
|
-
title: `${args.topic} · ${args.moduleTitle}`,
|
|
250
|
-
body,
|
|
251
|
-
meta: {
|
|
252
|
-
courseId: args.courseId,
|
|
253
|
-
moduleId: args.moduleId,
|
|
254
|
-
videos: JSON.stringify(content.videos),
|
|
255
|
-
links: JSON.stringify(content.links),
|
|
256
|
-
images: JSON.stringify(content.images),
|
|
257
|
-
diagrams: JSON.stringify(content.diagrams),
|
|
258
|
-
homework: JSON.stringify(content.homework),
|
|
259
|
-
title: args.moduleTitle,
|
|
260
|
-
objective: args.moduleObjective,
|
|
261
|
-
},
|
|
262
|
-
slug: `${args.courseId}-${args.moduleId}`,
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
await reflex.audit.log({
|
|
266
|
-
type: "module-built",
|
|
267
|
-
payload: {
|
|
268
|
-
courseId: args.courseId,
|
|
269
|
-
moduleId: args.moduleId,
|
|
270
|
-
videos: content.videos.length,
|
|
271
|
-
images: content.images.length,
|
|
272
|
-
searched: searchedImages.length,
|
|
273
|
-
generated: generatedImages.length,
|
|
274
|
-
},
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
return { ...content, relPath: saved.relPath };
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* For each query: do a web image search (5 candidates), ask the LLM
|
|
282
|
-
* which best fits the course material, attach the chosen one. If the
|
|
283
|
-
* LLM rejects all candidates (-1), the image slot is left empty — better
|
|
284
|
-
* than embedding clip-art or off-topic stock photo. Try/catch isolates
|
|
285
|
-
* per-image failures from killing the module build.
|
|
286
|
-
*/
|
|
287
|
-
async function resolveSearches(
|
|
288
|
-
queries: Array<{ id?: unknown; alt?: unknown; query?: unknown }>,
|
|
289
|
-
topic: string,
|
|
290
|
-
moduleTitle: string,
|
|
291
|
-
): Promise<ModuleContent["images"]> {
|
|
292
|
-
const clean = queries
|
|
293
|
-
.filter(
|
|
294
|
-
(q) =>
|
|
295
|
-
q &&
|
|
296
|
-
typeof q.query === "string" &&
|
|
297
|
-
q.query.trim().length > 0,
|
|
298
|
-
)
|
|
299
|
-
.slice(0, 4);
|
|
300
|
-
const out = await Promise.all(
|
|
301
|
-
clean.map(async (q, idx) => {
|
|
302
|
-
try {
|
|
303
|
-
const query = q.query as string;
|
|
304
|
-
const alt = typeof q.alt === "string" ? q.alt : query;
|
|
305
|
-
const id =
|
|
306
|
-
typeof q.id === "string" && q.id.trim().length > 0
|
|
307
|
-
? q.id.trim()
|
|
308
|
-
: `i${idx + 1}`;
|
|
309
|
-
// Default provider: Brave for breadth (real web), falls back to
|
|
310
|
-
// Unsplash/Pexels via service-router based on which key exists.
|
|
311
|
-
const search = await reflex.images.search({ query, count: 5 });
|
|
312
|
-
if (search.results.length === 0) return null;
|
|
313
|
-
// Vision-based pick: Reflex spawns the user's chat harness
|
|
314
|
-
// (Codex / Claude Code) on the candidate thumbnails and asks it
|
|
315
|
-
// to choose. The agent uses its Read tool — both runtimes get
|
|
316
|
-
// real vision content for image files, so off-topic results
|
|
317
|
-
// (clipart, mislabelled photos) are filtered by content, not
|
|
318
|
-
// metadata.
|
|
319
|
-
const pick = await reflex.images.pickBest({
|
|
320
|
-
query,
|
|
321
|
-
alt,
|
|
322
|
-
context: `${topic} → ${moduleTitle}`,
|
|
323
|
-
candidates: search.results.map((r) => ({
|
|
324
|
-
url: r.url,
|
|
325
|
-
thumb: r.thumb,
|
|
326
|
-
attribution: r.attribution,
|
|
327
|
-
})),
|
|
328
|
-
});
|
|
329
|
-
if (pick.pickIndex < 0 || pick.pickIndex >= search.results.length) {
|
|
330
|
-
void reflex.audit.log({
|
|
331
|
-
type: "image-rejected",
|
|
332
|
-
payload: { query, reason: pick.reason, via: pick.via },
|
|
333
|
-
});
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
const chosen = search.results[pick.pickIndex];
|
|
337
|
-
const attached = await reflex.images.attach({
|
|
338
|
-
sourceUrl: chosen.url,
|
|
339
|
-
});
|
|
340
|
-
return {
|
|
341
|
-
id,
|
|
342
|
-
alt,
|
|
343
|
-
url: attached.url,
|
|
344
|
-
source: "search" as const,
|
|
345
|
-
attribution: chosen.attribution,
|
|
346
|
-
};
|
|
347
|
-
} catch (err) {
|
|
348
|
-
// Log to audit; the module survives without this image.
|
|
349
|
-
void reflex.audit.log({
|
|
350
|
-
type: "image-search-failed",
|
|
351
|
-
payload: {
|
|
352
|
-
query: q.query,
|
|
353
|
-
error: err instanceof Error ? err.message : String(err),
|
|
354
|
-
},
|
|
355
|
-
});
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
}),
|
|
359
|
-
);
|
|
360
|
-
return out.filter((x): x is NonNullable<typeof x> => x !== null);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Walk through the article body, find `[[IMG:<id>]]` placeholders, and
|
|
365
|
-
* replace each with a markdown image reference using the resolved
|
|
366
|
-
* image's URL + alt + attribution.
|
|
367
|
-
*
|
|
368
|
-
* Returns the rewritten article + the set of ids successfully placed.
|
|
369
|
-
* Unknown placeholders (id has no matching resolved image) get stripped
|
|
370
|
-
* — likely a generation/search failure that already logged to audit.
|
|
371
|
-
*
|
|
372
|
-
* Whitespace around inline placeholders is normalized so the image sits
|
|
373
|
-
* as its own block (Markdown then renders `` as a paragraph).
|
|
374
|
-
*/
|
|
375
|
-
function substituteImagePlaceholders(
|
|
376
|
-
article: string,
|
|
377
|
-
images: ModuleContent["images"],
|
|
378
|
-
): { article: string; placedIds: Set<string> } {
|
|
379
|
-
const byId = new Map(images.map((im) => [im.id, im]));
|
|
380
|
-
const placedIds = new Set<string>();
|
|
381
|
-
// Accept variants: `[[IMG:i1]]`, `[[img:i1]]`, `[[IMG: i1 ]]`,
|
|
382
|
-
// `[[IMG i1]]`, and even single-bracket `[IMG:i1]` from sloppy LLMs.
|
|
383
|
-
const re = /\[\[?\s*IMG\s*[:\s]\s*([A-Za-z0-9_-]+)\s*\]\]?/gi;
|
|
384
|
-
const replaced = article.replace(re, (_, rawId: string) => {
|
|
385
|
-
const id = rawId.trim();
|
|
386
|
-
const img = byId.get(id);
|
|
387
|
-
if (!img) return "";
|
|
388
|
-
placedIds.add(id);
|
|
389
|
-
return `\n\n${renderInlineImage(img)}\n\n`;
|
|
390
|
-
});
|
|
391
|
-
return { article: replaced, placedIds };
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function renderInlineImage(im: ModuleContent["images"][number]): string {
|
|
395
|
-
const safeAlt = im.alt.replace(/[\[\]\n]/g, " ").slice(0, 200);
|
|
396
|
-
const credit =
|
|
397
|
-
im.source === "search" && im.attribution?.name
|
|
398
|
-
? `\n\n_Источник: [${im.attribution.name}](${im.attribution.link || im.url})_`
|
|
399
|
-
: im.source === "generated"
|
|
400
|
-
? `\n\n_Сгенерировано AI_`
|
|
401
|
-
: "";
|
|
402
|
-
return `${credit}`;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Strip residual `[[ИЛЛЮСТРАЦИЯ: ...]]` / `[[СХЕМА: ...]]` / orphan
|
|
407
|
-
* `[[IMG:<unknown-id>]]` placeholders the LLM might emit despite the
|
|
408
|
-
* prompt forbidding them OR after `substituteImagePlaceholders` failed
|
|
409
|
-
* to find the id. Drops the marker, collapses surrounding whitespace.
|
|
410
|
-
*
|
|
411
|
-
* Matches Russian variants + Latin spellings for safety. Case-insensitive.
|
|
412
|
-
*/
|
|
413
|
-
function stripPlaceholderMarkers(text: string): string {
|
|
414
|
-
const re =
|
|
415
|
-
/\[\[?\s*(?:ИЛЛЮСТРАЦИЯ|СХЕМА|ILLUSTRATION|SCHEME|IMAGE|IMG|DIAGRAM)\b[^\]]*?\]\]?/giu;
|
|
416
|
-
let out = text.replace(re, "");
|
|
417
|
-
// Clean up double blank lines + trailing whitespace the removal may
|
|
418
|
-
// have left behind.
|
|
419
|
-
out = out.replace(/[ \t]+\n/g, "\n");
|
|
420
|
-
out = out.replace(/\n{3,}/g, "\n\n");
|
|
421
|
-
return out.trim() + "\n";
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
async function resolveGenerations(
|
|
425
|
-
figures: Array<{ id?: unknown; alt?: unknown; prompt?: unknown }>,
|
|
426
|
-
): Promise<ModuleContent["images"]> {
|
|
427
|
-
const clean = figures
|
|
428
|
-
.filter(
|
|
429
|
-
(f) =>
|
|
430
|
-
f &&
|
|
431
|
-
typeof f.prompt === "string" &&
|
|
432
|
-
f.prompt.trim().length > 0,
|
|
433
|
-
)
|
|
434
|
-
.slice(0, 2);
|
|
435
|
-
const out = await Promise.all(
|
|
436
|
-
clean.map(async (f, idx) => {
|
|
437
|
-
try {
|
|
438
|
-
const gen = await reflex.images.generate({
|
|
439
|
-
prompt: f.prompt as string,
|
|
440
|
-
aspectRatio: "16:9",
|
|
441
|
-
});
|
|
442
|
-
const id =
|
|
443
|
-
typeof f.id === "string" && f.id.trim().length > 0
|
|
444
|
-
? f.id.trim()
|
|
445
|
-
: `f${idx + 1}`;
|
|
446
|
-
return {
|
|
447
|
-
id,
|
|
448
|
-
alt: typeof f.alt === "string" ? f.alt : (f.prompt as string).slice(0, 80),
|
|
449
|
-
url: gen.url,
|
|
450
|
-
source: "generated" as const,
|
|
451
|
-
};
|
|
452
|
-
} catch (err) {
|
|
453
|
-
void reflex.audit.log({
|
|
454
|
-
type: "image-generate-failed",
|
|
455
|
-
payload: {
|
|
456
|
-
prompt: f.prompt,
|
|
457
|
-
error: err instanceof Error ? err.message : String(err),
|
|
458
|
-
},
|
|
459
|
-
});
|
|
460
|
-
return null;
|
|
461
|
-
}
|
|
462
|
-
}),
|
|
463
|
-
);
|
|
464
|
-
return out.filter((x): x is NonNullable<typeof x> => x !== null);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
function sanitizeArr(
|
|
468
|
-
v: unknown,
|
|
469
|
-
required: string[],
|
|
470
|
-
): Array<Record<string, string>> {
|
|
471
|
-
if (!Array.isArray(v)) return [];
|
|
472
|
-
const out: Array<Record<string, string>> = [];
|
|
473
|
-
for (const item of v.slice(0, 12)) {
|
|
474
|
-
if (typeof item !== "object" || item === null) continue;
|
|
475
|
-
const o = item as Record<string, unknown>;
|
|
476
|
-
const ok = required.every(
|
|
477
|
-
(k) => typeof o[k] === "string" && (o[k] as string).trim() !== "",
|
|
478
|
-
);
|
|
479
|
-
if (!ok) continue;
|
|
480
|
-
const row: Record<string, string> = {};
|
|
481
|
-
for (const [k, val] of Object.entries(o)) {
|
|
482
|
-
if (typeof val === "string") row[k] = val;
|
|
483
|
-
}
|
|
484
|
-
out.push(row);
|
|
485
|
-
}
|
|
486
|
-
return out;
|
|
487
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { reflex } from "@host/api";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* "Explain this" feature: user highlights a snippet inside the module
|
|
5
|
-
* article and asks for a deeper explanation. The agent gets the
|
|
6
|
-
* surrounding paragraph as context so it understands what "это" means.
|
|
7
|
-
*
|
|
8
|
-
* Two modes:
|
|
9
|
-
* 1. Default — `question` omitted, agent gives a generic 2-5 paragraph
|
|
10
|
-
* breakdown of the selected fragment.
|
|
11
|
-
* 2. Custom — `question` supplied (book-style margin annotation), agent
|
|
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.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export interface ExplainSelectionArgs {
|
|
18
|
-
selection: string;
|
|
19
|
-
/** ~400-1000 chars around the selection for context. */
|
|
20
|
-
context: string;
|
|
21
|
-
topic: string;
|
|
22
|
-
moduleTitle: string;
|
|
23
|
-
/** Optional user question about the selection. */
|
|
24
|
-
question?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export default async function explainSelection(
|
|
28
|
-
args: ExplainSelectionArgs,
|
|
29
|
-
): Promise<{ text: string }> {
|
|
30
|
-
const userQuestion = (args.question ?? "").trim();
|
|
31
|
-
const promptLines: string[] = [
|
|
32
|
-
`Курс: «${args.topic}». Модуль: «${args.moduleTitle}».`,
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
if (userQuestion) {
|
|
36
|
-
promptLines.push(
|
|
37
|
-
"Пользователь выделил фрагмент и задал конкретный вопрос про этот фрагмент.",
|
|
38
|
-
"Ответь именно на его вопрос, опираясь на выделение + окружающий контекст. 2-4 абзаца, без воды, по делу.",
|
|
39
|
-
"Markdown без заголовков; короткие фразы, пример если уместен.",
|
|
40
|
-
);
|
|
41
|
-
} else {
|
|
42
|
-
promptLines.push(
|
|
43
|
-
"Пользователь выделил фрагмент и просит объяснить подробнее.",
|
|
44
|
-
"Дай развёрнутое объяснение в 2-5 абзацах: что это значит, как работает,",
|
|
45
|
-
"почему именно так, конкретный пример. Без воды. Markdown без заголовков.",
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
promptLines.push(
|
|
50
|
-
"",
|
|
51
|
-
`## Окружающий контекст\n${args.context.slice(0, 1500)}`,
|
|
52
|
-
"",
|
|
53
|
-
`## Выделение пользователя\n«${args.selection.slice(0, 800)}»`,
|
|
54
|
-
);
|
|
55
|
-
if (userQuestion) {
|
|
56
|
-
promptLines.push("", `## Вопрос пользователя\n${userQuestion.slice(0, 600)}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const r = await reflex.llm.complete({
|
|
60
|
-
task: "quick",
|
|
61
|
-
prompt: promptLines.join("\n"),
|
|
62
|
-
});
|
|
63
|
-
return { text: (r.text ?? "").trim() };
|
|
64
|
-
}
|