feat: export current graph as grammar with copy button for external AI analysis

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-30 09:36:34 +02:00
parent f9dddb7184
commit 9e929cf47f

View File

@@ -538,6 +538,65 @@ function applyGrammarOps(state: EditorState, ops: GrammarOp[]): { state: EditorS
return { state: s, errors }
}
function graphToGrammar(state: EditorState): string {
const lines: string[] = []
if (state.nodes.length) {
lines.push(`# Nœuds (${state.nodes.length})`)
for (const n of state.nodes) {
let l = `+node id=${n.id} label="${n.label}" type=${n.type} x=${n.x} y=${n.y}`
if (n.instrument) l += ` instrument=${n.instrument}`
if (n.unit) l += ` unit=${n.unit}`
if (n.formula) l += ` formula="${n.formula}"`
lines.push(l)
}
lines.push('')
}
if (state.edges.length) {
lines.push(`# Arêtes (${state.edges.length})`)
for (const e of state.edges) {
const sg = e.sign === 'positive' ? '+' : e.sign === 'negative' ? '-' : '='
let l = `+edge from=${e.from} to=${e.to} sign=${sg}`
if (e.strength != null && e.strength !== 2) l += ` s=${e.strength}`
if (e.style === 'dashed') l += ` style=dashed`
if (e.label) l += ` label="${e.label}"`
if (e.lag_days != null) l += ` lag=${e.lag_days}`
if (e.decay_days === null) l += ` decay=null`
else if (e.decay_days != null) l += ` decay=${e.decay_days}`
lines.push(l)
}
lines.push('')
}
const coefs = Object.entries(state.coefficients)
if (coefs.length) {
lines.push(`# Coefficients (${coefs.length})`)
for (const [key, c] of coefs) {
let l = `+coef key=${key} value=${c.value}`
if (c.description) l += ` desc="${c.description}"`
lines.push(l)
}
lines.push('')
}
const inputs = Object.entries(state.inputMapping)
if (inputs.length) {
lines.push(`# Inputs (${inputs.length})`)
for (const [node, m] of inputs) {
let l = `~input node=${node} source=${m.source}`
if (m.key) l += ` key=${m.key}`
lines.push(l)
}
lines.push('')
}
if (state.instruments.length)
lines.push(`~instruments ${state.instruments.join(',')}`)
return lines.join('\n').trim()
}
const GRAMMAR_REFERENCE = `# Nœuds
+node id=<id> label="<texte>" type=<macro_event|observable|latent|market_asset> [x=<n>] [y=<n>] [instrument=<str>]
~node id=<id> [label="..."] [type=...] [x=<n>] [y=<n>] [instrument=<str>]
@@ -1103,7 +1162,9 @@ function TabEditor({ initialId }: { initialId?: number | null }) {
const [grammarApplied, setGrammarApplied] = useState(0) // count of applied ops
const [aiLoading, setAiLoading] = useState(false)
const [aiError, setAiError] = useState<string | null>(null)
const [showGrammarRef, setShowGrammarRef] = useState(false)
const [showGrammarRef, setShowGrammarRef] = useState(false)
const [showGrammarExport, setShowGrammarExport] = useState(false)
const [copied, setCopied] = useState(false)
const svgRef = useRef<SVGSVGElement>(null)
useEffect(() => {
@@ -1837,6 +1898,41 @@ function TabEditor({ initialId }: { initialId?: number | null }) {
</pre>
)}
{/* Export — grammaire courante du graphe */}
<div className="rounded border border-slate-700/50 overflow-hidden">
<button
onClick={() => setShowGrammarExport(v => !v)}
className="w-full flex items-center justify-between px-3 py-2 bg-dark-800/60 hover:bg-dark-800 text-xs text-slate-400 transition-colors">
<span className="flex items-center gap-1.5">
<Copy className="w-3 h-3" />
Grammaire courante du graphe
<span className="text-slate-600">
({state.nodes.length} nœuds · {state.edges.length} arêtes)
</span>
</span>
<span className="text-slate-600">{showGrammarExport ? '' : ''}</span>
</button>
{showGrammarExport && (() => {
const grammar = graphToGrammar(state)
return (
<div className="relative">
<pre className="text-[10px] font-mono text-slate-400 bg-dark-900/80 p-3 overflow-x-auto max-h-52 leading-relaxed whitespace-pre">
{grammar}
</pre>
<button
onClick={() => {
navigator.clipboard.writeText(grammar)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}}
className="absolute top-2 right-2 flex items-center gap-1 px-2 py-1 bg-slate-700 hover:bg-slate-600 rounded text-[10px] text-slate-300 border border-slate-600 transition-all">
{copied ? ' Copié' : <><Copy className="w-2.5 h-2.5" /> Copier</>}
</button>
</div>
)
})()}
</div>
<textarea
value={grammarText}
onChange={e => { setGrammarText(e.target.value); setGrammarErrors([]); setGrammarApplied(0) }}