learngraph 0.5.0 → 0.8.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 (289) hide show
  1. package/dist/cjs/api/routes/analytics.js +288 -0
  2. package/dist/cjs/api/routes/analytics.js.map +1 -0
  3. package/dist/cjs/api/routes/assessments.js +269 -0
  4. package/dist/cjs/api/routes/assessments.js.map +1 -0
  5. package/dist/cjs/api/routes/curriculum.js +345 -0
  6. package/dist/cjs/api/routes/curriculum.js.map +1 -0
  7. package/dist/cjs/api/routes/edges.js +162 -0
  8. package/dist/cjs/api/routes/edges.js.map +1 -0
  9. package/dist/cjs/api/routes/explore.js +224 -0
  10. package/dist/cjs/api/routes/explore.js.map +1 -0
  11. package/dist/cjs/api/routes/learners.js +324 -0
  12. package/dist/cjs/api/routes/learners.js.map +1 -0
  13. package/dist/cjs/api/routes/me.js +404 -0
  14. package/dist/cjs/api/routes/me.js.map +1 -0
  15. package/dist/cjs/api/routes/skills.js +319 -0
  16. package/dist/cjs/api/routes/skills.js.map +1 -0
  17. package/dist/cjs/api/server.js +185 -0
  18. package/dist/cjs/api/server.js.map +1 -0
  19. package/dist/cjs/api/types.js +10 -0
  20. package/dist/cjs/api/types.js.map +1 -0
  21. package/dist/cjs/assessment/adaptive.js +390 -0
  22. package/dist/cjs/assessment/adaptive.js.map +1 -0
  23. package/dist/cjs/assessment/bkt.js +362 -0
  24. package/dist/cjs/assessment/bkt.js.map +1 -0
  25. package/dist/cjs/assessment/index.js +54 -0
  26. package/dist/cjs/assessment/index.js.map +1 -0
  27. package/dist/cjs/assessment/irt.js +420 -0
  28. package/dist/cjs/assessment/irt.js.map +1 -0
  29. package/dist/cjs/assessment/mastery-engine.js +411 -0
  30. package/dist/cjs/assessment/mastery-engine.js.map +1 -0
  31. package/dist/cjs/components/LearningPathView.js +320 -0
  32. package/dist/cjs/components/LearningPathView.js.map +1 -0
  33. package/dist/cjs/components/ProgressDashboard.js +308 -0
  34. package/dist/cjs/components/ProgressDashboard.js.map +1 -0
  35. package/dist/cjs/components/SkillCard.js +264 -0
  36. package/dist/cjs/components/SkillCard.js.map +1 -0
  37. package/dist/cjs/components/SkillExplorer.js +401 -0
  38. package/dist/cjs/components/SkillExplorer.js.map +1 -0
  39. package/dist/cjs/components/SkillGraph.js +636 -0
  40. package/dist/cjs/components/SkillGraph.js.map +1 -0
  41. package/dist/cjs/components/hooks.js +520 -0
  42. package/dist/cjs/components/hooks.js.map +1 -0
  43. package/dist/cjs/components/index.js +77 -0
  44. package/dist/cjs/components/index.js.map +1 -0
  45. package/dist/cjs/components/types.js +34 -0
  46. package/dist/cjs/components/types.js.map +1 -0
  47. package/dist/cjs/embeddings/base.js +104 -0
  48. package/dist/cjs/embeddings/base.js.map +1 -0
  49. package/dist/cjs/embeddings/index.js +91 -0
  50. package/dist/cjs/embeddings/index.js.map +1 -0
  51. package/dist/cjs/embeddings/local.js +224 -0
  52. package/dist/cjs/embeddings/local.js.map +1 -0
  53. package/dist/cjs/embeddings/openai.js +169 -0
  54. package/dist/cjs/embeddings/openai.js.map +1 -0
  55. package/dist/cjs/index.js +59 -1
  56. package/dist/cjs/index.js.map +1 -1
  57. package/dist/cjs/llm/adapters/anthropic.js +91 -3
  58. package/dist/cjs/llm/adapters/anthropic.js.map +1 -1
  59. package/dist/cjs/llm/adapters/gemini.js +101 -8
  60. package/dist/cjs/llm/adapters/gemini.js.map +1 -1
  61. package/dist/cjs/llm/adapters/index.js +42 -1
  62. package/dist/cjs/llm/adapters/index.js.map +1 -1
  63. package/dist/cjs/llm/adapters/ollama.js +120 -3
  64. package/dist/cjs/llm/adapters/ollama.js.map +1 -1
  65. package/dist/cjs/llm/adapters/openai.js +108 -2
  66. package/dist/cjs/llm/adapters/openai.js.map +1 -1
  67. package/dist/cjs/llm/graphrag-orchestrator.js +1004 -0
  68. package/dist/cjs/llm/graphrag-orchestrator.js.map +1 -0
  69. package/dist/cjs/llm/index.js +7 -1
  70. package/dist/cjs/llm/index.js.map +1 -1
  71. package/dist/cjs/mcp/cli.js +302 -0
  72. package/dist/cjs/mcp/cli.js.map +1 -0
  73. package/dist/cjs/mcp/index.js +79 -0
  74. package/dist/cjs/mcp/index.js.map +1 -0
  75. package/dist/cjs/mcp/prompts.js +425 -0
  76. package/dist/cjs/mcp/prompts.js.map +1 -0
  77. package/dist/cjs/mcp/resources.js +371 -0
  78. package/dist/cjs/mcp/resources.js.map +1 -0
  79. package/dist/cjs/mcp/server.js +410 -0
  80. package/dist/cjs/mcp/server.js.map +1 -0
  81. package/dist/cjs/mcp/tools.js +612 -0
  82. package/dist/cjs/mcp/tools.js.map +1 -0
  83. package/dist/cjs/mcp/types.js +10 -0
  84. package/dist/cjs/mcp/types.js.map +1 -0
  85. package/dist/cjs/storage/index.js +15 -1
  86. package/dist/cjs/storage/index.js.map +1 -1
  87. package/dist/cjs/storage/neo4j-graphrag.js +596 -0
  88. package/dist/cjs/storage/neo4j-graphrag.js.map +1 -0
  89. package/dist/cjs/types/assessment.js +46 -0
  90. package/dist/cjs/types/assessment.js.map +1 -0
  91. package/dist/cjs/types/bloom.js +12 -1
  92. package/dist/cjs/types/bloom.js.map +1 -1
  93. package/dist/cjs/types/graphrag.js +11 -0
  94. package/dist/cjs/types/graphrag.js.map +1 -0
  95. package/dist/cjs/types/index.js +7 -1
  96. package/dist/cjs/types/index.js.map +1 -1
  97. package/dist/esm/api/routes/analytics.js +285 -0
  98. package/dist/esm/api/routes/analytics.js.map +1 -0
  99. package/dist/esm/api/routes/assessments.js +266 -0
  100. package/dist/esm/api/routes/assessments.js.map +1 -0
  101. package/dist/esm/api/routes/curriculum.js +342 -0
  102. package/dist/esm/api/routes/curriculum.js.map +1 -0
  103. package/dist/esm/api/routes/edges.js +159 -0
  104. package/dist/esm/api/routes/edges.js.map +1 -0
  105. package/dist/esm/api/routes/explore.js +221 -0
  106. package/dist/esm/api/routes/explore.js.map +1 -0
  107. package/dist/esm/api/routes/learners.js +321 -0
  108. package/dist/esm/api/routes/learners.js.map +1 -0
  109. package/dist/esm/api/routes/me.js +401 -0
  110. package/dist/esm/api/routes/me.js.map +1 -0
  111. package/dist/esm/api/routes/skills.js +316 -0
  112. package/dist/esm/api/routes/skills.js.map +1 -0
  113. package/dist/esm/api/server.js +179 -0
  114. package/dist/esm/api/server.js.map +1 -0
  115. package/dist/esm/api/types.js +9 -0
  116. package/dist/esm/api/types.js.map +1 -0
  117. package/dist/esm/assessment/adaptive.js +384 -0
  118. package/dist/esm/assessment/adaptive.js.map +1 -0
  119. package/dist/esm/assessment/bkt.js +354 -0
  120. package/dist/esm/assessment/bkt.js.map +1 -0
  121. package/dist/esm/assessment/index.js +21 -0
  122. package/dist/esm/assessment/index.js.map +1 -0
  123. package/dist/esm/assessment/irt.js +406 -0
  124. package/dist/esm/assessment/irt.js.map +1 -0
  125. package/dist/esm/assessment/mastery-engine.js +406 -0
  126. package/dist/esm/assessment/mastery-engine.js.map +1 -0
  127. package/dist/esm/components/LearningPathView.js +316 -0
  128. package/dist/esm/components/LearningPathView.js.map +1 -0
  129. package/dist/esm/components/ProgressDashboard.js +304 -0
  130. package/dist/esm/components/ProgressDashboard.js.map +1 -0
  131. package/dist/esm/components/SkillCard.js +260 -0
  132. package/dist/esm/components/SkillCard.js.map +1 -0
  133. package/dist/esm/components/SkillExplorer.js +397 -0
  134. package/dist/esm/components/SkillExplorer.js.map +1 -0
  135. package/dist/esm/components/SkillGraph.js +599 -0
  136. package/dist/esm/components/SkillGraph.js.map +1 -0
  137. package/dist/esm/components/hooks.js +512 -0
  138. package/dist/esm/components/hooks.js.map +1 -0
  139. package/dist/esm/components/index.js +61 -0
  140. package/dist/esm/components/index.js.map +1 -0
  141. package/dist/esm/components/types.js +31 -0
  142. package/dist/esm/components/types.js.map +1 -0
  143. package/dist/esm/embeddings/base.js +99 -0
  144. package/dist/esm/embeddings/base.js.map +1 -0
  145. package/dist/esm/embeddings/index.js +79 -0
  146. package/dist/esm/embeddings/index.js.map +1 -0
  147. package/dist/esm/embeddings/local.js +219 -0
  148. package/dist/esm/embeddings/local.js.map +1 -0
  149. package/dist/esm/embeddings/openai.js +164 -0
  150. package/dist/esm/embeddings/openai.js.map +1 -0
  151. package/dist/esm/index.js +28 -0
  152. package/dist/esm/index.js.map +1 -1
  153. package/dist/esm/llm/adapters/anthropic.js +88 -2
  154. package/dist/esm/llm/adapters/anthropic.js.map +1 -1
  155. package/dist/esm/llm/adapters/gemini.js +98 -7
  156. package/dist/esm/llm/adapters/gemini.js.map +1 -1
  157. package/dist/esm/llm/adapters/index.js +15 -4
  158. package/dist/esm/llm/adapters/index.js.map +1 -1
  159. package/dist/esm/llm/adapters/ollama.js +117 -2
  160. package/dist/esm/llm/adapters/ollama.js.map +1 -1
  161. package/dist/esm/llm/adapters/openai.js +105 -1
  162. package/dist/esm/llm/adapters/openai.js.map +1 -1
  163. package/dist/esm/llm/graphrag-orchestrator.js +999 -0
  164. package/dist/esm/llm/graphrag-orchestrator.js.map +1 -0
  165. package/dist/esm/llm/index.js +4 -0
  166. package/dist/esm/llm/index.js.map +1 -1
  167. package/dist/esm/mcp/cli.js +267 -0
  168. package/dist/esm/mcp/cli.js.map +1 -0
  169. package/dist/esm/mcp/index.js +39 -0
  170. package/dist/esm/mcp/index.js.map +1 -0
  171. package/dist/esm/mcp/prompts.js +419 -0
  172. package/dist/esm/mcp/prompts.js.map +1 -0
  173. package/dist/esm/mcp/resources.js +359 -0
  174. package/dist/esm/mcp/resources.js.map +1 -0
  175. package/dist/esm/mcp/server.js +372 -0
  176. package/dist/esm/mcp/server.js.map +1 -0
  177. package/dist/esm/mcp/tools.js +598 -0
  178. package/dist/esm/mcp/tools.js.map +1 -0
  179. package/dist/esm/mcp/types.js +9 -0
  180. package/dist/esm/mcp/types.js.map +1 -0
  181. package/dist/esm/storage/index.js +8 -0
  182. package/dist/esm/storage/index.js.map +1 -1
  183. package/dist/esm/storage/neo4j-graphrag.js +591 -0
  184. package/dist/esm/storage/neo4j-graphrag.js.map +1 -0
  185. package/dist/esm/types/assessment.js +40 -0
  186. package/dist/esm/types/assessment.js.map +1 -0
  187. package/dist/esm/types/bloom.js +11 -0
  188. package/dist/esm/types/bloom.js.map +1 -1
  189. package/dist/esm/types/graphrag.js +10 -0
  190. package/dist/esm/types/graphrag.js.map +1 -0
  191. package/dist/esm/types/index.js +2 -1
  192. package/dist/esm/types/index.js.map +1 -1
  193. package/dist/types/api/routes/analytics.d.ts +14 -0
  194. package/dist/types/api/routes/analytics.d.ts.map +1 -0
  195. package/dist/types/api/routes/assessments.d.ts +14 -0
  196. package/dist/types/api/routes/assessments.d.ts.map +1 -0
  197. package/dist/types/api/routes/curriculum.d.ts +14 -0
  198. package/dist/types/api/routes/curriculum.d.ts.map +1 -0
  199. package/dist/types/api/routes/edges.d.ts +14 -0
  200. package/dist/types/api/routes/edges.d.ts.map +1 -0
  201. package/dist/types/api/routes/explore.d.ts +14 -0
  202. package/dist/types/api/routes/explore.d.ts.map +1 -0
  203. package/dist/types/api/routes/learners.d.ts +14 -0
  204. package/dist/types/api/routes/learners.d.ts.map +1 -0
  205. package/dist/types/api/routes/me.d.ts +14 -0
  206. package/dist/types/api/routes/me.d.ts.map +1 -0
  207. package/dist/types/api/routes/skills.d.ts +14 -0
  208. package/dist/types/api/routes/skills.d.ts.map +1 -0
  209. package/dist/types/api/server.d.ts +147 -0
  210. package/dist/types/api/server.d.ts.map +1 -0
  211. package/dist/types/api/types.d.ts +443 -0
  212. package/dist/types/api/types.d.ts.map +1 -0
  213. package/dist/types/assessment/adaptive.d.ts +155 -0
  214. package/dist/types/assessment/adaptive.d.ts.map +1 -0
  215. package/dist/types/assessment/bkt.d.ts +185 -0
  216. package/dist/types/assessment/bkt.d.ts.map +1 -0
  217. package/dist/types/assessment/index.d.ts +18 -0
  218. package/dist/types/assessment/index.d.ts.map +1 -0
  219. package/dist/types/assessment/irt.d.ts +159 -0
  220. package/dist/types/assessment/irt.d.ts.map +1 -0
  221. package/dist/types/assessment/mastery-engine.d.ts +178 -0
  222. package/dist/types/assessment/mastery-engine.d.ts.map +1 -0
  223. package/dist/types/components/LearningPathView.d.ts +40 -0
  224. package/dist/types/components/LearningPathView.d.ts.map +1 -0
  225. package/dist/types/components/ProgressDashboard.d.ts +49 -0
  226. package/dist/types/components/ProgressDashboard.d.ts.map +1 -0
  227. package/dist/types/components/SkillCard.d.ts +34 -0
  228. package/dist/types/components/SkillCard.d.ts.map +1 -0
  229. package/dist/types/components/SkillExplorer.d.ts +39 -0
  230. package/dist/types/components/SkillExplorer.d.ts.map +1 -0
  231. package/dist/types/components/SkillGraph.d.ts +38 -0
  232. package/dist/types/components/SkillGraph.d.ts.map +1 -0
  233. package/dist/types/components/hooks.d.ts +187 -0
  234. package/dist/types/components/hooks.d.ts.map +1 -0
  235. package/dist/types/components/index.d.ts +59 -0
  236. package/dist/types/components/index.d.ts.map +1 -0
  237. package/dist/types/components/types.d.ts +410 -0
  238. package/dist/types/components/types.d.ts.map +1 -0
  239. package/dist/types/embeddings/base.d.ts +51 -0
  240. package/dist/types/embeddings/base.d.ts.map +1 -0
  241. package/dist/types/embeddings/index.d.ts +48 -0
  242. package/dist/types/embeddings/index.d.ts.map +1 -0
  243. package/dist/types/embeddings/local.d.ts +82 -0
  244. package/dist/types/embeddings/local.d.ts.map +1 -0
  245. package/dist/types/embeddings/openai.d.ts +48 -0
  246. package/dist/types/embeddings/openai.d.ts.map +1 -0
  247. package/dist/types/index.d.ts +3 -0
  248. package/dist/types/index.d.ts.map +1 -1
  249. package/dist/types/llm/adapters/anthropic.d.ts +84 -1
  250. package/dist/types/llm/adapters/anthropic.d.ts.map +1 -1
  251. package/dist/types/llm/adapters/gemini.d.ts +93 -6
  252. package/dist/types/llm/adapters/gemini.d.ts.map +1 -1
  253. package/dist/types/llm/adapters/index.d.ts +13 -4
  254. package/dist/types/llm/adapters/index.d.ts.map +1 -1
  255. package/dist/types/llm/adapters/ollama.d.ts +126 -1
  256. package/dist/types/llm/adapters/ollama.d.ts.map +1 -1
  257. package/dist/types/llm/adapters/openai.d.ts +104 -1
  258. package/dist/types/llm/adapters/openai.d.ts.map +1 -1
  259. package/dist/types/llm/graphrag-orchestrator.d.ts +280 -0
  260. package/dist/types/llm/graphrag-orchestrator.d.ts.map +1 -0
  261. package/dist/types/llm/index.d.ts +2 -0
  262. package/dist/types/llm/index.d.ts.map +1 -1
  263. package/dist/types/mcp/cli.d.ts +15 -0
  264. package/dist/types/mcp/cli.d.ts.map +1 -0
  265. package/dist/types/mcp/index.d.ts +32 -0
  266. package/dist/types/mcp/index.d.ts.map +1 -0
  267. package/dist/types/mcp/prompts.d.ts +27 -0
  268. package/dist/types/mcp/prompts.d.ts.map +1 -0
  269. package/dist/types/mcp/resources.d.ts +59 -0
  270. package/dist/types/mcp/resources.d.ts.map +1 -0
  271. package/dist/types/mcp/server.d.ts +136 -0
  272. package/dist/types/mcp/server.d.ts.map +1 -0
  273. package/dist/types/mcp/tools.d.ts +344 -0
  274. package/dist/types/mcp/tools.d.ts.map +1 -0
  275. package/dist/types/mcp/types.d.ts +137 -0
  276. package/dist/types/mcp/types.d.ts.map +1 -0
  277. package/dist/types/storage/index.d.ts +2 -0
  278. package/dist/types/storage/index.d.ts.map +1 -1
  279. package/dist/types/storage/neo4j-graphrag.d.ts +106 -0
  280. package/dist/types/storage/neo4j-graphrag.d.ts.map +1 -0
  281. package/dist/types/types/assessment.d.ts +512 -0
  282. package/dist/types/types/assessment.d.ts.map +1 -0
  283. package/dist/types/types/bloom.d.ts +4 -0
  284. package/dist/types/types/bloom.d.ts.map +1 -1
  285. package/dist/types/types/graphrag.d.ts +335 -0
  286. package/dist/types/types/graphrag.d.ts.map +1 -0
  287. package/dist/types/types/index.d.ts +4 -1
  288. package/dist/types/types/index.d.ts.map +1 -1
  289. package/package.json +48 -3
@@ -0,0 +1,636 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SkillGraph = void 0;
37
+ const jsx_runtime_1 = require("react/jsx-runtime");
38
+ /**
39
+ * SkillGraph Component
40
+ *
41
+ * Interactive graph visualization for skills and prerequisites.
42
+ * Uses React Flow for rendering and interaction.
43
+ *
44
+ * @packageDocumentation
45
+ */
46
+ const react_1 = require("react");
47
+ const reactflow_1 = __importStar(require("reactflow"));
48
+ const types_js_1 = require("./types.js");
49
+ /**
50
+ * Calculate hierarchical layout (top-to-bottom)
51
+ */
52
+ function calculateHierarchicalLayout(skills, edges) {
53
+ const nodeWidth = 200;
54
+ const nodeHeight = 80;
55
+ const levelGap = 120;
56
+ const nodeGap = 40;
57
+ // Build adjacency for levels
58
+ const prereqMap = new Map();
59
+ for (const edge of edges) {
60
+ const targetId = String(edge.targetId);
61
+ const sourceId = String(edge.sourceId);
62
+ if (!prereqMap.has(targetId)) {
63
+ prereqMap.set(targetId, new Set());
64
+ }
65
+ prereqMap.get(targetId).add(sourceId);
66
+ }
67
+ // Calculate levels (BFS from roots)
68
+ const levels = new Map();
69
+ const roots = skills.filter((s) => {
70
+ const prereqs = prereqMap.get(String(s.id));
71
+ return !prereqs || prereqs.size === 0;
72
+ });
73
+ // Initialize roots at level 0
74
+ for (const root of roots) {
75
+ levels.set(String(root.id), 0);
76
+ }
77
+ // BFS to assign levels
78
+ const queue = [...roots.map((r) => String(r.id))];
79
+ const visited = new Set(queue);
80
+ while (queue.length > 0) {
81
+ const nodeId = queue.shift();
82
+ const nodeLevel = levels.get(nodeId) || 0;
83
+ // Find dependents
84
+ for (const edge of edges) {
85
+ if (String(edge.sourceId) === nodeId) {
86
+ const targetId = String(edge.targetId);
87
+ const currentLevel = levels.get(targetId);
88
+ const newLevel = nodeLevel + 1;
89
+ if (currentLevel === undefined || newLevel > currentLevel) {
90
+ levels.set(targetId, newLevel);
91
+ }
92
+ if (!visited.has(targetId)) {
93
+ visited.add(targetId);
94
+ queue.push(targetId);
95
+ }
96
+ }
97
+ }
98
+ }
99
+ // Assign level 0 to any remaining nodes
100
+ for (const skill of skills) {
101
+ if (!levels.has(String(skill.id))) {
102
+ levels.set(String(skill.id), 0);
103
+ }
104
+ }
105
+ // Group by level
106
+ const levelGroups = new Map();
107
+ for (const [nodeId, level] of levels) {
108
+ if (!levelGroups.has(level)) {
109
+ levelGroups.set(level, []);
110
+ }
111
+ levelGroups.get(level).push(nodeId);
112
+ }
113
+ // Calculate positions
114
+ const nodes = [];
115
+ const maxNodesInLevel = Math.max(...Array.from(levelGroups.values()).map((g) => g.length));
116
+ const totalWidth = maxNodesInLevel * (nodeWidth + nodeGap);
117
+ for (const [level, nodeIds] of levelGroups) {
118
+ const levelWidth = nodeIds.length * (nodeWidth + nodeGap);
119
+ const startX = (totalWidth - levelWidth) / 2;
120
+ nodeIds.forEach((nodeId, index) => {
121
+ nodes.push({
122
+ id: nodeId,
123
+ x: startX + index * (nodeWidth + nodeGap),
124
+ y: level * (nodeHeight + levelGap),
125
+ });
126
+ });
127
+ }
128
+ return { nodes };
129
+ }
130
+ /**
131
+ * Calculate force-directed layout (simplified)
132
+ */
133
+ function calculateForceLayout(skills, edges) {
134
+ const width = 1000;
135
+ const height = 800;
136
+ // Simple grid layout with some randomness
137
+ const cols = Math.ceil(Math.sqrt(skills.length));
138
+ const cellWidth = width / cols;
139
+ const cellHeight = height / Math.ceil(skills.length / cols);
140
+ const nodes = skills.map((skill, index) => ({
141
+ id: String(skill.id),
142
+ x: (index % cols) * cellWidth + cellWidth / 2 + (Math.random() - 0.5) * 50,
143
+ y: Math.floor(index / cols) * cellHeight +
144
+ cellHeight / 2 +
145
+ (Math.random() - 0.5) * 50,
146
+ }));
147
+ return { nodes };
148
+ }
149
+ /**
150
+ * Calculate circular layout
151
+ */
152
+ function calculateCircularLayout(skills) {
153
+ const centerX = 500;
154
+ const centerY = 400;
155
+ const radius = 300;
156
+ const nodes = skills.map((skill, index) => {
157
+ const angle = (2 * Math.PI * index) / skills.length;
158
+ return {
159
+ id: String(skill.id),
160
+ x: centerX + radius * Math.cos(angle),
161
+ y: centerY + radius * Math.sin(angle),
162
+ };
163
+ });
164
+ return { nodes };
165
+ }
166
+ /**
167
+ * Calculate layout based on algorithm
168
+ */
169
+ function calculateLayout(skills, edges, layout) {
170
+ switch (layout) {
171
+ case 'hierarchical':
172
+ case 'dagre':
173
+ return calculateHierarchicalLayout(skills, edges);
174
+ case 'force':
175
+ return calculateForceLayout(skills, edges);
176
+ case 'circular':
177
+ case 'radial':
178
+ return calculateCircularLayout(skills);
179
+ default:
180
+ return calculateHierarchicalLayout(skills, edges);
181
+ }
182
+ }
183
+ const SkillNodeComponent = ({ data, selected }) => {
184
+ const { skill, type, bloomColor, onClick } = data;
185
+ const [showTooltip, setShowTooltip] = (0, react_1.useState)(false);
186
+ const backgroundColor = (0, react_1.useMemo)(() => {
187
+ switch (type) {
188
+ case 'mastered':
189
+ return types_js_1.STATUS_COLORS.mastered;
190
+ case 'available':
191
+ return types_js_1.STATUS_COLORS.available;
192
+ case 'locked':
193
+ return types_js_1.STATUS_COLORS.locked;
194
+ case 'target':
195
+ return '#ec4899'; // pink-500
196
+ default:
197
+ return bloomColor;
198
+ }
199
+ }, [type, bloomColor]);
200
+ // Difficulty color gradient (green to red)
201
+ const difficultyColor = (0, react_1.useMemo)(() => {
202
+ const d = skill.difficulty;
203
+ if (d < 0.3)
204
+ return '#22c55e'; // green
205
+ if (d < 0.5)
206
+ return '#84cc16'; // lime
207
+ if (d < 0.7)
208
+ return '#eab308'; // yellow
209
+ if (d < 0.85)
210
+ return '#f97316'; // orange
211
+ return '#ef4444'; // red
212
+ }, [skill.difficulty]);
213
+ const style = {
214
+ padding: '12px 16px',
215
+ borderRadius: '8px',
216
+ backgroundColor,
217
+ color: type === 'locked' ? '#374151' : '#ffffff',
218
+ border: selected ? '3px solid #1f2937' : skill.isThresholdConcept ? '2px solid #fbbf24' : '2px solid transparent',
219
+ boxShadow: selected
220
+ ? '0 4px 6px -1px rgba(0, 0, 0, 0.1)'
221
+ : skill.isThresholdConcept
222
+ ? '0 0 12px rgba(251, 191, 36, 0.5)'
223
+ : '0 1px 3px rgba(0, 0, 0, 0.1)',
224
+ cursor: 'pointer',
225
+ minWidth: '180px',
226
+ maxWidth: '220px',
227
+ transition: 'all 0.2s ease',
228
+ position: 'relative',
229
+ };
230
+ const tooltipStyle = {
231
+ position: 'absolute',
232
+ top: '100%',
233
+ left: '50%',
234
+ transform: 'translateX(-50%)',
235
+ marginTop: '8px',
236
+ backgroundColor: '#1f2937',
237
+ color: '#ffffff',
238
+ padding: '12px',
239
+ borderRadius: '8px',
240
+ fontSize: '12px',
241
+ minWidth: '250px',
242
+ maxWidth: '320px',
243
+ zIndex: 1000,
244
+ boxShadow: '0 10px 25px rgba(0, 0, 0, 0.3)',
245
+ pointerEvents: 'none',
246
+ };
247
+ return ((0, jsx_runtime_1.jsxs)("div", { style: style, onClick: onClick, onMouseEnter: () => setShowTooltip(true), onMouseLeave: () => setShowTooltip(false), children: [(0, jsx_runtime_1.jsx)(reactflow_1.Handle, { type: "target", position: reactflow_1.Position.Top, style: { background: '#6b7280' } }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '4px' }, children: [skill.isThresholdConcept && ((0, jsx_runtime_1.jsx)("span", { title: "Threshold Concept - Gateway to new understanding", style: { fontSize: '14px' }, children: "\uD83D\uDD11" })), (0, jsx_runtime_1.jsx)("div", { style: { fontWeight: 600, fontSize: '14px', flex: 1 }, children: skill.name })] }), (0, jsx_runtime_1.jsxs)("div", { style: {
248
+ fontSize: '11px',
249
+ opacity: 0.9,
250
+ display: 'flex',
251
+ gap: '6px',
252
+ alignItems: 'center',
253
+ flexWrap: 'wrap',
254
+ }, children: [(0, jsx_runtime_1.jsx)("span", { style: {
255
+ textTransform: 'capitalize',
256
+ backgroundColor: 'rgba(255,255,255,0.2)',
257
+ padding: '2px 6px',
258
+ borderRadius: '4px',
259
+ }, children: skill.bloomLevel }), (0, jsx_runtime_1.jsxs)("span", { children: [skill.estimatedMinutes, "m"] }), skill.domain && ((0, jsx_runtime_1.jsx)("span", { style: { backgroundColor: 'rgba(255,255,255,0.15)', padding: '2px 6px', borderRadius: '4px' }, children: skill.domain }))] }), (0, jsx_runtime_1.jsxs)("div", { style: { marginTop: '8px' }, children: [(0, jsx_runtime_1.jsxs)("div", { style: {
260
+ display: 'flex',
261
+ justifyContent: 'space-between',
262
+ fontSize: '10px',
263
+ opacity: 0.8,
264
+ marginBottom: '2px'
265
+ }, children: [(0, jsx_runtime_1.jsx)("span", { children: "Difficulty" }), (0, jsx_runtime_1.jsxs)("span", { children: [Math.round(skill.difficulty * 100), "%"] })] }), (0, jsx_runtime_1.jsx)("div", { style: {
266
+ height: '4px',
267
+ backgroundColor: 'rgba(255,255,255,0.2)',
268
+ borderRadius: '2px',
269
+ overflow: 'hidden'
270
+ }, children: (0, jsx_runtime_1.jsx)("div", { style: {
271
+ width: `${skill.difficulty * 100}%`,
272
+ height: '100%',
273
+ backgroundColor: difficultyColor,
274
+ borderRadius: '2px',
275
+ transition: 'width 0.3s ease'
276
+ } }) })] }), showTooltip && ((0, jsx_runtime_1.jsxs)("div", { style: tooltipStyle, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontWeight: 600, marginBottom: '8px', fontSize: '13px' }, children: skill.name }), (0, jsx_runtime_1.jsx)("div", { style: { marginBottom: '8px', lineHeight: 1.4, opacity: 0.9 }, children: skill.description }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', fontSize: '11px' }, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Bloom:" }), " ", skill.bloomLevel] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Time:" }), " ", skill.estimatedMinutes, " min"] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Difficulty:" }), " ", Math.round(skill.difficulty * 100), "%"] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Mastery Req:" }), " ", Math.round(skill.masteryThreshold * 100), "%"] })] }), !!(skill.metadata?.cognitiveLoad || skill.metadata?.knowledgeType) && ((0, jsx_runtime_1.jsxs)("div", { style: {
277
+ marginTop: '8px',
278
+ padding: '8px',
279
+ backgroundColor: 'rgba(99, 102, 241, 0.15)',
280
+ borderRadius: '4px',
281
+ border: '1px solid rgba(99, 102, 241, 0.3)',
282
+ fontSize: '11px'
283
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontWeight: 600, marginBottom: '6px', color: '#a5b4fc' }, children: "Cognitive Science" }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px' }, children: [!!skill.metadata?.cognitiveLoad && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Load:" }), ' ', (0, jsx_runtime_1.jsx)("span", { style: {
284
+ backgroundColor: skill.metadata.cognitiveLoad === 'high' ? 'rgba(239, 68, 68, 0.3)' :
285
+ skill.metadata.cognitiveLoad === 'medium' ? 'rgba(234, 179, 8, 0.3)' : 'rgba(34, 197, 94, 0.3)',
286
+ padding: '1px 4px',
287
+ borderRadius: '3px',
288
+ textTransform: 'capitalize'
289
+ }, children: String(skill.metadata.cognitiveLoad) })] })), !!skill.metadata?.knowledgeType && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Type:" }), " ", String(skill.metadata.knowledgeType)] }))] })] })), !!(skill.metadata?.commonMisconceptions && Array.isArray(skill.metadata.commonMisconceptions) && skill.metadata.commonMisconceptions.length > 0) && ((0, jsx_runtime_1.jsxs)("div", { style: { marginTop: '8px', fontSize: '11px' }, children: [(0, jsx_runtime_1.jsx)("strong", { style: { color: '#fca5a5' }, children: "Common Misconceptions:" }), (0, jsx_runtime_1.jsx)("ul", { style: { margin: '4px 0 0 16px', padding: 0, opacity: 0.9 }, children: skill.metadata.commonMisconceptions.slice(0, 2).map((m, i) => ((0, jsx_runtime_1.jsx)("li", { style: { marginBottom: '2px' }, children: m }, i))) })] })), !!(skill.metadata?.retrievalCues && Array.isArray(skill.metadata.retrievalCues) && skill.metadata.retrievalCues.length > 0) && ((0, jsx_runtime_1.jsxs)("div", { style: { marginTop: '8px', fontSize: '11px' }, children: [(0, jsx_runtime_1.jsx)("strong", { style: { color: '#86efac' }, children: "Retrieval Cues:" }), (0, jsx_runtime_1.jsx)("ul", { style: { margin: '4px 0 0 16px', padding: 0, opacity: 0.9, fontStyle: 'italic' }, children: skill.metadata.retrievalCues.slice(0, 2).map((c, i) => ((0, jsx_runtime_1.jsx)("li", { style: { marginBottom: '2px' }, children: c }, i))) })] })), skill.tags.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { style: { marginTop: '8px' }, children: [(0, jsx_runtime_1.jsx)("strong", { children: "Tags:" }), ' ', skill.tags.map((tag, i) => ((0, jsx_runtime_1.jsx)("span", { style: {
290
+ backgroundColor: 'rgba(255,255,255,0.15)',
291
+ padding: '2px 6px',
292
+ borderRadius: '4px',
293
+ marginRight: '4px',
294
+ marginTop: '2px',
295
+ display: 'inline-block'
296
+ }, children: tag }, i)))] })), skill.isThresholdConcept && ((0, jsx_runtime_1.jsxs)("div", { style: {
297
+ marginTop: '8px',
298
+ padding: '8px',
299
+ backgroundColor: 'rgba(251, 191, 36, 0.2)',
300
+ borderRadius: '4px',
301
+ border: '1px solid rgba(251, 191, 36, 0.5)'
302
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontWeight: 600, marginBottom: '4px' }, children: "\uD83D\uDD11 Threshold Concept" }), (0, jsx_runtime_1.jsx)("div", { style: { opacity: 0.9, fontSize: '11px' }, children: "Gateway concept that transforms understanding and unlocks new domains." }), !!skill.metadata?.thresholdDetails && ((0, jsx_runtime_1.jsx)("div", { style: { marginTop: '6px', fontSize: '10px', opacity: 0.85 }, children: !!skill.metadata.thresholdDetails?.unlocksDomains && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Unlocks:" }), " ", (skill.metadata.thresholdDetails.unlocksDomains).join(', ')] })) }))] }))] })), (0, jsx_runtime_1.jsx)(reactflow_1.Handle, { type: "source", position: reactflow_1.Position.Bottom, style: { background: '#6b7280' } })] }));
303
+ };
304
+ const PrerequisiteEdgeComponent = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data, style = {}, }) => {
305
+ const [showTooltip, setShowTooltip] = (0, react_1.useState)(false);
306
+ const [edgePath, labelX, labelY] = (0, reactflow_1.getBezierPath)({
307
+ sourceX,
308
+ sourceY,
309
+ sourcePosition,
310
+ targetX,
311
+ targetY,
312
+ targetPosition,
313
+ });
314
+ const strokeWidth = (0, react_1.useMemo)(() => {
315
+ const strength = data?.strength ?? 0.5;
316
+ return 1 + strength * 2;
317
+ }, [data?.strength]);
318
+ const strokeColor = (0, react_1.useMemo)(() => {
319
+ switch (data?.type) {
320
+ case 'required':
321
+ return '#374151'; // darker gray for required
322
+ case 'recommended':
323
+ return '#6b7280';
324
+ case 'optional':
325
+ return '#9ca3af';
326
+ default:
327
+ return '#9ca3af';
328
+ }
329
+ }, [data?.type]);
330
+ const edgeTypeLabel = (0, react_1.useMemo)(() => {
331
+ switch (data?.edgeType) {
332
+ case 'hard':
333
+ return 'Required';
334
+ case 'soft':
335
+ return 'Important';
336
+ case 'recommended':
337
+ return 'Helpful';
338
+ default:
339
+ return '';
340
+ }
341
+ }, [data?.edgeType]);
342
+ const edgeTypeColor = (0, react_1.useMemo)(() => {
343
+ switch (data?.edgeType) {
344
+ case 'hard':
345
+ return '#dc2626'; // red
346
+ case 'soft':
347
+ return '#f59e0b'; // amber
348
+ case 'recommended':
349
+ return '#22c55e'; // green
350
+ default:
351
+ return '#6b7280';
352
+ }
353
+ }, [data?.edgeType]);
354
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("path", { id: id, style: {
355
+ ...style,
356
+ strokeWidth,
357
+ stroke: strokeColor,
358
+ }, className: "react-flow__edge-path", d: edgePath, markerEnd: `url(#arrow-${data?.type || 'default'})` }), (0, jsx_runtime_1.jsx)("foreignObject", { x: labelX - 45, y: labelY - 12, width: 90, height: 24, style: { overflow: 'visible', pointerEvents: 'all' }, children: (0, jsx_runtime_1.jsxs)("div", { onMouseEnter: () => setShowTooltip(true), onMouseLeave: () => setShowTooltip(false), style: {
359
+ display: 'flex',
360
+ alignItems: 'center',
361
+ justifyContent: 'center',
362
+ gap: '4px',
363
+ backgroundColor: '#ffffff',
364
+ border: `1px solid ${edgeTypeColor}`,
365
+ borderRadius: '10px',
366
+ padding: '2px 8px',
367
+ fontSize: '10px',
368
+ fontWeight: 500,
369
+ color: '#374151',
370
+ cursor: data?.reasoning ? 'help' : 'default',
371
+ boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
372
+ position: 'relative',
373
+ }, children: [(0, jsx_runtime_1.jsx)("span", { style: {
374
+ width: '6px',
375
+ height: '6px',
376
+ borderRadius: '50%',
377
+ backgroundColor: edgeTypeColor,
378
+ } }), (0, jsx_runtime_1.jsxs)("span", { children: [Math.round((data?.strength ?? 0) * 100), "%"] }), showTooltip && data?.reasoning && ((0, jsx_runtime_1.jsxs)("div", { style: {
379
+ position: 'absolute',
380
+ top: '100%',
381
+ left: '50%',
382
+ transform: 'translateX(-50%)',
383
+ marginTop: '8px',
384
+ backgroundColor: '#1f2937',
385
+ color: '#ffffff',
386
+ padding: '10px 12px',
387
+ borderRadius: '6px',
388
+ fontSize: '11px',
389
+ minWidth: '200px',
390
+ maxWidth: '280px',
391
+ zIndex: 1001,
392
+ boxShadow: '0 10px 25px rgba(0, 0, 0, 0.3)',
393
+ lineHeight: 1.4,
394
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { style: {
395
+ fontWeight: 600,
396
+ marginBottom: '6px',
397
+ display: 'flex',
398
+ alignItems: 'center',
399
+ gap: '6px',
400
+ }, children: [(0, jsx_runtime_1.jsx)("span", { style: {
401
+ backgroundColor: edgeTypeColor,
402
+ color: '#fff',
403
+ padding: '1px 6px',
404
+ borderRadius: '4px',
405
+ fontSize: '10px',
406
+ }, children: edgeTypeLabel }), (0, jsx_runtime_1.jsx)("span", { children: "Prerequisite" })] }), (0, jsx_runtime_1.jsx)("div", { style: { opacity: 0.9 }, children: data.reasoning }), (0, jsx_runtime_1.jsxs)("div", { style: {
407
+ marginTop: '6px',
408
+ paddingTop: '6px',
409
+ borderTop: '1px solid rgba(255,255,255,0.2)',
410
+ fontSize: '10px',
411
+ opacity: 0.7,
412
+ }, children: ["Strength: ", Math.round((data.strength ?? 0) * 100), "%"] })] }))] }) })] }));
413
+ };
414
+ // ─────────────────────────────────────────────────────────────────────────────
415
+ // Main Component
416
+ // ─────────────────────────────────────────────────────────────────────────────
417
+ const nodeTypes = {
418
+ skillNode: SkillNodeComponent,
419
+ };
420
+ const edgeTypes = {
421
+ prereqEdge: PrerequisiteEdgeComponent,
422
+ };
423
+ /**
424
+ * SkillGraph Component
425
+ *
426
+ * Interactive visualization of skills and their prerequisite relationships.
427
+ *
428
+ * @example
429
+ * ```tsx
430
+ * import { SkillGraph } from 'learngraph/components';
431
+ *
432
+ * function CurriculumView() {
433
+ * const { skills, edges } = useLearnGraph();
434
+ *
435
+ * return (
436
+ * <SkillGraph
437
+ * skills={skills}
438
+ * edges={edges}
439
+ * masteredSkillIds={['skill-1', 'skill-2']}
440
+ * layout="hierarchical"
441
+ * onNodeClick={(node) => console.log('Selected:', node.skill.name)}
442
+ * height={600}
443
+ * />
444
+ * );
445
+ * }
446
+ * ```
447
+ */
448
+ const SkillGraph = ({ skills, edges, masteredSkillIds = [], targetSkillId, layout = 'hierarchical', onNodeClick, onEdgeClick, interactive = true, showMinimap = true, showControls = true, bloomColors = {}, width = '100%', height = 500, className, style, }) => {
449
+ const masteredSet = (0, react_1.useMemo)(() => new Set(masteredSkillIds), [masteredSkillIds]);
450
+ // Build prerequisite map for availability check
451
+ const prereqMap = (0, react_1.useMemo)(() => {
452
+ const map = new Map();
453
+ for (const edge of edges) {
454
+ const targetId = String(edge.targetId);
455
+ const sourceId = String(edge.sourceId);
456
+ if (!map.has(targetId)) {
457
+ map.set(targetId, new Set());
458
+ }
459
+ map.get(targetId).add(sourceId);
460
+ }
461
+ return map;
462
+ }, [edges]);
463
+ // Check if skill is available
464
+ const isAvailable = (0, react_1.useCallback)((skillId) => {
465
+ if (masteredSet.has(skillId))
466
+ return false;
467
+ const prereqs = prereqMap.get(skillId);
468
+ if (!prereqs || prereqs.size === 0)
469
+ return true;
470
+ return Array.from(prereqs).every((id) => masteredSet.has(id));
471
+ }, [prereqMap, masteredSet]);
472
+ // Get bloom color
473
+ const getBloomColor = (0, react_1.useCallback)((level) => {
474
+ return bloomColors[level] ?? types_js_1.DEFAULT_BLOOM_COLORS[level];
475
+ }, [bloomColors]);
476
+ // Calculate layout
477
+ const layoutResult = (0, react_1.useMemo)(() => calculateLayout(skills, edges, layout), [skills, edges, layout]);
478
+ // Create position map
479
+ const positionMap = (0, react_1.useMemo)(() => {
480
+ const map = new Map();
481
+ for (const node of layoutResult.nodes) {
482
+ map.set(node.id, { x: node.x, y: node.y });
483
+ }
484
+ return map;
485
+ }, [layoutResult]);
486
+ // Convert to React Flow nodes
487
+ const initialNodes = (0, react_1.useMemo)(() => {
488
+ return skills.map((skill) => {
489
+ const skillId = String(skill.id);
490
+ const position = positionMap.get(skillId) || { x: 0, y: 0 };
491
+ let nodeType = 'skill';
492
+ if (skillId === targetSkillId) {
493
+ nodeType = 'target';
494
+ }
495
+ else if (masteredSet.has(skillId)) {
496
+ nodeType = 'mastered';
497
+ }
498
+ else if (isAvailable(skillId)) {
499
+ nodeType = 'available';
500
+ }
501
+ else {
502
+ nodeType = 'locked';
503
+ }
504
+ return {
505
+ id: skillId,
506
+ type: 'skillNode',
507
+ position,
508
+ data: {
509
+ skill,
510
+ type: nodeType,
511
+ bloomColor: getBloomColor(skill.bloomLevel),
512
+ onClick: onNodeClick
513
+ ? () => onNodeClick({
514
+ id: skillId,
515
+ label: skill.name,
516
+ skill,
517
+ position,
518
+ type: nodeType,
519
+ })
520
+ : undefined,
521
+ },
522
+ };
523
+ });
524
+ }, [
525
+ skills,
526
+ positionMap,
527
+ targetSkillId,
528
+ masteredSet,
529
+ isAvailable,
530
+ getBloomColor,
531
+ onNodeClick,
532
+ ]);
533
+ // Convert to React Flow edges
534
+ const initialEdges = (0, react_1.useMemo)(() => {
535
+ return edges.map((edge) => {
536
+ // Visual type based on strength (for stroke styling)
537
+ const visualType = edge.strength > 0.7
538
+ ? 'required'
539
+ : edge.strength > 0.4
540
+ ? 'recommended'
541
+ : 'optional';
542
+ return {
543
+ id: String(edge.id),
544
+ source: String(edge.sourceId),
545
+ target: String(edge.targetId),
546
+ type: 'prereqEdge',
547
+ animated: targetSkillId
548
+ ? String(edge.targetId) === targetSkillId
549
+ : false,
550
+ data: {
551
+ strength: edge.strength,
552
+ type: visualType,
553
+ edgeType: edge.type, // actual edge type: hard/soft/recommended
554
+ reasoning: edge.reasoning, // pass reasoning for tooltip
555
+ },
556
+ markerEnd: {
557
+ type: reactflow_1.MarkerType.ArrowClosed,
558
+ width: 15,
559
+ height: 15,
560
+ color: '#6b7280',
561
+ },
562
+ };
563
+ });
564
+ }, [edges, targetSkillId]);
565
+ const [nodes, setNodes, onNodesChange] = (0, reactflow_1.useNodesState)(initialNodes);
566
+ const [flowEdges, setEdges, onEdgesChange] = (0, reactflow_1.useEdgesState)(initialEdges);
567
+ const reactFlowInstance = (0, react_1.useRef)(null);
568
+ // Track skill IDs for key generation to force remount on data change
569
+ const skillsKey = (0, react_1.useMemo)(() => {
570
+ return skills.map(s => String(s.id)).sort().join(',');
571
+ }, [skills]);
572
+ // Update when props change - use functional update to avoid stale closure
573
+ (0, react_1.useEffect)(() => {
574
+ console.log('[SkillGraph] Syncing nodes:', initialNodes.length, 'edges:', initialEdges.length);
575
+ setNodes(initialNodes);
576
+ setEdges(initialEdges);
577
+ }, [skillsKey]); // Only re-sync when actual skill data changes
578
+ // Fit view when nodes are set
579
+ (0, react_1.useEffect)(() => {
580
+ if (reactFlowInstance.current && nodes.length > 0) {
581
+ // Small delay to ensure nodes are rendered
582
+ const timer = setTimeout(() => {
583
+ console.log('[SkillGraph] Fitting view for', nodes.length, 'nodes');
584
+ reactFlowInstance.current?.fitView({ padding: 0.2, duration: 200 });
585
+ }, 100);
586
+ return () => clearTimeout(timer);
587
+ }
588
+ return undefined;
589
+ }, [nodes.length]);
590
+ const onInit = (0, react_1.useCallback)((instance) => {
591
+ reactFlowInstance.current = instance;
592
+ // Initial fit view
593
+ setTimeout(() => {
594
+ instance.fitView({ padding: 0.2, duration: 200 });
595
+ }, 100);
596
+ }, []);
597
+ const containerStyle = {
598
+ width,
599
+ height,
600
+ ...style,
601
+ };
602
+ // Show empty state if no skills
603
+ if (skills.length === 0) {
604
+ return ((0, jsx_runtime_1.jsxs)("div", { className: className, style: {
605
+ ...containerStyle,
606
+ display: 'flex',
607
+ flexDirection: 'column',
608
+ alignItems: 'center',
609
+ justifyContent: 'center',
610
+ backgroundColor: '#f9fafb',
611
+ borderRadius: '8px',
612
+ border: '2px dashed #d1d5db',
613
+ color: '#6b7280',
614
+ }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: '48px', marginBottom: '16px' }, children: "\uD83D\uDCCA" }), (0, jsx_runtime_1.jsx)("div", { style: { fontSize: '18px', fontWeight: 600, marginBottom: '8px' }, children: "No Skills to Display" }), (0, jsx_runtime_1.jsx)("div", { style: { fontSize: '14px', textAlign: 'center', maxWidth: '300px' }, children: "Upload a syllabus to extract skills and visualize your learning graph." })] }));
615
+ }
616
+ // Debug logging
617
+ console.log('[SkillGraph] Rendering with', skills.length, 'skills,', nodes.length, 'nodes in state');
618
+ return ((0, jsx_runtime_1.jsx)("div", { className: className, style: { ...containerStyle, position: 'relative' }, children: (0, jsx_runtime_1.jsxs)(reactflow_1.default, { nodes: nodes, edges: flowEdges, onNodesChange: interactive ? onNodesChange : undefined, onEdgesChange: interactive ? onEdgesChange : undefined, onInit: onInit, nodeTypes: nodeTypes, edgeTypes: edgeTypes, fitView: true, fitViewOptions: { padding: 0.2 }, attributionPosition: "bottom-left", nodesDraggable: interactive, nodesConnectable: false, elementsSelectable: interactive, panOnDrag: interactive, panOnScroll: false, zoomOnScroll: interactive, zoomOnPinch: interactive, zoomOnDoubleClick: interactive, minZoom: 0.1, maxZoom: 4, preventScrolling: true, children: [(0, jsx_runtime_1.jsx)(reactflow_1.Background, { color: "#e5e7eb", gap: 16 }), showControls && (0, jsx_runtime_1.jsx)(reactflow_1.Controls, { showZoom: true, showFitView: true, showInteractive: false }), showMinimap && ((0, jsx_runtime_1.jsx)(reactflow_1.MiniMap, { nodeColor: (node) => {
619
+ const data = node.data;
620
+ switch (data?.type) {
621
+ case 'mastered':
622
+ return types_js_1.STATUS_COLORS.mastered;
623
+ case 'available':
624
+ return types_js_1.STATUS_COLORS.available;
625
+ case 'locked':
626
+ return types_js_1.STATUS_COLORS.locked;
627
+ case 'target':
628
+ return '#ec4899';
629
+ default:
630
+ return data?.bloomColor || '#94a3b8';
631
+ }
632
+ }, maskColor: "rgba(0, 0, 0, 0.1)" }))] }, skillsKey) }));
633
+ };
634
+ exports.SkillGraph = SkillGraph;
635
+ exports.default = exports.SkillGraph;
636
+ //# sourceMappingURL=SkillGraph.js.map