feat: page interactive, démos Play/Step et simulateur Carnot

Ajoute le pipeline PageSpec (validation, rendu, publication /p/{slug}),
les démos TipTap /demo, et le simulateur Carnot (modes frigo/PAC/moteur,
énergie kJ vs puissance W, unités K/°C/°F) avec correctifs d’équations KaTeX.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Antigravity
2026-07-24 17:51:43 +00:00
parent f385d43d5d
commit 69c99e4f4f
67 changed files with 12005 additions and 33 deletions

View File

@@ -0,0 +1,65 @@
import type { IntentId } from '@/lib/interactive-demo/types'
import { carnotCycleSimulator } from './carnot-cycle'
import { carnotCycleAnim } from './carnot-cycle-anim'
import { tsDiagramAnim } from './ts-diagram'
import type { AnimPlugin, AnyPlugin, SimulatorPlugin } from './types'
export const GENERIC_SIM_ID = 'generic-formula' as const
const REGISTRY: Record<string, AnyPlugin> = {
[carnotCycleSimulator.id]: carnotCycleSimulator,
[carnotCycleAnim.id]: carnotCycleAnim,
[tsDiagramAnim.id]: tsDiagramAnim,
}
export function getPlugin(id: string): AnyPlugin | null {
return REGISTRY[id] ?? null
}
export function getSimulator(id: string): SimulatorPlugin | null {
const p = REGISTRY[id]
return p?.family === 'sim' ? p : null
}
export function getAnimPlugin(id: string): AnimPlugin | null {
const p = REGISTRY[id]
return p?.family === 'anim' ? p : null
}
export function isCatalogSimId(id: string): boolean {
return id !== GENERIC_SIM_ID && id in REGISTRY
}
export function listSimulators(): AnyPlugin[] {
return Object.values(REGISTRY)
}
/**
* Compact catalog injected into the section-generation prompt so the LLM
* can pick a curated plugin (sim or anim) and bind note values into a preset.
*/
export function catalogForPrompt(lang: string): string {
const fr = lang.startsWith('fr')
const catalog = listSimulators().map((plugin) => ({
simId: plugin.id,
family: plugin.family,
summary: fr ? plugin.summary.fr : plugin.summary.en,
keywords: plugin.keywords.slice(0, 10),
...(plugin.family === 'sim'
? {
params: plugin.params.map((p) => ({
id: p.id,
symbol: p.symbol,
range: [p.min, p.max],
default: p.defaultValue,
unit: p.unit,
})),
outputs: plugin.outputs.map((o) => o.symbol),
}
: {}),
}))
return JSON.stringify(catalog, null, 1)
}
export type { SimulatorPlugin, AnimPlugin, AnyPlugin, AnimBeat, SimI18n, SimParamDef, SimOutputDef } from './types'
export type { IntentId }