Skip to content

feat(chart-editor): 参数化图表本地微调与实时渲染#1

Merged
Cmochance merged 8 commits into
mainfrom
codex/chart-editor-local-tuning
Jun 20, 2026
Merged

feat(chart-editor): 参数化图表本地微调与实时渲染#1
Cmochance merged 8 commits into
mainfrom
codex/chart-editor-local-tuning

Conversation

@Cmochance

Copy link
Copy Markdown
Owner

背景

nature-figure 的图片绘制模块此前只能整图重生成:每次微调(改配色、字号、线宽等)都要把上一版 Python 脚本和新文字要求拼成 prompt 重新喂给 codex,走完一次完整 LLM 推理,耗时、费 token 且 LLM 重写脚本可能引入不确定性。

本 PR 借鉴 Sci-Data-Analyzer(私有)的 chart_renderer 参数化模板架构,为 nature-figure 新增一层本地参数化微调器,实现不经过 LLM 的 matplotlib 实时渲染。

整条链路(三阶段不可分割)

阶段一:后端渲染引擎

  • 移植 4 个自包含 Python 绘图模板到 `src-tauri/resources/chart_renderer/templates/`:
    • `line_plot.py`(折线,755 行)、`bar_plot.py`(柱状)、`heatmap_plot.py`(热图)、`dual_y_plot.py`(双 Y 轴)
    • 模板只依赖标准库 + matplotlib + numpy,无任何项目内部依赖,从同目录读 `plot_spec.json` / `plot_data.json` 渲染输出 `out.{format}`
  • 新增 Rust 模块 `renderer.rs` + Tauri 命令 `preview_plot`:接收参数 JSON → 写临时目录 → 复用 uv 隔离 Python 环境跑模板 → 读回图片字节 base64 返回,子进程隔离 + headless Agg 后端
  • `pyenv.rs` 的 `venv_python()` 改为 pub,供渲染命令复用
  • `tauri.conf.json` 声明模板为打包资源

阶段二:前端参数面板

  • 新增 `ChartEditor` 组件(`src/components/ChartEditor.tsx`):左侧参数面板(全局/样式/图例边框/数据序列四个分区),右侧实时预览
  • 防抖预览机制(400ms + payload 去重),参数变化自动触发本地渲染
  • SVG / PNG 导出
  • 类型定义 `src/types/plot.ts` + API 封装 `src/api/plotApi.ts`
  • 集成到 figure RunView,新增「⚙ 图表微调」入口

阶段三:codex ↔ 本地编辑器衔接

  • 修改 `skills-bundled/nature-figure/static/fragments/backend/python.md`,增加参数导出契约(`export_plot_params` + JSON schema),codex 生成图时同步输出 `plot_spec.json` / `plot_data.json`
  • RunView 在任务成功完成后自动检测并读取这两个文件,ChartEditor 优先使用真实参数,无参数时回退演示数据

附带:浏览器预览 mock 层

  • 新增 `src/mock/` 下的 Tauri 模块替身,通过 Vite alias 在纯浏览器 dev 时替换原生模块,支持前端 UI 开发预览
  • mock `preview_plot` 根据参数生成简化 SVG 预览

验证

  • TypeScript `tsc --noEmit` 零错误
  • Rust `cargo check` 编译通过
  • 命令行验证:模板 + uv Python 成功输出 19756 字节合法 SVG
  • CI / AI review

数据流

```
用户描述 → codex 跑 nature-figure skill → 第一版图 + plot_spec.json + plot_data.json

前端自动读取参数 → 「图表微调」编辑器

参数变化 → 防抖 400ms → Rust preview_plot → Python 模板 → matplotlib → SVG → 预览
(纯本地渲染,百毫秒级,不经过 LLM)
```

借鉴 Sci-Data-Analyzer 的 chart_renderer 架构,为 nature-figure 新增
参数化绘图微调层,实现不经过 LLM 的本地 matplotlib 实时渲染。

