tavotto-omicos-web
v0.1.0
Published
OmicOS build of the Tavotto figure workbench (React) — embeddable in a host application.
Maintainers
Readme
Tavotto v2 — 前端工作台
论文 Figure 排版 + 参数化图表编辑的浏览器工作台。Vite + React 19 + TypeScript + Tailwind v4 + Radix + Zustand/Immer。
启动
后端(Flask,端口 5089)需先跑起来:
cd .. && ./run.sh # 或 python app.py前端:
pnpm install
pnpm dev # http://localhost:5173/api 与 /exports 由 vite dev server 代理到 http://127.0.0.1:5089(见
vite.config.ts)。
pnpm build # tsc -b && vite build → dist/
pnpm exec tsc -b # 只做类型检查
pnpm lint # oxlint目录
src/
canvas/ 画布:视口、世界层、各对象视图、屏幕坐标覆盖层、标尺、交互
interactions.ts 所有指针拖动逻辑(移动/缩放/端点/框选/平移/绘制/参考线/裁剪/图内拖动)
PanelView.tsx 面板显示:PNG ↔ 引擎 SVG 切换、裁剪、图内命中层、渲染状态角标
components/ App Shell:顶栏、左侧栏(素材/图层)、Inspector(检查器 | AI)、状态栏、对话框
inspector/ ElementInspector 由 manifest 动态生成表单,前端不硬编码 matplotlib 属性
ai/ AiPanel 流式对话 + DiffView(unified diff,可放大到对话框)
ui/ 自绘的 shadcn 风格基元(Button/Input/Select/Dialog/Menu/…)
store/ Zustand:document(唯一进 undo)/selection/viewport/asset/ui/interaction/render/ai
hooks/ useEngineSync(引擎渲染的唯一驱动点)、useServerEvents(SSE)、useKeyboard
lib/ 单位换算、几何(吸附/对齐/分布)、API、v1 布局迁移
types/ 文档模型(schema 2)关键约定
- 单位一律 mm,
objects数组顺序即 z 序(末尾在最上)。 - 世界坐标:1mm =
BASE_PX_PER_MM(96dpi) 世界像素,scale(zoom)是唯一世界变换; 选择框 / 手柄 / 参考线画在屏幕坐标的OverlaySvg上,任何缩放下线宽恒为 1px。 - 撤销:只有
documentStore进历史,用 ImmerproduceWithPatches;拖动类操作 pointerdown 开事务、pointerup 合并成一条(上限 200 条)。文字自适应高度走silent()不进历史。 - 字体分离:UI 用系统字体,画布内文字对象用
--font-doc("Times New Roman", "Songti SC", serif)。 - 面板取图:矢量走
/api/render?w=<bucket>分档 PNG(档位只升不降),位图走/api/file。裁剪用「放大的 img + overflow hidden」实现,不改源文件。 - 布局兼容:
/api/layouts读到 v1 结构时由lib/migrate.ts转成 schema 2; 保存一律写 schema 2。
图内元素编辑(Phase 4)
双击 ⚡ 面板(或 Inspector 里「编辑图内元素」)进入。进入后面板从 PNG 换成引擎 出的内联 SVG,所见即所得;退出时若该面板有 override 则继续用 SVG 显示。
- 命中测试只用
manifest.bbox(面积小者优先、axes 降权、0.4% 容差), 不依赖 SVG 内部结构,换 matplotlib 版本也不会失效。 - 表单完全由
manifest.elements[].editable驱动(text/number/color/bool/enum/ pair/rect 七种类型),前端只维护一张 prop → 中文名的映射表。 - override 存在 PanelObject 上,因此天然进 undo history。
hooks/useEngineSync.ts是唯一的渲染驱动点:只要「文档里的 overrides」与 「已渲染的 patches」不一致就重渲染 —— 编辑、撤销/重做、AI 改脚本、文件变更 全部走同一条路径,各处不需要自己触发渲染。 - 合流:文字/数值防抖 300ms,颜色/开关/枚举/拖动结束立即;渲染中再来请求
只保留最后一次(
renderStore的 busy/queued)。 - 拖动先直接平移 SVG 里对应的
<g id="gid">做乐观预览,松手才写 override。 - 失败保留旧 SVG 并展示可折叠 traceback;
panel.file_changed把相关面板标 stale。
AI 改脚本(Phase 5)
右栏 AI 标签页。选中 ⚡ 面板(和可选的图内元素)后用自然语言描述改动,
POST /api/ai/run 拉起 codex / claude 直接改产出该图的 matplotlib 脚本,
输出经 SSE ai.delta 逐行流式回显;结束后如脚本有变化则显示 unified diff
(可放大到对话框)与「回滚此次修改」。
改动落盘后 watcher 会作废渲染会话,前端据此重建图表;回滚同理。
作为 npm 包使用(tavotto-omicos-web)
这个目录同时作为 npm 包发布,供宿主应用(如 omicOS-ui)嵌入工作台界面, 而不是把源码复制一份进去。
npm i tavotto-omicos-web react react-domimport { createRoot } from 'react-dom/client'
import { createElement } from 'react'
import { App, currentProjectId, setCurrentProjectId, migrateToProject } from 'tavotto-omicos-web'
import 'tavotto-omicos-web/style.css' // 样式不自动注入,宿主导入一次
createRoot(el).render(createElement(App))- 发的是构建产物不是源码:源码里
@/、@profiles、@glyphcoverage这几个别名用了近 2900 次,其中两个还指向web/之外的 Python 包 (出版规范 profile、字形覆盖表,各自只允许有一份权威副本)。发源码等于要求 每个消费方复刻这套构建,所以别名在vite.lib.config.ts里构建时解析掉。 react/react-dom是 peer:宿主必须持有唯一的 React 实例,两份会让 hooks 失效。- 公开面只有
src/embed.ts一个文件,改它等于改契约。 - 构建:
npm run build:lib(产物在dist-lib/,npm publish前自动跑)。 - 许可证 AGPL-3.0-only,与
omicos-figure-engine(Python 渲染引擎)一致; 两者同源于本仓库,见根目录MODIFICATIONS.md。
