Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions deeptutor/agents/visualize/agents/code_generator_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from deeptutor.core.trace import build_trace_metadata, new_call_id

from ..models import VisualizationAnalysis
from ..utils import extract_code_block
from ..utils import build_widget_from_spec, extract_code_block, extract_json_object, is_widget_spec


class CodeGeneratorAgent(BaseAgent):
Expand Down Expand Up @@ -73,15 +73,23 @@ async def process(
else:
lang_hint = "javascript"

extracted = extract_code_block(response, lang_hint) or extract_code_block(response)

# For html, the model sometimes returns the full document with no fence.
# `extract_code_block` will then return the trimmed raw response — accept
# it as long as it looks like an HTML document.
if analysis.render_type == "html" and not extracted:
# ── HTML: try JSON widget spec first, then fall back to raw HTML ──────
if analysis.render_type == "html":
try:
spec = extract_json_object(response)
if is_widget_spec(spec):
return build_widget_from_spec(spec)
except Exception:
pass
# Fallback: accept raw HTML document if LLM returned one directly
stripped = (response or "").strip()
lowered = stripped.lower()
if lowered.startswith("<!doctype") or lowered.startswith("<html"):
return stripped
extracted = extract_code_block(response, "html")
if extracted:
return extracted
return stripped

extracted = extract_code_block(response, lang_hint) or extract_code_block(response)
return extracted
25 changes: 23 additions & 2 deletions deeptutor/agents/visualize/prompts/en/answer_now.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
system: |
You are DeepTutor's visualization code generator. The user is waiting,
so emit the final renderable code in one shot. Output strictly the JSON
{"render_type": "svg|chartjs|mermaid", "code": "..."}, where ``code`` is
the renderable source (SVG markup, Chart.js JS, or Mermaid DSL).
{"render_type": "svg|chartjs|mermaid|html", "code": "..."}, where ``code`` is
the renderable source (SVG markup, Chart.js JS, Mermaid DSL).

EXCEPTION for html: when render_type is "html", set "code" to a JSON STRING
containing a widget spec object (not a full HTML document). The host app has a
fixed Google-style shell; you only provide data + logic:

{
"render_type": "html",
"code": "{\"metrics\":[{\"id\":\"m1\",\"label\":\"K-Speed\",\"value\":\"50\",\"unit\":\"km/h\"},...],
\"canvas_type\":\"svg\",
\"canvas_html\":\"<svg id=\\\"dt-svg\\\" viewBox=\\\"0 0 800 280\\\">...</svg>\",
\"controls\":[{\"type\":\"slider\",\"id\":\"spd\",\"label\":\"Speed (km/h)\",
\"min\":10,\"max\":100,\"step\":1,\"value\":50}],
\"update_js\":\"var d=values.spd*10; updateMetric('m1',d+' km'); dtSvg.getElementById('bar').setAttribute('width',d/800*760);\"}"
}

Widget spec rules:
- metrics: 3–5 entries. IDs unique.
- canvas_html: complete SVG with id="dt-svg" and all shapes having unique IDs.
- controls: sliders for numbers, toggles for booleans.
- update_js: must call updateMetric() for every metric AND update SVG/canvas.
- NO titles, headings, solution text, or explanations anywhere in the spec.

user_template: |
User request: {original}
Expand Down
82 changes: 61 additions & 21 deletions deeptutor/agents/visualize/prompts/en/code_generator_agent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,76 @@ system: |
Rules:
- If render_type is "svg", output a complete, self-contained SVG string.
The SVG must include the xmlns attribute and be well-formed XML.
Use viewBox for responsive sizing. Prefer clean, modern aesthetics with
readable fonts, clear colors, and proper spacing.
Use viewBox="0 0 900 480" unless the user explicitly needs another ratio.
Design it as a compact in-answer diagram, not a poster or full lesson page:
* Everything must fit inside the viewBox with 32px margins.
* Use one primary diagram area, not multiple large cards or sections.
* Keep labels short and tied to the written solution (same names/quantities).
* Use at most 6 prominent labels and at most 2 accent colors.
* Avoid large title banners, huge headings, explanatory paragraphs, and
dark formula blocks inside the SVG; the app renders the derivation below.
* Prefer the Google-style pattern: a small metric strip only when useful,
a simple axis/lane/geometry, direct annotations, and generous empty space.
* Text must never be clipped or placed outside the viewBox.
Prefer clean, minimal aesthetics with readable fonts, clear colors, and proper spacing.
- If render_type is "chartjs", output a valid JavaScript object literal that
can be passed as the configuration to `new Chart(ctx, config)`.
The config must include `type`, `data`, and `options` fields.
Use modern color palettes and ensure labels are readable.
Set options.maintainAspectRatio=false and keep labels readable in a compact panel.
- If render_type is "mermaid", output valid Mermaid.js diagram code.
The code must start with a valid diagram type keyword (graph, flowchart,
sequenceDiagram, classDiagram, stateDiagram-v2, erDiagram, gantt, mindmap, etc.).
Use clear node labels and readable edge descriptions. Do NOT use spaces in
node IDs — use camelCase or underscores. Avoid reserved keywords as node IDs.
- If render_type is "html", output one complete, self-contained single-file HTML
learning page.
Technical constraints:
* Must include everything from <!DOCTYPE html> to </html>.
* Put all CSS inside <style>; put all JavaScript inside <script> before </body>.
* Do NOT rely on external CDNs, fonts, or other resources (KaTeX is injected
by the host page; use $...$ for inline math, $$...$$ for block math).
* The page will render inside an iframe — use max-width: 100% and
box-sizing: border-box, avoid fixed widths.
* For long content use overflow-x: auto when needed; never let content be
clipped.
* Wrap all interactions inside DOMContentLoaded and use addEventListener;
do NOT use inline onclick handlers.
* Always check that an element exists before manipulating it.
Within these constraints, freely choose layout, colors, visual style, and
interaction patterns to make the page clear, beautiful, and easy to learn from.
- If render_type is "html", output a JSON widget spec (NOT a full HTML page).
The host app has a fixed Google-style shell that handles all layout, CSS, and
controls wiring. You only provide the DATA and LOGIC.

Output a ```json code block containing exactly this schema:

{
"metrics": [
{"id": "unique_id", "label": "SHORT LABEL", "value": "50", "unit": "km/h"},
... (3–5 entries)
],
"canvas_type": "svg",
"canvas_html": "<svg viewBox=\"0 0 800 280\" xmlns=\"...\" id=\"dt-svg\">
... complete SVG content with all shapes/labels ...
</svg>",
"controls": [
{"type": "slider", "id": "ctrl_id", "label": "Label (unit)",
"min": 10, "max": 100, "step": 1, "value": 50},
{"type": "toggle", "id": "ctrl_id", "label": "Label", "value": false}
],
"update_js": "// Plain JS that runs on every control change.
// Available: values (object of {id: currentValue}),
// updateMetric(id, text) to update metric pills,
// dtSvg (the SVG element, for svg canvas_type).
// Example for svg:
// var dist = values.speed * values.time;
// updateMetric('m_dist', dist.toFixed(1) + ' km');
// dtSvg.getElementById('bar').setAttribute('width', dist/800*760);
"
}

For canvas2d instead of svg, use:
"canvas_type": "canvas2d",
"canvas_html": "",
"draw_js": "// Body of: function dtDraw(values) { var w=..., h=...
// dtCtx.clearRect(0,0,w,h); ... draw here ... }"

Rules for the spec:
- metrics: 3–5 short-label, live-updating key metrics. IDs must be unique.
- canvas_html: complete SVG with all initial shapes. Give interactive
elements unique id attributes so update_js can target them.
- controls: sliders for numeric variables, toggles for boolean flags.
- update_js: must call updateMetric() for EVERY metric on every change.
Must also update SVG attributes or call redraw for canvas2d.
- No titles, headings, explanations, or solution text anywhere in the spec.

- Do NOT include any explanation outside the code fence.
- Wrap the code in a fenced code block with the appropriate language tag
(```svg, ```javascript, ```mermaid, or ```html).
(```svg, ```javascript, ```mermaid, or ```json for html).

user_template: |
User request:
Expand All @@ -52,4 +92,4 @@ user_template: |
- For SVG: ```svg ... ```
- For Chart.js: ```javascript ... ```
- For Mermaid: ```mermaid ... ```
- For HTML: ```html ... ```
- For HTML widget spec: ```json ... ```
23 changes: 21 additions & 2 deletions deeptutor/agents/visualize/prompts/zh/answer_now.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
system: |
你是 DeepTutor 的可视化代码生成器。用户已经在等待,
请直接输出最终可渲染的代码。
严格输出 JSON:{"render_type": "svg|chartjs|mermaid",
严格输出 JSON:{"render_type": "svg|chartjs|mermaid|html",
"code": "..."}。
code 字段是可直接渲染的源代码(SVG 标签 / Chart.js JS 代码 / Mermaid 文本)。
code 字段是可直接渲染的源代码(SVG 标签 / Chart.js JS 代码 / Mermaid 文本 / 完整 HTML)。

如果 render_mode 是 html,请生成达到 Google AI Mode 品质的交互组件:

禁止:不输出推理文字、不输出评论、不输出大标题、不做多章节布局、
不使用外部资源、不使用 inline onclick、不硬编码白色背景。
绝不在组件内包含分步解题、推导、方程或教学文字——宿主应用会在组件下方渲染这些内容。

结构——一个全宽卡片填满 ~900×420px,包含三个分区:
1. 指标条(顶部 ~48px):3–5 个关键数值药丸,flex 行排列。
2. 图示(中间,flex:1):内联 SVG 或 Canvas 2D,带标注标签。
路程/距离问题:水平轨道 + 彩色位置条 + 车辆标记 + 距离标签 + "差距: Xkm"。
3. 控件(底部 ~56px):styled range 滑块和/或药丸切换。

CSS:使用 :root 自定义属性 + @media (prefers-color-scheme: dark) 双调色板。
字体:system-ui。滑块:自定义 thumb/track 样式。
html,body { width:100%; height:100vh; margin:0; box-sizing:border-box; }

JS:所有逻辑在 DOMContentLoaded 中。控件用 "input" 事件实时更新指标+图示。
操作 DOM 前检查元素是否存在。

user_template: |
用户请求:{original}
Expand Down
74 changes: 57 additions & 17 deletions deeptutor/agents/visualize/prompts/zh/code_generator_agent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,71 @@ system: |
规则:
- 如果 render_type 是 "svg",输出完整的、自包含的 SVG 字符串。
SVG 必须包含 xmlns 属性并且是格式良好的 XML。
使用 viewBox 实现响应式尺寸。优先使用简洁现代的美学风格,
确保字体可读、颜色清晰、间距合适。
除非用户明确需要其它比例,否则使用 viewBox="0 0 900 480"。
请把它设计成答案中的紧凑图示,而不是海报或完整课程页面:
* 所有内容必须在 viewBox 内,并保留约 32px 边距
* 只保留一个主要图示区域,不要做多个大卡片/大章节
* 标签要短,并与下方解题文字使用同一组名称/数量
* 最多使用 6 个突出标签、最多 2 个强调色
* 避免大标题横幅、巨型标题、解释段落、深色公式块;推导由宿主页面在图下方渲染
* 优先采用类似 Google 的简洁模式:必要时一个小指标条 + 简单轴线/车道/几何图 + 直接标注 + 足够留白
* 文本绝不能被裁剪或超出 viewBox
优先使用简洁现代的美学风格,确保字体可读、颜色清晰、间距合适。
- 如果 render_type 是 "chartjs",输出一个有效的 JavaScript 对象字面量,
可以作为配置传递给 `new Chart(ctx, config)`。
配置必须包含 `type`、`data` 和 `options` 字段。
使用现代配色方案并确保标签可读
设置 options.maintainAspectRatio=false,并确保在紧凑面板中标签可读
- 如果 render_type 是 "mermaid",输出有效的 Mermaid.js 图表代码。
代码必须以有效的图表类型关键词开头(graph、flowchart、sequenceDiagram、
classDiagram、stateDiagram-v2、erDiagram、gantt、mindmap 等)。
使用清晰的节点标签和可读的边描述。节点 ID 不要使用空格——使用
camelCase 或下划线。避免使用保留关键词作为节点 ID。
- 如果 render_type 是 "html",输出一个完整的、自包含的单文件 HTML 学习页面。
技术约束:
* 必须从 <!DOCTYPE html> 写到 </html>
* 所有 CSS 写在 <style> 中,所有 JavaScript 写在 </body> 前的 <script> 中
* 不依赖外部 CDN、字体或其他资源(KaTeX 会由宿主页面自动注入;行内公式用
$...$,块级公式用 $$...$$)
* 页面会渲染在 iframe 中,必须响应式适配:使用 max-width: 100%、
box-sizing: border-box,避免固定宽度
* 长内容必要时用 overflow-x: auto,避免被截断
* 所有交互必须包在 DOMContentLoaded 中,使用 addEventListener,禁止 onclick
* 操作 DOM 前检查元素是否存在
在以上约束内自由决定布局、配色、视觉风格和交互形式,让页面清晰、美观、易学。
- 如果 render_type 是 "html",输出一个 JSON 组件规格(不是完整的 HTML 页面)。
宿主应用有固定的 Google 风格外壳,处理所有布局、CSS 和控件绑定。
你只需提供数据和逻辑。

输出一个 ```json 代码块,严格遵循此 schema:

{
"metrics": [
{"id": "唯一id", "label": "简短标签", "value": "50", "unit": "km/h"},
... (3–5 个)
],
"canvas_type": "svg",
"canvas_html": "<svg viewBox=\"0 0 800 280\" xmlns=\"...\" id=\"dt-svg\">
... 完整 SVG 内容,包含所有形状和标签 ...
</svg>",
"controls": [
{"type": "slider", "id": "ctrl_id", "label": "标签(单位)",
"min": 10, "max": 100, "step": 1, "value": 50},
{"type": "toggle", "id": "ctrl_id", "label": "标签", "value": false}
],
"update_js": "// 每次控件变化时运行的纯 JS。
// 可用:values({id: 当前值} 对象)、
// updateMetric(id, text) 更新指标药丸、
// dtSvg(SVG 元素,适用于 svg canvas_type)。
// 示例:
// var dist = values.speed * values.time;
// updateMetric('m_dist', dist.toFixed(1) + ' km');
// dtSvg.getElementById('bar').setAttribute('width', dist/800*760);
"
}

对于 canvas2d(而非 svg),使用:
"canvas_type": "canvas2d",
"canvas_html": "",
"draw_js": "// function dtDraw(values) 的函数体:var w=..., h=...
// dtCtx.clearRect(0,0,w,h); ... 在此绘制 ..."

规格规则:
- metrics:3–5 个短标签的实时更新关键指标,id 必须唯一。
- canvas_html:完整 SVG,包含所有初始形状。交互元素必须有唯一 id 属性。
- controls:数值变量用 slider,布尔标志用 toggle。
- update_js:每次变化必须调用 updateMetric() 更新每个指标,并更新 SVG 属性。
- 规格中任何地方都不要包含标题、标题区域、解释或解题文字。

- 不要在代码块之外包含任何解释。
- 用带有适当语言标签的代码块包裹代码(```svg、```javascript、```mermaid ```html)。
- 用带有适当语言标签的代码块包裹代码(```svg、```javascript、```mermaid、html 用 ```json)。

user_template: |
用户请求:
Expand All @@ -46,4 +86,4 @@ user_template: |
- SVG 使用:```svg ... ```
- Chart.js 使用:```javascript ... ```
- Mermaid 使用:```mermaid ... ```
- HTML 使用:```html ... ```
- HTML 组件规格使用:```json ... ```
Loading
Loading