整条链路三阶段:
- 阶段一:移植 Python 绘图模板(line/bar/heatmap/dual_y)+ Rust preview_plot
  命令,复用 uv 隔离 Python 环境在子进程渲染,返回 base64 图片
- 阶段二:ChartEditor 组件,左侧参数面板(配色/字号/线宽/图例/边框/序列)
  + 右侧防抖(400ms)实时预览 + SVG/PNG 导出
- 阶段三:修改 figure skill 导出 plot_spec/plot_data 参数契约,codex 生成
  图后前端自动读取参数进入本地编辑器

同时新增 Vite alias mock 层,支持纯浏览器预览前端界面

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a94e37e3a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/renderer.rs Outdated
let resource = app.path().resource_dir().ok();
let dev = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let candidates: [Option<PathBuf>; 2] = [
resource.map(|d| d.join("chart_renderer/templates").join(name)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the bundled resource path when resolving templates

In packaged builds the configured resource entry is resources/chart_renderer/templates/*, and Tauri preserves that relative path under the resource directory, so the files land under $RESOURCE/resources/chart_renderer/templates/... (Tauri resources docs). This resolver strips the leading resources/, so installed apps will fail preview_plot with “找不到绘图模板” unless the original source tree happens to exist on the user's machine.

Useful? React with 👍 / 👎.

raise RuntimeError("plot_data.series is empty")

datetime_plotted = False
for idx, series in enumerate(series_list):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor hidden series in dual-axis plots

When the editor's per-series checkbox is unchecked it writes visible: false, and the line/bar templates skip those series, but the dual-Y template enters this loop and plots every series without checking that flag. For line_dual_y charts, hiding a series in the UI therefore still renders it in the preview/export, making the control misleading for that chart type.

Useful? React with 👍 / 👎.

Comment thread src-tauri/src/renderer.rs Outdated
Comment on lines +115 to +116
let python: PathBuf = if venv_py.exists() {
venv_py

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wait for the managed Python environment before using it

The startup setup creates the venv before installing matplotlib/numpy and writing the .nature-ready marker, but this code selects the venv as soon as the python binary exists. If setup is still running or previously failed after venv creation, chart previews run in an incomplete environment and fail with import errors; use the same readiness gate as engine.rs (venv_bin_if_ready/marker) or report that the renderer environment is not ready.

Useful? React with 👍 / 👎.

- plot.ts: 新增 PlotGrid 接口(mode/values/cmap/origin/z_label)和
  CMAP_PRESETS 常量(16 个常用 matplotlib colormap)
- ChartEditor: heatmap 专属编辑面板,含 colormap 下拉、origin 选择、
  colorbar 标签输入、values 二维矩阵编辑器(行列增删 + 单元格数值编辑)
- 渲染时注入默认 grid(用户切入 heatmap 但无数据时不报错)
- mock: heatmap 色块矩阵预览(viridis 近似色阶)
- CSS: 矩阵编辑器样式

同时修正: 后端技术栈确认为 Rust(Tauri),Python 仅作 Rust spawn 的
子进程执行绘图模板,非独立后端
P1: renderer.rs 打包路径修正 — Tauri 打包后资源保留 resources/ 前缀,
原来少了这层导致发布版找不到模板(崩溃)。补上 resources/ 前缀。
P2: dual_y_plot.py 添加 visible 检查 — 编辑器隐藏序列时双 Y 轴模板
仍然渲染,现在与 line/bar 模板行为一致。
P2: renderer.rs Python 环境就绪检测 — 用 is_ready()(检查 python 存在
+ marker 版本匹配)替代仅检查路径存在,避免依赖安装中途渲染失败

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1a1ef73c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

: data;
try {
const resp = await previewPlot(chartType, spec, renderData, "svg");
setPreviewUrl(toDataUrl(resp.imageFormat, resp.imageBase64));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore stale preview responses

When a render takes longer than the next debounce interval and the user changes another control, multiple previewPlot calls can be in flight; an older call can resolve after the newer one and unconditionally replace the preview with an image for stale parameters (and similarly clear/set loading or error), leaving the editor showing “已同步” for the wrong chart. Track a request id or compare the captured payload before applying the response.

Useful? React with 👍 / 👎.

ChartEditor 防抖预览存在竞态:用户快速连续调整参数时多个渲染请求
同时在途,旧的请求可能比新的晚返回并覆盖预览,导致显示过期参数
的结果。

修复:用递增 reqIdRef 跟踪每次请求,响应返回时检查是否仍为最新,
过期响应直接丢弃。
@Cmochance

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 883db71052

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/ChartEditor.tsx Outdated
Comment on lines +104 to +106
const timer = setTimeout(async () => {
payloadRef.current = payload;
const myId = ++reqIdRef.current;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate in-flight previews when inputs change

If a render is already in flight and the user changes a control, the next request id is not incremented until the 400 ms timeout fires, so the old render can resolve during that debounce window and still pass the myId !== reqIdRef.current check, briefly marking the editor synced with stale parameters (or leaving that stale image behind if the new render errors). Fresh evidence for re-raising the stale-preview issue is that the generation is still bumped inside the debounced callback rather than immediately when this new payload is observed.

Useful? React with 👍 / 👎.

Comment thread src/App.tsx
Comment on lines +235 to +237
} catch {
// 参数文件不存在(旧版产物或 mock 模式),保留演示数据
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear stale plot params when loading fails

After one figure run successfully loads plot_spec.json/plot_data.json, a later run or workdir switch where those files are missing or invalid falls into this catch without clearing loadedSpec/loadedData, so 「图表微调」 still opens the previous chart while the comment says it should fall back to demo data. Reset both cached params on this path (and when starting a new run or changing workdir) so the editor cannot render stale results.

Useful? React with 👍 / 👎.

Comment thread src-tauri/src/renderer.rs
Comment on lines +127 to +129
.env("MPLBACKEND", "Agg")
.env("HOME", &cwd)
.env("PYTHONUNBUFFERED", "1")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve HOME for system Python fallback

When the managed venv is not ready and the renderer falls back to python3, overriding HOME to the temp directory hides packages installed with pip install --user (and user font/config paths), so previews can fail with ModuleNotFoundError even though the user's system Python has matplotlib/numpy installed. The engine path already uses MPLCONFIGDIR/XDG_CACHE_HOME for cache isolation instead of changing HOME; do the same here to keep the fallback usable.

Useful? React with 👍 / 👎.

1. ChartEditor 竞态修复不完整: reqIdRef 递增应在参数变化时立即执行,
   而非等 400ms 防抖后,否则旧请求仍有窗口通过 ID 检查。
2. App.tsx 旧参数未清除: 任务失败或切换工作目录时不清除
   loadedSpec/loadedData,编辑器仍渲染过期数据。现在 catch 和
   launch 开始时都清除。
3. renderer.rs HOME 覆盖: 回退系统 python3 时覆盖 HOME 导致
   pip install --user 的包不可见。改用 MPLCONFIGDIR/XDG_CACHE_HOME
   隔离缓存,保留 HOME。
@Cmochance

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7edf6fca8c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
```

Set `chart_type` to `"line"`, `"bar"`, `"heatmap"`, or `"line_dual_y"` so the editor loads the correct render template. Extract `x` and `y` arrays from the same data you plotted. If a panel has many series, include only the major ones (the editor is for style fine-tuning, not data exploration). For multi-panel figures, export the parameters of the primary panel.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include grid values for heatmap exports

When chart_type is heatmap, the renderer selects heatmap_plot.py, which reads plot_data.grid.values; the schema here only tells Codex to export series with x/y. Following this instruction for a heatmap produces params the editor cannot render because the template receives an empty grid and calls imshow on it. Please document/export the grid contract for heatmaps or translate series data before rendering.

Useful? React with 👍 / 👎.

Comment on lines +66 to +67
const [spec, setSpec] = useState<PlotSpec>(initialSpec);
const [data, setData] = useState<PlotData>(initialData);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Sync editor state when loaded params arrive

If the user opens the editor while a figure run is still in progress or while tryLoadPlotParams() is still reading the generated JSON, it mounts with the demo params. When loadedSpec/loadedData arrive later, these useState initializers are not rerun, so the editor keeps rendering/exporting the demo chart until it is closed and reopened. Sync state on prop changes or disable the entry until real params have loaded.

Useful? React with 👍 / 👎.

if merged is not None:
handles, labels = merged
if len(handles) > 1:
legend = ax.legend(handles, labels, loc="best", fontsize=max(8, font_size - 2))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Honor legend settings in dual-y renderer

For line_dual_y charts with two visible series, this unconditional legend creation ignores the editor's style.legend.enabled and style.legend.location settings. In that chart type, unchecking “显示图例” or choosing another location has no effect, unlike the line and bar templates.

Useful? React with 👍 / 👎.

@Cmochance Cmochance merged commit d50cfa4 into main Jun 20, 2026
1 check passed
@Cmochance Cmochance deleted the codex/chart-editor-local-tuning branch June 20, 2026 18:26
Cmochance added a commit that referenced this pull request Jun 21, 2026
- 把已合入 main 的图表微调(ChartEditor + Rust renderer,PR #1)接回新三栏 UI:
  figure 任务头加「图表微调」入口;useRun 在 figure 成功后载入 workdir 的 plot_spec/plot_data
  (无则 demo 兜底);App 加全屏 ChartEditor 视图;ChartEditor 样式按 cyanotype 主题重写(.ce-*)
- CI 完善:拆成并行两 job —— 前端(tsc --noEmit + build,ubuntu)、Rust(fmt --check +
  clippy -D warnings + 单测,macOS);新增 clippy/fmt 门禁
- 为过门禁:cargo fmt 全量格式化 src-tauri + 修 skills.rs 文档注释(doc_lazy_continuation)
Cmochance added a commit that referenced this pull request Jun 21, 2026
* feat(ui): 重构为现代 Agent 工作台(三栏 + 深浅双主题 + i18n)

- 全 app 重做视觉:cyanotype 设计系统、三栏 shell(侧栏 / 事件流 / 产物面板)、四视图(首页 / 新建任务 / 运行 / 设置)
- i18n:中 / 英整页切换(零混排);自托管字体 Space Grotesk + IBM Plex Mono(离线打包,不依赖 CDN)
- 运行状态上移到 useRun 控制器(App 持有,RunView 转纯展示):修复启动崩溃循环、追加指令清空对话、离开再回重复执行、运行指示灯错位
- 产物面板按 skill 路由:reader 双语 / reviewer 三栏 / citation 列表 / polishing 原稿对照;图片 cache-bust;读取失败显式提示
- 放开 fs / asset 能力域到所选工作目录;恢复 engineError 补救文案、首启自举失败横幅、退出码、命令失败态
- 修 BibTeX 嵌套花括号解析截断;抽取共享 lib(路径工具 / 选择器);清理无用文件(App.css、ArtifactPreview)

* feat(ui): 整合图表微调到新 UI + 完善 CI

- 把已合入 main 的图表微调(ChartEditor + Rust renderer,PR #1)接回新三栏 UI:
  figure 任务头加「图表微调」入口;useRun 在 figure 成功后载入 workdir 的 plot_spec/plot_data
  (无则 demo 兜底);App 加全屏 ChartEditor 视图;ChartEditor 样式按 cyanotype 主题重写(.ce-*)
- CI 完善:拆成并行两 job —— 前端(tsc --noEmit + build,ubuntu)、Rust(fmt --check +
  clippy -D warnings + 单测,macOS);新增 clippy/fmt 门禁
- 为过门禁:cargo fmt 全量格式化 src-tauri + 修 skills.rs 文档注释(doc_lazy_continuation)

* ci: 加聚合门禁 job「test」以满足分支保护必需 check

main 分支保护要求名为 test 的必需 check;前述把单 job 拆成 frontend/rust 两个新名后该 check 不再产生 → PR 永久 BLOCKED。新增聚合 job test(needs: [frontend, rust]),两者全绿才绿。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant