@xom11/whiteboard
v0.33.0
Published
Excalidraw + JSXGraph + KaTeX whiteboard component (drawing, geometry stamps, LaTeX stamps).
Readme
@xom11/whiteboard
Excalidraw-based whiteboard component dùng cho HocTotBachKhoa classroom: bút/shape/text/import ảnh + hai stamp tools (hình học JSXGraph, công thức LaTeX KaTeX).
Install
Cài qua git URL (repo public, không cần npm publish):
# pin tag (recommended)
npm install github:xom11/whiteboard#v0.2.0
# hoặc trong package.json
"@xom11/whiteboard": "github:xom11/whiteboard#v0.2.0"Peer dependencies
Từ phiên bản này, @excalidraw/excalidraw, jsxgraph và katex được externalize khỏi bundle (giảm bundle ~70%). Consumer bắt buộc cài kèm:
npm install @excalidraw/excalidraw@^0.18.1 jsxgraph@^1.12.2 katex@^0.16.45 react@>=18 react-dom@>=18Hoặc thêm vào package.json của consumer:
{
"dependencies": {
"@excalidraw/excalidraw": "^0.18.1",
"jsxgraph": "^1.12.2",
"katex": "^0.16.45",
"react": ">=18",
"react-dom": ">=18"
}
}Lý do externalize:
- Tránh duplicate React khi consumer cũng dùng Excalidraw trực tiếp.
- Bundle nhẹ hơn ~70% (jsxgraph ~600KB + katex ~280KB + excalidraw ~2MB không còn nằm trong dist của whiteboard).
- Consumer kiểm soát version + dedupe qua npm/pnpm tự nhiên.
Usage
import { ExcalidrawWhiteboardView, type ExcalidrawSceneSnapshot } from '@xom11/whiteboard';
export function ClassroomBoard() {
return (
<ExcalidrawWhiteboardView
role="teacher"
roomId="room-123"
initialScene={null}
remoteScene={null}
onSceneChange={(snapshot) => { /* persist + broadcast */ }}
onFilesChange={(files, newIds) => { /* upload new files */ }}
/>
);
}AI dựng hình học 2D (opt-in)
Textarea AI chỉ xuất hiện khi truyền generateGeometryFigure. Callback chạy từ client nên phải gọi server boundary của ứng dụng; không bao giờ đặt API key vào component / biến môi trường public.
'use client';
import { Whiteboard, type GenerateGeometryFigure } from '@xom11/whiteboard';
const generateGeometryFigure: GenerateGeometryFigure = async (problem, { signal }) => {
const response = await fetch('/api/geometry/ai', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ problem }),
signal,
});
return response.json();
};
export function ClassroomBoard() {
return <Whiteboard generateGeometryFigure={generateGeometryFigure} />;
}Provider backend (chọn 1)
Từ phiên bản hỗ trợ multi-provider, có 2 lựa chọn:
A. Local Gemma 3 qua Ollama (mặc định, miễn phí, không cần API key)
Setup máy chạy server:
# macOS
brew install ollama
ollama serve # chạy nền cổng 11434
# Chọn 1 trong 2 model:
ollama pull gemma3:4b # ~3.3GB Q4 — nhanh ~20s/đề, accuracy 75%
ollama pull gemma3:12b # ~8GB Q4 — chậm ~60s/đề, accuracy 92% (recommended cho production)
# Linux
curl -fsSL https://ollama.com/install.sh | sh
ollama serve &
ollama pull gemma3:12bEval 12 đề THCS/lớp 10 (transpile success rate + dùng đúng primitive):
| Model | Transpile OK | Kind đúng | Refuse đúng | Avg latency | |-----------|--------------|-----------|-------------|-------------| | gemma3:4b | 75% | 63% | 0/1 | 20s | | gemma3:12b | 92% | 88% | 1/1 | 62s | | Claude Opus 4.7 | ~100% | ~100% | 1/1 | ~5s (cost ~$0.01/đề) |
Đề nghị: 12b cho production, 4b cho prototype/máy yếu, Anthropic cho accuracy tuyệt đối.
Server-side route Next.js:
import { generateFigure } from '@xom11/whiteboard/ai';
export async function POST(request: Request) {
const { problem } = await request.json();
// Default: provider=ollama, model=gemma3:4b, baseUrl=http://localhost:11434
const result = await generateFigure(problem);
return Response.json(
result.ok ? { ok: true, state: result.state } : { ok: false, message: result.message },
);
}Env override (optional):
WHITEBOARD_AI_PROVIDER=ollama # default
OLLAMA_BASE_URL=http://localhost:11434 # default
OLLAMA_DEFAULT_MODEL=gemma3:12b # default gemma3:4b; recommend gemma3:12b cho productionB. Anthropic Claude (chính xác cao, tốn API cost)
import { generateFigure } from '@xom11/whiteboard/ai';
const result = await generateFigure(problem, {
apiKey: process.env.ANTHROPIC_API_KEY ?? '',
});Hoặc qua env:
WHITEBOARD_AI_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-...C. Provider tuỳ biến (vd OpenAI, OpenRouter, vLLM): implement interface AIProvider:
import { generateFigure, type AIProvider } from '@xom11/whiteboard/ai';
const customProvider: AIProvider = {
name: 'my-llm',
defaultModel: 'my-model',
async call(req) {
// req.systemPrompt, req.userPrompt, req.schema (JSON Schema cho envelope)
// ...
return { kind: 'json', data: envelopeObject };
},
};
const result = await generateFigure(problem, { provider: customProvider });Smoke test local Ollama
brew install ollama && ollama serve & ollama pull gemma3:4b
OLLAMA_SMOKE=1 npx jest ollama.smokeMigration to v0.8.0 (geometry-3d redesign)
geometry3dStamp được viết lại theo UX của GeoGebra 3D Calculator:
- Click trên mặt nền / trục / mặt phẳng / mặt cầu để đặt điểm constraint (không còn prompt nhập toạ độ).
- Drag điểm trên Move tool — điểm trượt theo surface (z-axis chỉ thay đổi z, mặt nền giữ z=0).
- Algebra panel mới (tab bên trái) hiển thị mỗi object: label, biểu thức symbolic, giá trị numeric, menu ⋮ (đổi tên / màu / ẩn / xoá).
- 16 tool: Move, Point, Point-on-Object, Segment, Line, Ray, Vector, Polygon, Plane (3 điểm), Pyramid, Prism, Tetrahedron, Cube, Sphere, Cylinder, Cone.
Backward compat: Stamps lưu từ v0.7.0 load OK (legacy points → constraint free). API consumer giữ nguyên.
Tạm thời bỏ: Chord-shortcut 2 phím (G S cho segment, v.v.) — sẽ trở lại với letter-mapping mới.
Migration to v0.7.0 (BREAKING)
DEFAULT_STAMPS v0.7.0 chỉ gồm 2 stamps stable: geometry + latex. 3D + graph2d chuyển sang opt-IN (experimental).
Giữ behavior cũ (4 stamps):
import { Whiteboard, ALL_STAMPS } from '@xom11/whiteboard';
<Whiteboard stamps={ALL_STAMPS} />Chỉ thêm 3D (giữ default + 3D):
import { Whiteboard, DEFAULT_STAMPS, geometry3dStamp } from '@xom11/whiteboard';
<Whiteboard stamps={[...DEFAULT_STAMPS, geometry3dStamp]} />Subpath imports (tree-shake):
Mỗi stamp có thể import riêng để bundle nhẹ hơn:
import { Whiteboard } from '@xom11/whiteboard';
import { geometryStamp } from '@xom11/whiteboard/geometry-2d';
import { latexStamp } from '@xom11/whiteboard/latex';
<Whiteboard stamps={[geometryStamp, latexStamp]} />Drop Next.js peer dep
next không còn là peer dependency. Whiteboard dùng React.lazy + Suspense thuần. Consumer cần Next.js App Router vẫn hoạt động (dist có sẵn 'use client' directive).
Extending — thêm stamp mới
Fork repo + viết stamp mới trong ~30 phút. Tham khảo:
- Howto:
docs/superpowers/specs/add-new-stamp-howto.md— 6 bước có sẵn lệnh. - Template:
examples/stamp-template/— skeleton "color-swatch" stamp, copy + đổikind. - Contract test: mỗi stamp PHẢI pass
runStampContract(xemsrc/stamps/shared/__tests__/stamp-contract.ts) để đảm bảomatchesCustomData/renderSvgFromCustomData/ roundtrip restore không break. - Catalog: thêm entry vào
src/stamps/shared/catalog.ts. Bundle size tự tính quascripts/build-catalog.mjskhinpm run build.
import { STAMP_CATALOG, findCatalogEntry } from '@xom11/whiteboard';
// Render admin UI từ catalog
STAMP_CATALOG.forEach((entry) => {
console.log(entry.id, entry.title, entry.bundleSize.js + 'KB gzip');
});Development
npm install
npm test
npm run build # tsup → dist/{index.js, index.mjs, index.d.ts}
npm run dev # tsup watch modeE2E tests
Playwright smoke tests chạy qua headless Chromium, tự start vite demo:
npx playwright install chromium # cài browser binary (chỉ làm 1 lần)
npm run test:e2e # chạy specsChi tiết xem tests/e2e/README.md.
Workflow phát hành phiên bản mới
npm ci --ignore-scripts ở consumer skip prepare hook → phải commit dist/:
npm run build
git add dist/
git commit -am "release vX.Y.Z"
npm version patch # bump package.json + tạo tag
git push --follow-tagsConsumer pin tag mới trong package.json rồi npm install.
Architecture
src/ExcalidrawWhiteboardView.tsx— main wrapper, quản lý sync teacher ↔ student, stamp lifecycle.src/ExcalidrawWithMenus.tsx— Excalidraw + custom MainMenu/Footer/WelcomeScreen.src/stamp/— hai stamp tools (hình học, LaTeX) + helpers (JSXGraph board, KaTeX renderer, serialize/restore).src/serialize.ts— pick sync-able subset của Excalidraw appState.src/types.ts— re-export Excalidraw types + project types.
Extracted from
Hoctotbachkhoa/hoctotbachkhoa (apps/web/components/classroom/excalidrawBoard/) — git history preserved via git filter-repo. Repo sau đó transfer từ Hoctotbachkhoa/whiteboard sang xom11/whiteboard (v0.2.0 trở đi).
