@magicborn/dialogue-forge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/README.md +233 -0
  2. package/bin/dialogue-forge.js +78 -0
  3. package/demo/app/layout.tsx +36 -0
  4. package/demo/app/page.tsx +440 -0
  5. package/demo/components/ThemeSwitcher.tsx +611 -0
  6. package/demo/next.config.mjs +7 -0
  7. package/demo/package.json +29 -0
  8. package/demo/postcss.config.mjs +7 -0
  9. package/demo/public/logo.svg +1 -0
  10. package/demo/styles/globals.css +19 -0
  11. package/demo/tailwind.config.ts +90 -0
  12. package/demo/tsconfig.json +42 -0
  13. package/dist/components/ChoiceEdgeV2.d.ts +3 -0
  14. package/dist/components/ChoiceEdgeV2.js +103 -0
  15. package/dist/components/CodeBlock.d.ts +8 -0
  16. package/dist/components/CodeBlock.js +24 -0
  17. package/dist/components/ConditionAutocomplete.d.ts +14 -0
  18. package/dist/components/ConditionAutocomplete.js +284 -0
  19. package/dist/components/ConditionalNodeV2.d.ts +16 -0
  20. package/dist/components/ConditionalNodeV2.js +147 -0
  21. package/dist/components/DialogueEditorV2.d.ts +22 -0
  22. package/dist/components/DialogueEditorV2.js +1170 -0
  23. package/dist/components/EdgeIcon.d.ts +8 -0
  24. package/dist/components/EdgeIcon.js +13 -0
  25. package/dist/components/ExampleLoader.d.ts +11 -0
  26. package/dist/components/ExampleLoader.js +52 -0
  27. package/dist/components/ExampleLoaderButton.d.ts +15 -0
  28. package/dist/components/ExampleLoaderButton.js +102 -0
  29. package/dist/components/FlagManager.d.ts +11 -0
  30. package/dist/components/FlagManager.js +282 -0
  31. package/dist/components/FlagSelector.d.ts +11 -0
  32. package/dist/components/FlagSelector.js +235 -0
  33. package/dist/components/GuidePanel.d.ts +7 -0
  34. package/dist/components/GuidePanel.js +1176 -0
  35. package/dist/components/Minimap.d.ts +16 -0
  36. package/dist/components/Minimap.js +93 -0
  37. package/dist/components/NPCEdgeV2.d.ts +3 -0
  38. package/dist/components/NPCEdgeV2.js +104 -0
  39. package/dist/components/NPCNodeV2.d.ts +26 -0
  40. package/dist/components/NPCNodeV2.js +86 -0
  41. package/dist/components/NodeEditor.d.ts +18 -0
  42. package/dist/components/NodeEditor.js +1025 -0
  43. package/dist/components/PlayView.d.ts +12 -0
  44. package/dist/components/PlayView.js +307 -0
  45. package/dist/components/PlayerNodeV2.d.ts +16 -0
  46. package/dist/components/PlayerNodeV2.js +139 -0
  47. package/dist/components/ReactFlowPOC.d.ts +61 -0
  48. package/dist/components/ReactFlowPOC.js +312 -0
  49. package/dist/components/ScenePlayer.d.ts +18 -0
  50. package/dist/components/ScenePlayer.js +196 -0
  51. package/dist/components/YarnView.d.ts +9 -0
  52. package/dist/components/YarnView.js +45 -0
  53. package/dist/components/ZoomControls.d.ts +11 -0
  54. package/dist/components/ZoomControls.js +34 -0
  55. package/dist/esm/components/ChoiceEdgeV2.d.ts +3 -0
  56. package/dist/esm/components/ChoiceEdgeV2.js +67 -0
  57. package/dist/esm/components/CodeBlock.d.ts +8 -0
  58. package/dist/esm/components/CodeBlock.js +18 -0
  59. package/dist/esm/components/ConditionAutocomplete.d.ts +14 -0
  60. package/dist/esm/components/ConditionAutocomplete.js +248 -0
  61. package/dist/esm/components/ConditionalNodeV2.d.ts +16 -0
  62. package/dist/esm/components/ConditionalNodeV2.js +111 -0
  63. package/dist/esm/components/DialogueEditorV2.d.ts +22 -0
  64. package/dist/esm/components/DialogueEditorV2.js +1134 -0
  65. package/dist/esm/components/EdgeIcon.d.ts +8 -0
  66. package/dist/esm/components/EdgeIcon.js +7 -0
  67. package/dist/esm/components/ExampleLoader.d.ts +11 -0
  68. package/dist/esm/components/ExampleLoader.js +46 -0
  69. package/dist/esm/components/ExampleLoaderButton.d.ts +15 -0
  70. package/dist/esm/components/ExampleLoaderButton.js +66 -0
  71. package/dist/esm/components/FlagManager.d.ts +11 -0
  72. package/dist/esm/components/FlagManager.js +246 -0
  73. package/dist/esm/components/FlagSelector.d.ts +11 -0
  74. package/dist/esm/components/FlagSelector.js +199 -0
  75. package/dist/esm/components/GuidePanel.d.ts +7 -0
  76. package/dist/esm/components/GuidePanel.js +1140 -0
  77. package/dist/esm/components/Minimap.d.ts +16 -0
  78. package/dist/esm/components/Minimap.js +57 -0
  79. package/dist/esm/components/NPCEdgeV2.d.ts +3 -0
  80. package/dist/esm/components/NPCEdgeV2.js +68 -0
  81. package/dist/esm/components/NPCNodeV2.d.ts +26 -0
  82. package/dist/esm/components/NPCNodeV2.js +80 -0
  83. package/dist/esm/components/NodeEditor.d.ts +18 -0
  84. package/dist/esm/components/NodeEditor.js +989 -0
  85. package/dist/esm/components/PlayView.d.ts +12 -0
  86. package/dist/esm/components/PlayView.js +271 -0
  87. package/dist/esm/components/PlayerNodeV2.d.ts +16 -0
  88. package/dist/esm/components/PlayerNodeV2.js +103 -0
  89. package/dist/esm/components/ReactFlowPOC.d.ts +61 -0
  90. package/dist/esm/components/ReactFlowPOC.js +275 -0
  91. package/dist/esm/components/ScenePlayer.d.ts +18 -0
  92. package/dist/esm/components/ScenePlayer.js +160 -0
  93. package/dist/esm/components/YarnView.d.ts +9 -0
  94. package/dist/esm/components/YarnView.js +39 -0
  95. package/dist/esm/components/ZoomControls.d.ts +11 -0
  96. package/dist/esm/components/ZoomControls.js +28 -0
  97. package/dist/esm/examples/example-loader.d.ts +29 -0
  98. package/dist/esm/examples/example-loader.js +103 -0
  99. package/dist/esm/examples/examples-registry.d.ts +38 -0
  100. package/dist/esm/examples/examples-registry.js +153 -0
  101. package/dist/esm/examples/index.d.ts +26 -0
  102. package/dist/esm/examples/index.js +50 -0
  103. package/dist/esm/examples/legacy-examples.d.ts +9 -0
  104. package/dist/esm/examples/legacy-examples.js +814 -0
  105. package/dist/esm/examples/yarn-examples.d.ts +35 -0
  106. package/dist/esm/examples/yarn-examples.js +181 -0
  107. package/dist/esm/index.d.ts +21 -0
  108. package/dist/esm/index.js +26 -0
  109. package/dist/esm/lib/flag-manager.d.ts +21 -0
  110. package/dist/esm/lib/flag-manager.js +93 -0
  111. package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
  112. package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.js +169 -0
  113. package/dist/esm/lib/yarn-converter.d.ts +17 -0
  114. package/dist/esm/lib/yarn-converter.js +521 -0
  115. package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
  116. package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.js +171 -0
  117. package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
  118. package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.js +237 -0
  119. package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
  120. package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.js +106 -0
  121. package/dist/esm/lib/yarn-runner/condition-evaluator.d.ts +12 -0
  122. package/dist/esm/lib/yarn-runner/condition-evaluator.js +56 -0
  123. package/dist/esm/lib/yarn-runner/index.d.ts +12 -0
  124. package/dist/esm/lib/yarn-runner/index.js +11 -0
  125. package/dist/esm/lib/yarn-runner/node-processor.d.ts +18 -0
  126. package/dist/esm/lib/yarn-runner/node-processor.js +129 -0
  127. package/dist/esm/lib/yarn-runner/variable-manager.d.ts +51 -0
  128. package/dist/esm/lib/yarn-runner/variable-manager.js +120 -0
  129. package/dist/esm/lib/yarn-runner/variable-operations.d.ts +16 -0
  130. package/dist/esm/lib/yarn-runner/variable-operations.js +88 -0
  131. package/dist/esm/types/conditionals.d.ts +29 -0
  132. package/dist/esm/types/conditionals.js +1 -0
  133. package/dist/esm/types/constants.d.ts +59 -0
  134. package/dist/esm/types/constants.js +55 -0
  135. package/dist/esm/types/flags.d.ts +49 -0
  136. package/dist/esm/types/flags.js +49 -0
  137. package/dist/esm/types/game-state.d.ts +62 -0
  138. package/dist/esm/types/game-state.js +6 -0
  139. package/dist/esm/types/index.d.ts +77 -0
  140. package/dist/esm/types/index.js +1 -0
  141. package/dist/esm/utils/constants.d.ts +5 -0
  142. package/dist/esm/utils/constants.js +5 -0
  143. package/dist/esm/utils/feature-flags.d.ts +11 -0
  144. package/dist/esm/utils/feature-flags.js +11 -0
  145. package/dist/esm/utils/game-state-flattener.d.ts +41 -0
  146. package/dist/esm/utils/game-state-flattener.js +135 -0
  147. package/dist/esm/utils/layout/collision.d.ts +27 -0
  148. package/dist/esm/utils/layout/collision.js +74 -0
  149. package/dist/esm/utils/layout/index.d.ts +82 -0
  150. package/dist/esm/utils/layout/index.js +98 -0
  151. package/dist/esm/utils/layout/registry.d.ts +91 -0
  152. package/dist/esm/utils/layout/registry.js +148 -0
  153. package/dist/esm/utils/layout/strategies/dagre.d.ts +19 -0
  154. package/dist/esm/utils/layout/strategies/dagre.js +182 -0
  155. package/dist/esm/utils/layout/strategies/force.d.ts +21 -0
  156. package/dist/esm/utils/layout/strategies/force.js +178 -0
  157. package/dist/esm/utils/layout/strategies/grid.d.ts +17 -0
  158. package/dist/esm/utils/layout/strategies/grid.js +91 -0
  159. package/dist/esm/utils/layout/strategies/index.d.ts +8 -0
  160. package/dist/esm/utils/layout/strategies/index.js +8 -0
  161. package/dist/esm/utils/layout/types.d.ts +100 -0
  162. package/dist/esm/utils/layout/types.js +7 -0
  163. package/dist/esm/utils/layout.d.ts +9 -0
  164. package/dist/esm/utils/layout.js +17 -0
  165. package/dist/esm/utils/node-helpers.d.ts +7 -0
  166. package/dist/esm/utils/node-helpers.js +94 -0
  167. package/dist/esm/utils/reactflow-converter.d.ts +42 -0
  168. package/dist/esm/utils/reactflow-converter.js +217 -0
  169. package/dist/examples/example-loader.d.ts +29 -0
  170. package/dist/examples/example-loader.js +109 -0
  171. package/dist/examples/examples-registry.d.ts +38 -0
  172. package/dist/examples/examples-registry.js +160 -0
  173. package/dist/examples/index.d.ts +26 -0
  174. package/dist/examples/index.js +63 -0
  175. package/dist/examples/legacy-examples.d.ts +9 -0
  176. package/dist/examples/legacy-examples.js +817 -0
  177. package/dist/examples/yarn-examples.d.ts +35 -0
  178. package/dist/examples/yarn-examples.js +189 -0
  179. package/dist/index.d.ts +21 -0
  180. package/dist/index.js +66 -0
  181. package/dist/lib/flag-manager.d.ts +21 -0
  182. package/dist/lib/flag-manager.js +99 -0
  183. package/dist/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
  184. package/dist/lib/yarn-converter/__tests__/round-trip.test.js +171 -0
  185. package/dist/lib/yarn-converter.d.ts +17 -0
  186. package/dist/lib/yarn-converter.js +525 -0
  187. package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
  188. package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.js +173 -0
  189. package/dist/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
  190. package/dist/lib/yarn-runner/__tests__/node-processor.test.js +239 -0
  191. package/dist/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
  192. package/dist/lib/yarn-runner/__tests__/variable-manager.test.js +108 -0
  193. package/dist/lib/yarn-runner/condition-evaluator.d.ts +12 -0
  194. package/dist/lib/yarn-runner/condition-evaluator.js +60 -0
  195. package/dist/lib/yarn-runner/index.d.ts +12 -0
  196. package/dist/lib/yarn-runner/index.js +21 -0
  197. package/dist/lib/yarn-runner/node-processor.d.ts +18 -0
  198. package/dist/lib/yarn-runner/node-processor.js +133 -0
  199. package/dist/lib/yarn-runner/variable-manager.d.ts +51 -0
  200. package/dist/lib/yarn-runner/variable-manager.js +124 -0
  201. package/dist/lib/yarn-runner/variable-operations.d.ts +16 -0
  202. package/dist/lib/yarn-runner/variable-operations.js +92 -0
  203. package/dist/types/conditionals.d.ts +29 -0
  204. package/dist/types/conditionals.js +2 -0
  205. package/dist/types/constants.d.ts +59 -0
  206. package/dist/types/constants.js +58 -0
  207. package/dist/types/flags.d.ts +49 -0
  208. package/dist/types/flags.js +52 -0
  209. package/dist/types/game-state.d.ts +62 -0
  210. package/dist/types/game-state.js +7 -0
  211. package/dist/types/index.d.ts +77 -0
  212. package/dist/types/index.js +2 -0
  213. package/dist/utils/constants.d.ts +5 -0
  214. package/dist/utils/constants.js +8 -0
  215. package/dist/utils/feature-flags.d.ts +11 -0
  216. package/dist/utils/feature-flags.js +14 -0
  217. package/dist/utils/game-state-flattener.d.ts +41 -0
  218. package/dist/utils/game-state-flattener.js +140 -0
  219. package/dist/utils/layout/collision.d.ts +27 -0
  220. package/dist/utils/layout/collision.js +77 -0
  221. package/dist/utils/layout/index.d.ts +82 -0
  222. package/dist/utils/layout/index.js +109 -0
  223. package/dist/utils/layout/registry.d.ts +91 -0
  224. package/dist/utils/layout/registry.js +151 -0
  225. package/dist/utils/layout/strategies/dagre.d.ts +19 -0
  226. package/dist/utils/layout/strategies/dagre.js +189 -0
  227. package/dist/utils/layout/strategies/force.d.ts +21 -0
  228. package/dist/utils/layout/strategies/force.js +182 -0
  229. package/dist/utils/layout/strategies/grid.d.ts +17 -0
  230. package/dist/utils/layout/strategies/grid.js +95 -0
  231. package/dist/utils/layout/strategies/index.d.ts +8 -0
  232. package/dist/utils/layout/strategies/index.js +14 -0
  233. package/dist/utils/layout/types.d.ts +100 -0
  234. package/dist/utils/layout/types.js +8 -0
  235. package/dist/utils/layout.d.ts +9 -0
  236. package/dist/utils/layout.js +25 -0
  237. package/dist/utils/node-helpers.d.ts +7 -0
  238. package/dist/utils/node-helpers.js +101 -0
  239. package/dist/utils/reactflow-converter.d.ts +42 -0
  240. package/dist/utils/reactflow-converter.js +223 -0
  241. package/package.json +70 -0
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Proof of Concept: React Flow Implementation with Custom Choice Edges
3
+ *
4
+ * This demonstrates how we can use React Flow's custom edges feature
5
+ * to implement our choice-based edge system.
6
+ *
7
+ * Key concepts:
8
+ * 1. Dynamic handles on PlayerNode (one handle per choice)
9
+ * 2. Custom ChoiceEdge component that colors based on choice index
10
+ * 3. Edge data stores choiceIndex and choiceId
11
+ *
12
+ * To use this, install: npm install reactflow
13
+ */
14
+ import React, { useCallback, useMemo } from 'react';
15
+ import { NODE_TYPE } from '../types/constants';
16
+ // Color scheme for choice edges (same as current implementation)
17
+ const CHOICE_COLORS = ['#e94560', '#8b5cf6', '#06b6d4', '#22c55e', '#f59e0b'];
18
+ /**
19
+ * Custom Edge Component for Player Choice Connections
20
+ *
21
+ * This edge:
22
+ * - Colors based on choice index (from edge data)
23
+ * - Uses bezier path for smooth curves
24
+ * - Matches our current visual style
25
+ */
26
+ // function ChoiceEdge({ id, sourceX, sourceY, targetX, targetY, data }: EdgeProps) {
27
+ // const [edgePath] = getBezierPath({
28
+ // sourceX,
29
+ // sourceY,
30
+ // targetX,
31
+ // targetY,
32
+ // });
33
+ //
34
+ // const choiceIndex = data?.choiceIndex ?? 0;
35
+ // const color = CHOICE_COLORS[choiceIndex % CHOICE_COLORS.length];
36
+ //
37
+ // return (
38
+ // <>
39
+ // <BaseEdge
40
+ // id={id}
41
+ // path={edgePath}
42
+ // style={{ stroke: color, strokeWidth: 2, opacity: 0.7 }}
43
+ // markerEnd={{
44
+ // type: 'arrowclosed',
45
+ // color: color,
46
+ // }}
47
+ // />
48
+ // </>
49
+ // );
50
+ // }
51
+ /**
52
+ * NPC Node Component
53
+ *
54
+ * Features:
55
+ * - Single output handle at bottom
56
+ * - Speaker + content display
57
+ * - Matches current styling
58
+ */
59
+ // function NPCNode({ data, selected }: NodeProps) {
60
+ // const node = data.node as DialogueNode;
61
+ //
62
+ // return (
63
+ // <div className={`rounded-lg border-2 transition-all ${
64
+ // selected ? 'border-[#e94560] shadow-lg shadow-[#e94560]/20' : 'border-[#2a2a3e]'
65
+ // } bg-[#1a1a2e] min-w-[200px]`}>
66
+ // {/* Input handle at top */}
67
+ // <Handle type="target" position={Position.Top} className="!bg-[#2a2a3e] !border-[#4a4a6a]" />
68
+ //
69
+ // {/* Header */}
70
+ // <div className="px-3 py-1.5 border-b border-[#2a2a3e] bg-[#12121a] flex items-center gap-2 rounded-t-lg">
71
+ // <span className="text-[10px] font-mono text-gray-500 truncate flex-1">{node.id}</span>
72
+ // <span className="text-[10px] text-gray-600">NPC</span>
73
+ // </div>
74
+ //
75
+ // {/* Content */}
76
+ // <div className="px-3 py-2 min-h-[50px]">
77
+ // {node.speaker && (
78
+ // <div className="text-[10px] text-[#e94560] font-medium">{node.speaker}</div>
79
+ // )}
80
+ // <div className="text-xs text-gray-400 line-clamp-2">
81
+ // {node.content.slice(0, 60) + (node.content.length > 60 ? '...' : '')}
82
+ // </div>
83
+ // </div>
84
+ //
85
+ // {/* Output handle at bottom (for nextNodeId connection) */}
86
+ // {node.nextNodeId && (
87
+ // <Handle
88
+ // type="source"
89
+ // position={Position.Bottom}
90
+ // className="!bg-[#2a2a3e] !border-[#4a4a6a] !w-4 !h-4"
91
+ // id="next"
92
+ // />
93
+ // )}
94
+ // </div>
95
+ // );
96
+ // }
97
+ /**
98
+ * Player Node Component
99
+ *
100
+ * Features:
101
+ * - Dynamic handles: one per choice (positioned on right side)
102
+ * - Each handle positioned at choice's Y offset
103
+ * - Matches current styling
104
+ */
105
+ // function PlayerNode({ data, selected }: NodeProps) {
106
+ // const node = data.node as DialogueNode;
107
+ // const choices = node.choices || [];
108
+ //
109
+ // return (
110
+ // <div className={`rounded-lg border-2 transition-all ${
111
+ // selected ? 'border-[#e94560] shadow-lg shadow-[#e94560]/20' : 'border-[#2a2a3e]'
112
+ // } bg-[#1e1e3a] min-w-[200px]`}>
113
+ // {/* Input handle at top */}
114
+ // <Handle type="target" position={Position.Top} className="!bg-[#2a2a3e] !border-[#4a4a6a]" />
115
+ //
116
+ // {/* Header */}
117
+ // <div className="px-3 py-1.5 border-b border-[#2a2a3e] bg-[#16162a] flex items-center gap-2 rounded-t-lg">
118
+ // <span className="text-[10px] font-mono text-gray-500 truncate flex-1">{node.id}</span>
119
+ // <span className="text-[10px] text-gray-600">PLAYER</span>
120
+ // </div>
121
+ //
122
+ // {/* Choices */}
123
+ // <div className="border-t border-[#2a2a3e]">
124
+ // {choices.map((choice, idx) => {
125
+ // const color = CHOICE_COLORS[idx % CHOICE_COLORS.length];
126
+ // // Calculate Y position: base offset + choice index * height
127
+ // const handleY = 20 + idx * 24; // Matches current: fromY + 10 + idx * 24
128
+ //
129
+ // return (
130
+ // <div
131
+ // key={choice.id}
132
+ // className="px-3 py-1 text-[10px] text-gray-400 flex items-center gap-2 border-b border-[#2a2a3e] last:border-0 relative"
133
+ // style={{ minHeight: 24 }}
134
+ // >
135
+ // <div className="flex-1 min-w-0">
136
+ // <span className="truncate block">{choice.text.slice(0, 25)}...</span>
137
+ // </div>
138
+ //
139
+ // {/* Dynamic handle for this choice */}
140
+ // <Handle
141
+ // type="source"
142
+ // position={Position.Right}
143
+ // id={`choice-${idx}`}
144
+ // style={{
145
+ // top: `${handleY}px`,
146
+ // right: '-6px',
147
+ // background: color,
148
+ // borderColor: color,
149
+ // width: '12px',
150
+ // height: '12px',
151
+ // }}
152
+ // className="!bg-[#2a2a3e] !border-2 hover:!border-[#e94560] hover:!bg-[#e94560]/20"
153
+ // />
154
+ // </div>
155
+ // );
156
+ // })}
157
+ // </div>
158
+ // </div>
159
+ // );
160
+ // }
161
+ /**
162
+ * Convert DialogueTree to React Flow format
163
+ */
164
+ export function convertDialogueTreeToReactFlow(dialogue) {
165
+ const nodes = [];
166
+ const edges = [];
167
+ // Define node types
168
+ const nodeTypes = {
169
+ // 'npc': NPCNode,
170
+ // 'player': PlayerNode,
171
+ };
172
+ // Define edge types
173
+ const edgeTypes = {
174
+ // 'choice': ChoiceEdge,
175
+ };
176
+ // Convert nodes
177
+ Object.values(dialogue.nodes).forEach(node => {
178
+ nodes.push({
179
+ id: node.id,
180
+ type: node.type, // 'npc' or 'player'
181
+ position: { x: node.x, y: node.y },
182
+ data: { node },
183
+ selected: false,
184
+ });
185
+ });
186
+ // Convert edges
187
+ Object.values(dialogue.nodes).forEach(node => {
188
+ if (node.type === NODE_TYPE.NPC && node.nextNodeId) {
189
+ // NPC -> next node (single connection)
190
+ edges.push({
191
+ id: `${node.id}-next`,
192
+ source: node.id,
193
+ target: node.nextNodeId,
194
+ sourceHandle: 'next',
195
+ type: 'default', // Use default edge for NPC connections
196
+ style: { stroke: '#4a4a6a', strokeWidth: 2 },
197
+ });
198
+ }
199
+ if (node.type === NODE_TYPE.PLAYER && node.choices) {
200
+ // Player -> multiple choices (one edge per choice)
201
+ node.choices.forEach((choice, idx) => {
202
+ if (choice.nextNodeId) {
203
+ edges.push({
204
+ id: `${node.id}-choice-${idx}`,
205
+ source: node.id,
206
+ target: choice.nextNodeId,
207
+ sourceHandle: `choice-${idx}`, // Connect to specific choice handle
208
+ type: 'choice', // Use custom ChoiceEdge
209
+ data: {
210
+ choiceIndex: idx,
211
+ choiceId: choice.id,
212
+ },
213
+ });
214
+ }
215
+ });
216
+ }
217
+ });
218
+ return { nodes, edges, nodeTypes, edgeTypes };
219
+ }
220
+ /**
221
+ * Main React Flow Component (POC)
222
+ *
223
+ * Usage:
224
+ * ```tsx
225
+ * <ReactFlowProvider>
226
+ * <ReactFlowPOC dialogue={dialogueTree} />
227
+ * </ReactFlowProvider>
228
+ * ```
229
+ */
230
+ export function ReactFlowPOC({ dialogue }) {
231
+ // Convert dialogue to React Flow format
232
+ const { nodes, edges, nodeTypes, edgeTypes } = useMemo(() => convertDialogueTreeToReactFlow(dialogue), [dialogue]);
233
+ // Handle node changes (drag, etc.)
234
+ const onNodesChange = useCallback((changes) => {
235
+ // Update dialogue tree positions
236
+ // This would sync back to our DialogueTree structure
237
+ console.log('Nodes changed:', changes);
238
+ }, []);
239
+ // Handle edge connections
240
+ const onConnect = useCallback((connection) => {
241
+ // Handle new edge connections
242
+ // This would update our DialogueTree structure
243
+ console.log('Connected:', connection);
244
+ }, []);
245
+ // Handle edge changes (delete, etc.)
246
+ const onEdgesChange = useCallback((changes) => {
247
+ console.log('Edges changed:', changes);
248
+ }, []);
249
+ return (React.createElement("div", { className: "w-full h-full" },
250
+ React.createElement("div", { className: "p-8 text-gray-400" },
251
+ React.createElement("h2", { className: "text-xl font-bold mb-4 text-white" }, "React Flow POC"),
252
+ React.createElement("p", { className: "mb-4" }, "This is a proof of concept for migrating to React Flow."),
253
+ React.createElement("p", { className: "mb-4" }, "To use this:"),
254
+ React.createElement("ol", { className: "list-decimal list-inside space-y-2 mb-4" },
255
+ React.createElement("li", null,
256
+ "Install React Flow: ",
257
+ React.createElement("code", { className: "bg-[#1a1a2e] px-2 py-1 rounded" }, "npm install reactflow")),
258
+ React.createElement("li", null, "Uncomment the React Flow imports and components"),
259
+ React.createElement("li", null, "Test with your dialogue tree")),
260
+ React.createElement("div", { className: "mt-8 p-4 bg-[#1a1a2e] rounded" },
261
+ React.createElement("h3", { className: "font-semibold mb-2" }, "Key Features:"),
262
+ React.createElement("ul", { className: "list-disc list-inside space-y-1 text-sm" },
263
+ React.createElement("li", null, "\u2705 Custom ChoiceEdge component with color-coded edges"),
264
+ React.createElement("li", null, "\u2705 Dynamic handles on PlayerNode (one per choice)"),
265
+ React.createElement("li", null, "\u2705 NPCNode with single output handle"),
266
+ React.createElement("li", null, "\u2705 Conversion functions (DialogueTree \u2194 React Flow)"),
267
+ React.createElement("li", null, "\u2705 Matches current visual style"))),
268
+ React.createElement("div", { className: "mt-4 p-4 bg-[#1a1a2e] rounded" },
269
+ React.createElement("h3", { className: "font-semibold mb-2" }, "Current Stats:"),
270
+ React.createElement("p", { className: "text-sm" },
271
+ "Nodes: ",
272
+ nodes.length,
273
+ ", Edges: ",
274
+ edges.length)))));
275
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { DialogueTree, DialogueNode, Choice } from '../types';
3
+ import { FlagState, DialogueResult } from '../types/game-state';
4
+ import { type FlattenConfig } from '../utils/game-state-flattener';
5
+ export interface ScenePlayerProps {
6
+ dialogue: DialogueTree;
7
+ gameState: Record<string, any>;
8
+ startNodeId?: string;
9
+ onComplete: (result: DialogueResult) => void;
10
+ onFlagUpdate?: (flags: FlagState) => void;
11
+ flattenConfig?: FlattenConfig;
12
+ onNodeEnter?: (nodeId: string, node: DialogueNode) => void;
13
+ onNodeExit?: (nodeId: string, node: DialogueNode) => void;
14
+ onChoiceSelect?: (nodeId: string, choice: Choice) => void;
15
+ onDialogueStart?: () => void;
16
+ onDialogueEnd?: () => void;
17
+ }
18
+ export declare function ScenePlayer({ dialogue, gameState, startNodeId, onComplete, onFlagUpdate, flattenConfig, onNodeEnter, onNodeExit, onChoiceSelect, onDialogueStart, onDialogueEnd, }: ScenePlayerProps): React.JSX.Element;
@@ -0,0 +1,160 @@
1
+ import React, { useState, useEffect, useRef, useMemo } from 'react';
2
+ import { mergeFlagUpdates } from '../lib/flag-manager';
3
+ import { validateGameState, extractFlagsFromGameState } from '../utils/game-state-flattener';
4
+ export function ScenePlayer({ dialogue, gameState, startNodeId, onComplete, onFlagUpdate, flattenConfig, onNodeEnter, onNodeExit, onChoiceSelect, onDialogueStart, onDialogueEnd, }) {
5
+ // Validate and extract flags from gameState
6
+ const initialFlags = useMemo(() => {
7
+ try {
8
+ validateGameState(gameState);
9
+ return extractFlagsFromGameState(gameState, flattenConfig);
10
+ }
11
+ catch (error) {
12
+ console.error('ScenePlayer: Invalid gameState', error);
13
+ throw error;
14
+ }
15
+ }, [gameState, flattenConfig]);
16
+ const [currentNodeId, setCurrentNodeId] = useState(startNodeId || dialogue.startNodeId);
17
+ const [flags, setFlags] = useState(initialFlags);
18
+ const [history, setHistory] = useState([]);
19
+ const [isTyping, setIsTyping] = useState(false);
20
+ const [visitedNodes, setVisitedNodes] = useState(new Set());
21
+ const chatEndRef = useRef(null);
22
+ // Re-extract flags when gameState changes
23
+ useEffect(() => {
24
+ try {
25
+ validateGameState(gameState);
26
+ const newFlags = extractFlagsFromGameState(gameState, flattenConfig);
27
+ setFlags(newFlags);
28
+ }
29
+ catch (error) {
30
+ console.error('ScenePlayer: Failed to update flags from gameState', error);
31
+ }
32
+ }, [gameState, flattenConfig]);
33
+ // Initialize dialogue
34
+ useEffect(() => {
35
+ if (currentNodeId === dialogue.startNodeId) {
36
+ onDialogueStart?.();
37
+ }
38
+ }, []); // Only on mount
39
+ // Process current node
40
+ useEffect(() => {
41
+ const node = dialogue.nodes[currentNodeId];
42
+ if (!node)
43
+ return;
44
+ // Call onNodeEnter hook
45
+ onNodeEnter?.(currentNodeId, node);
46
+ if (node.type === 'npc') {
47
+ setIsTyping(true);
48
+ const timer = setTimeout(() => {
49
+ // Mark as visited
50
+ setVisitedNodes(prev => new Set([...prev, node.id]));
51
+ // Update flags
52
+ if (node.setFlags && node.setFlags.length > 0) {
53
+ const updated = mergeFlagUpdates(flags, node.setFlags);
54
+ setFlags(updated);
55
+ onFlagUpdate?.(updated);
56
+ }
57
+ // Add to history
58
+ setHistory(prev => [...prev, {
59
+ nodeId: node.id,
60
+ type: 'npc',
61
+ speaker: node.speaker,
62
+ content: node.content
63
+ }]);
64
+ setIsTyping(false);
65
+ // Call onNodeExit before moving to next
66
+ onNodeExit?.(currentNodeId, node);
67
+ // Auto-advance if there's a next node
68
+ if (node.nextNodeId) {
69
+ setTimeout(() => setCurrentNodeId(node.nextNodeId), 300);
70
+ }
71
+ else {
72
+ // Dialogue complete
73
+ onDialogueEnd?.();
74
+ onComplete({
75
+ updatedFlags: flags,
76
+ dialogueTree: dialogue,
77
+ completedNodeIds: Array.from(visitedNodes)
78
+ });
79
+ }
80
+ }, 500);
81
+ return () => clearTimeout(timer);
82
+ }
83
+ }, [currentNodeId, dialogue, flags, onNodeEnter, onNodeExit, onDialogueEnd, onComplete, onFlagUpdate]);
84
+ useEffect(() => {
85
+ chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
86
+ }, [history, isTyping]);
87
+ const currentNode = dialogue.nodes[currentNodeId];
88
+ // Filter choices based on conditions and flags
89
+ const availableChoices = currentNode?.choices?.filter(choice => {
90
+ if (!choice.conditions)
91
+ return true;
92
+ return choice.conditions.every(cond => {
93
+ const flagValue = flags[cond.flag];
94
+ const hasFlag = flagValue !== undefined && flagValue !== false && flagValue !== 0 && flagValue !== '';
95
+ return cond.operator === 'is_set' ? hasFlag : !hasFlag;
96
+ });
97
+ }) || [];
98
+ const handleChoice = (choice) => {
99
+ const currentNode = dialogue.nodes[currentNodeId];
100
+ // Call onChoiceSelect hook
101
+ onChoiceSelect?.(currentNodeId, choice);
102
+ // Call onNodeExit for current player node
103
+ if (currentNode) {
104
+ onNodeExit?.(currentNodeId, currentNode);
105
+ }
106
+ // Add to history
107
+ setHistory(prev => [...prev, {
108
+ nodeId: choice.id,
109
+ type: 'player',
110
+ content: choice.text
111
+ }]);
112
+ // Update flags
113
+ if (choice.setFlags && choice.setFlags.length > 0) {
114
+ const updated = mergeFlagUpdates(flags, choice.setFlags);
115
+ setFlags(updated);
116
+ onFlagUpdate?.(updated);
117
+ }
118
+ // Move to next node
119
+ if (choice.nextNodeId) {
120
+ setCurrentNodeId(choice.nextNodeId);
121
+ }
122
+ else {
123
+ // Choice leads nowhere - dialogue complete
124
+ onComplete({
125
+ updatedFlags: flags,
126
+ dialogueTree: dialogue,
127
+ completedNodeIds: Array.from(visitedNodes)
128
+ });
129
+ }
130
+ };
131
+ return (React.createElement("div", { className: "flex-1 flex flex-col" },
132
+ React.createElement("div", { className: "flex-1 overflow-y-auto p-4" },
133
+ React.createElement("div", { className: "max-w-2xl mx-auto space-y-4" },
134
+ history.map((entry, idx) => (React.createElement("div", { key: idx, className: `flex ${entry.type === 'player' ? 'justify-end' : 'justify-start'}` },
135
+ React.createElement("div", { className: `max-w-[80%] rounded-2xl px-4 py-3 ${entry.type === 'player'
136
+ ? 'bg-[#e94560] text-white rounded-br-md'
137
+ : 'bg-[#1a1a2e] text-gray-100 rounded-bl-md'}` },
138
+ entry.type === 'npc' && entry.speaker && (React.createElement("div", { className: "text-xs text-[#e94560] font-medium mb-1" }, entry.speaker)),
139
+ React.createElement("div", { className: "whitespace-pre-wrap" }, entry.content))))),
140
+ isTyping && (React.createElement("div", { className: "flex justify-start" },
141
+ React.createElement("div", { className: "bg-[#1a1a2e] rounded-2xl rounded-bl-md px-4 py-3" },
142
+ React.createElement("div", { className: "flex gap-1" },
143
+ React.createElement("span", { className: "w-2 h-2 bg-[#e94560] rounded-full animate-bounce", style: { animationDelay: '0ms' } }),
144
+ React.createElement("span", { className: "w-2 h-2 bg-[#e94560] rounded-full animate-bounce", style: { animationDelay: '150ms' } }),
145
+ React.createElement("span", { className: "w-2 h-2 bg-[#e94560] rounded-full animate-bounce", style: { animationDelay: '300ms' } }))))),
146
+ React.createElement("div", { ref: chatEndRef }))),
147
+ currentNode?.type === 'player' && !isTyping && availableChoices.length > 0 && (React.createElement("div", { className: "border-t border-[#1a1a2e] bg-[#0d0d14]/80 backdrop-blur-sm p-4" },
148
+ React.createElement("div", { className: "max-w-2xl mx-auto space-y-2" }, availableChoices.map((choice) => (React.createElement("button", { key: choice.id, onClick: () => handleChoice(choice), className: "w-full text-left px-4 py-3 rounded-lg border border-[#2a2a3e] hover:border-[#e94560] bg-[#12121a] hover:bg-[#1a1a2e] text-gray-200 transition-all group flex items-center justify-between" },
149
+ React.createElement("span", null, choice.text),
150
+ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600 group-hover:text-[#e94560] transition-colors" },
151
+ React.createElement("polyline", { points: "9 18 15 12 9 6" })))))))),
152
+ currentNode?.type === 'npc' && !currentNode.nextNodeId && !isTyping && (React.createElement("div", { className: "border-t border-[#1a1a2e] bg-[#0d0d14]/80 backdrop-blur-sm p-4" },
153
+ React.createElement("div", { className: "max-w-2xl mx-auto text-center" },
154
+ React.createElement("p", { className: "text-gray-500 mb-3" }, "Dialogue complete"),
155
+ React.createElement("button", { onClick: () => onComplete({
156
+ updatedFlags: flags,
157
+ dialogueTree: dialogue,
158
+ completedNodeIds: Array.from(visitedNodes)
159
+ }), className: "px-4 py-2 bg-[#e94560] hover:bg-[#d63850] text-white rounded-lg transition-colors" }, "Close"))))));
160
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { DialogueTree } from '../types';
3
+ interface YarnViewProps {
4
+ dialogue: DialogueTree;
5
+ onExport: () => void;
6
+ onImport?: (yarn: string) => void;
7
+ }
8
+ export declare function YarnView({ dialogue, onExport, onImport }: YarnViewProps): React.JSX.Element;
9
+ export {};
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { exportToYarn } from '../lib/yarn-converter';
3
+ export function YarnView({ dialogue, onExport, onImport }) {
4
+ const fileInputRef = React.useRef(null);
5
+ const handleImport = (e) => {
6
+ const file = e.target.files?.[0];
7
+ if (!file)
8
+ return;
9
+ const reader = new FileReader();
10
+ reader.onload = (ev) => {
11
+ const content = ev.target?.result;
12
+ if (onImport) {
13
+ onImport(content);
14
+ }
15
+ };
16
+ reader.readAsText(file);
17
+ e.target.value = '';
18
+ };
19
+ return (React.createElement("main", { className: "flex-1 flex flex-col bg-[#0d0d14] overflow-hidden" },
20
+ React.createElement("div", { className: "border-b border-[#1a1a2e] px-4 py-2 flex items-center justify-between flex-shrink-0" },
21
+ React.createElement("span", { className: "text-sm text-gray-400" }, "Yarn Spinner Output"),
22
+ React.createElement("div", { className: "flex items-center gap-2" },
23
+ onImport && (React.createElement(React.Fragment, null,
24
+ React.createElement("button", { onClick: () => fileInputRef.current?.click(), className: "px-3 py-1.5 bg-[#2a2a3e] hover:bg-[#3a3a4e] text-gray-300 text-sm rounded flex items-center gap-2" },
25
+ React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
26
+ React.createElement("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
27
+ React.createElement("polyline", { points: "17 8 12 3 7 8" }),
28
+ React.createElement("line", { x1: "12", y1: "3", x2: "12", y2: "15" })),
29
+ "Import .yarn"),
30
+ React.createElement("input", { ref: fileInputRef, type: "file", accept: ".yarn", onChange: handleImport, className: "hidden" }))),
31
+ React.createElement("button", { onClick: onExport, className: "px-3 py-1.5 bg-[#e94560] hover:bg-[#d63850] text-white text-sm rounded flex items-center gap-2" },
32
+ React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
33
+ React.createElement("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
34
+ React.createElement("polyline", { points: "7 10 12 15 17 10" }),
35
+ React.createElement("line", { x1: "12", y1: "15", x2: "12", y2: "3" })),
36
+ "Download .yarn"))),
37
+ React.createElement("div", { className: "flex-1 overflow-y-auto p-4 min-h-0" },
38
+ React.createElement("pre", { className: "font-mono text-sm text-gray-300 whitespace-pre-wrap bg-[#08080c] rounded-lg p-4 border border-[#1a1a2e]" }, exportToYarn(dialogue)))));
39
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface ZoomControlsProps {
3
+ scale: number;
4
+ onZoomIn: () => void;
5
+ onZoomOut: () => void;
6
+ onZoomFit: () => void;
7
+ onZoomToSelection?: () => void;
8
+ className?: string;
9
+ }
10
+ export declare function ZoomControls({ scale, onZoomIn, onZoomOut, onZoomFit, onZoomToSelection, className }: ZoomControlsProps): React.JSX.Element;
11
+ export {};
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ export function ZoomControls({ scale, onZoomIn, onZoomOut, onZoomFit, onZoomToSelection, className }) {
3
+ const zoomPercent = Math.round(scale * 100);
4
+ return (React.createElement("div", { className: `flex flex-col gap-1 ${className}` },
5
+ React.createElement("div", { className: "bg-[#0d0d14] border border-[#2a2a3e] rounded px-2 py-1 text-xs text-gray-400 text-center mb-1" },
6
+ zoomPercent,
7
+ "%"),
8
+ React.createElement("div", { className: "flex flex-col gap-1 bg-[#0d0d14] border border-[#2a2a3e] rounded-lg p-1" },
9
+ React.createElement("button", { onClick: onZoomIn, className: "p-1.5 hover:bg-[#1a1a2e] rounded text-gray-400 hover:text-white transition-colors", title: "Zoom In (Ctrl +)" },
10
+ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
11
+ React.createElement("circle", { cx: "11", cy: "11", r: "8" }),
12
+ React.createElement("line", { x1: "11", y1: "8", x2: "11", y2: "14" }),
13
+ React.createElement("line", { x1: "8", y1: "11", x2: "14", y2: "11" }))),
14
+ React.createElement("button", { onClick: onZoomOut, className: "p-1.5 hover:bg-[#1a1a2e] rounded text-gray-400 hover:text-white transition-colors", title: "Zoom Out (Ctrl -)" },
15
+ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
16
+ React.createElement("circle", { cx: "11", cy: "11", r: "8" }),
17
+ React.createElement("line", { x1: "8", y1: "11", x2: "14", y2: "11" }))),
18
+ React.createElement("div", { className: "h-px bg-[#2a2a3e] my-1" }),
19
+ React.createElement("button", { onClick: onZoomFit, className: "p-1.5 hover:bg-[#1a1a2e] rounded text-gray-400 hover:text-white transition-colors", title: "Zoom to Fit (Ctrl 0)" },
20
+ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
21
+ React.createElement("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
22
+ React.createElement("path", { d: "M8 8h8v8" }))),
23
+ onZoomToSelection && (React.createElement("button", { onClick: onZoomToSelection, className: "p-1.5 hover:bg-[#1a1a2e] rounded text-gray-400 hover:text-white transition-colors", title: "Zoom to Selection" },
24
+ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
25
+ React.createElement("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
26
+ React.createElement("path", { d: "M8 8h8v8" }),
27
+ React.createElement("circle", { cx: "12", cy: "12", r: "2" })))))));
28
+ }
@@ -0,0 +1,29 @@
1
+ import { DialogueTree } from '../types';
2
+ import { FlagSchema } from '../types/flags';
3
+ /**
4
+ * Load an example dialogue from a Yarn file
5
+ * This is the unified way to load examples - all examples are Yarn files
6
+ */
7
+ export declare function loadExampleDialogue(exampleId: string): Promise<DialogueTree | null>;
8
+ /**
9
+ * Get flag schema for an example
10
+ */
11
+ export declare function getFlagSchemaForExample(exampleId: string): FlagSchema | null;
12
+ /**
13
+ * Get all examples with their metadata
14
+ */
15
+ export declare function getAllExamples(): {
16
+ flagSchema: FlagSchema | null;
17
+ id: string;
18
+ title: string;
19
+ description: string;
20
+ filename: string;
21
+ flagSchemaId: string;
22
+ nodeCount?: number;
23
+ features: string[];
24
+ }[];
25
+ /**
26
+ * Synchronous version for use with pre-loaded examples
27
+ * This is used when examples are bundled/inlined
28
+ */
29
+ export declare function loadExampleDialogueSync(exampleId: string, yarnContent: string): DialogueTree | null;