1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use super::registry::NodeRegistry;
7use super::types::NodeSchema;
8use jellyflow_core::core::{
9 CanvasPoint, Edge, EdgeId, EdgeKind, EdgeViewDescriptor, Graph, GraphBuilder, GraphId, NodeId,
10 NodeKindKey, PortDirection, PortId,
11};
12
13mod builtins;
14
15pub use builtins::{
16 builtin_node_kits, erd_table_manifest, mind_map_knowledge_canvas_manifest,
17 shader_blueprint_manifest, workflow_automation_manifest,
18};
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
22#[serde(transparent)]
23pub struct NodeKitKey(pub String);
24
25impl NodeKitKey {
26 pub fn new(key: impl Into<String>) -> Self {
27 Self(key.into())
28 }
29}
30
31impl From<&str> for NodeKitKey {
32 fn from(value: &str) -> Self {
33 Self::new(value)
34 }
35}
36
37impl From<String> for NodeKitKey {
38 fn from(value: String) -> Self {
39 Self::new(value)
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
45#[serde(transparent)]
46pub struct NodeKitAdapterKey(pub String);
47
48impl NodeKitAdapterKey {
49 pub fn new(key: impl Into<String>) -> Self {
50 Self(key.into())
51 }
52}
53
54impl From<&str> for NodeKitAdapterKey {
55 fn from(value: &str) -> Self {
56 Self::new(value)
57 }
58}
59
60impl From<String> for NodeKitAdapterKey {
61 fn from(value: String) -> Self {
62 Self::new(value)
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68pub struct NodeKitLayoutHints {
69 #[serde(default = "NodeKitLayoutHints::default_full_zoom_min")]
71 pub full_zoom_min: f32,
72 #[serde(default = "NodeKitLayoutHints::default_compact_zoom_min")]
74 pub compact_zoom_min: f32,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub field_spacing: Option<f32>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub action_spacing: Option<f32>,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub measurement_note: Option<String>,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
88#[serde(rename_all = "snake_case")]
89pub enum NodeKitContentDensity {
90 Compact,
91 Regular,
92 Full,
93}
94
95impl Default for NodeKitLayoutHints {
96 fn default() -> Self {
97 Self {
98 full_zoom_min: Self::default_full_zoom_min(),
99 compact_zoom_min: Self::default_compact_zoom_min(),
100 field_spacing: None,
101 action_spacing: None,
102 measurement_note: None,
103 }
104 }
105}
106
107impl NodeKitLayoutHints {
108 fn default_full_zoom_min() -> f32 {
109 0.62
110 }
111
112 fn default_compact_zoom_min() -> f32 {
113 0.18
114 }
115
116 pub fn with_full_zoom_min(mut self, zoom_min: f32) -> Self {
117 self.full_zoom_min = zoom_min;
118 self
119 }
120
121 pub fn with_compact_zoom_min(mut self, zoom_min: f32) -> Self {
122 self.compact_zoom_min = zoom_min;
123 self
124 }
125
126 pub fn with_zoom_range(mut self, compact_zoom_min: f32, full_zoom_min: f32) -> Self {
127 self.compact_zoom_min = compact_zoom_min;
128 self.full_zoom_min = full_zoom_min;
129 self
130 }
131
132 pub fn with_field_spacing(mut self, spacing: f32) -> Self {
133 self.field_spacing = Some(spacing);
134 self
135 }
136
137 pub fn with_action_spacing(mut self, spacing: f32) -> Self {
138 self.action_spacing = Some(spacing);
139 self
140 }
141
142 pub fn with_measurement_note(mut self, note: impl Into<String>) -> Self {
143 self.measurement_note = Some(note.into());
144 self
145 }
146
147 pub fn content_density_for_zoom(&self, zoom: f32) -> NodeKitContentDensity {
148 let compact_zoom_min = self.compact_zoom_min.min(self.full_zoom_min);
149 let full_zoom_min = self.full_zoom_min.max(self.compact_zoom_min);
150
151 if zoom >= full_zoom_min {
152 NodeKitContentDensity::Full
153 } else if zoom >= compact_zoom_min {
154 NodeKitContentDensity::Regular
155 } else {
156 NodeKitContentDensity::Compact
157 }
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163pub struct NodeKitFixtureNode {
164 pub alias: String,
165 pub kind: NodeKindKey,
166 pub pos: CanvasPoint,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub data: Option<Value>,
169}
170
171impl NodeKitFixtureNode {
172 pub fn new(alias: impl Into<String>, kind: impl Into<NodeKindKey>, pos: CanvasPoint) -> Self {
173 Self {
174 alias: alias.into(),
175 kind: kind.into(),
176 pos,
177 data: None,
178 }
179 }
180
181 pub fn with_data(mut self, data: Value) -> Self {
182 self.data = Some(data);
183 self
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
189pub struct NodeKitFixtureEdge {
190 pub from: String,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub from_port: Option<String>,
193 pub to: String,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub to_port: Option<String>,
196 pub kind: EdgeKind,
197 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub data: Option<Value>,
199 #[serde(default, skip_serializing_if = "EdgeViewDescriptor::is_default")]
200 pub view: EdgeViewDescriptor,
201}
202
203impl NodeKitFixtureEdge {
204 pub fn new(from: impl Into<String>, to: impl Into<String>, kind: EdgeKind) -> Self {
205 Self {
206 from: from.into(),
207 from_port: None,
208 to: to.into(),
209 to_port: None,
210 kind,
211 data: None,
212 view: EdgeViewDescriptor::default(),
213 }
214 }
215
216 pub fn with_from_port(mut self, from_port: impl Into<String>) -> Self {
217 self.from_port = Some(from_port.into());
218 self
219 }
220
221 pub fn with_to_port(mut self, to_port: impl Into<String>) -> Self {
222 self.to_port = Some(to_port.into());
223 self
224 }
225
226 pub fn with_data(mut self, data: Value) -> Self {
227 self.data = Some(data);
228 self
229 }
230
231 pub fn with_view(mut self, view: EdgeViewDescriptor) -> Self {
232 self.view = view;
233 self
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
239pub struct NodeKitFixture {
240 pub key: String,
241 pub title: String,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub description: Option<String>,
244 #[serde(default, skip_serializing_if = "Vec::is_empty")]
245 pub nodes: Vec<NodeKitFixtureNode>,
246 #[serde(default, skip_serializing_if = "Vec::is_empty")]
247 pub edges: Vec<NodeKitFixtureEdge>,
248 #[serde(default, skip_serializing_if = "Option::is_none")]
249 pub expected_node_count: Option<usize>,
250 #[serde(default, skip_serializing_if = "Option::is_none")]
251 pub expected_edge_count: Option<usize>,
252}
253
254impl NodeKitFixture {
255 pub fn new(key: impl Into<String>, title: impl Into<String>) -> Self {
256 Self {
257 key: key.into(),
258 title: title.into(),
259 description: None,
260 nodes: Vec::new(),
261 edges: Vec::new(),
262 expected_node_count: None,
263 expected_edge_count: None,
264 }
265 }
266
267 pub fn with_description(mut self, description: impl Into<String>) -> Self {
268 self.description = Some(description.into());
269 self
270 }
271
272 pub fn node(mut self, node: NodeKitFixtureNode) -> Self {
273 self.nodes.push(node);
274 self
275 }
276
277 pub fn edge(mut self, edge: NodeKitFixtureEdge) -> Self {
278 self.edges.push(edge);
279 self
280 }
281
282 pub fn expect_counts(mut self, nodes: usize, edges: usize) -> Self {
283 self.expected_node_count = Some(nodes);
284 self.expected_edge_count = Some(edges);
285 self
286 }
287
288 pub fn build_graph(
289 &self,
290 kit_key: &NodeKitKey,
291 registry: &NodeRegistry,
292 ) -> Result<Graph, NodeKitFixtureError> {
293 let mut builder = GraphBuilder::new(GraphId::from_u128(stable_u128(&format!(
294 "kit.fixture.graph::{}::{}",
295 kit_key.0, self.key
296 ))));
297 let mut alias_to_kind = BTreeMap::new();
298 let mut alias_to_ports: BTreeMap<String, BTreeMap<String, PortId>> = BTreeMap::new();
299
300 for node in &self.nodes {
301 let schema =
302 registry
303 .get(&node.kind)
304 .ok_or_else(|| NodeKitFixtureError::MissingSchema {
305 fixture: self.key.clone(),
306 kind: node.kind.clone(),
307 })?;
308 let node_id = NodeId::from_u128(stable_u128(&format!(
309 "kit.fixture.node::{}::{}",
310 self.key, node.alias
311 )));
312 let port_ids = schema
313 .ports
314 .iter()
315 .map(|decl| {
316 PortId::from_u128(stable_u128(&format!(
317 "kit.fixture.port::{}::{}::{}",
318 self.key, node.alias, decl.key.0
319 )))
320 })
321 .collect::<Vec<_>>();
322 let mut inst = schema
323 .instantiate_with_ids(node_id, node.pos, port_ids)
324 .map_err(|error| NodeKitFixtureError::Instantiation {
325 fixture: self.key.clone(),
326 kind: node.kind.clone(),
327 error,
328 })?;
329
330 if let Some(data) = &node.data {
331 inst.node.data = data.clone();
332 }
333
334 alias_to_kind.insert(node.alias.clone(), node.kind.clone());
335 let mut port_map = BTreeMap::new();
336 for ((port_id, port), decl) in inst.ports.into_iter().zip(schema.ports.iter()) {
337 port_map.insert(decl.key.0.clone(), port_id);
338 builder = builder.with_port(port_id, port);
339 }
340 alias_to_ports.insert(node.alias.clone(), port_map);
341 builder = builder.with_node(node_id, inst.node);
342 }
343
344 for edge in &self.edges {
345 let from_kind = alias_to_kind.get(&edge.from).ok_or_else(|| {
346 NodeKitFixtureError::MissingNodeAlias {
347 fixture: self.key.clone(),
348 alias: edge.from.clone(),
349 }
350 })?;
351 let to_kind = alias_to_kind.get(&edge.to).ok_or_else(|| {
352 NodeKitFixtureError::MissingNodeAlias {
353 fixture: self.key.clone(),
354 alias: edge.to.clone(),
355 }
356 })?;
357 let from_schema =
358 registry
359 .get(from_kind)
360 .ok_or_else(|| NodeKitFixtureError::MissingSchema {
361 fixture: self.key.clone(),
362 kind: from_kind.clone(),
363 })?;
364 let to_schema =
365 registry
366 .get(to_kind)
367 .ok_or_else(|| NodeKitFixtureError::MissingSchema {
368 fixture: self.key.clone(),
369 kind: to_kind.clone(),
370 })?;
371 let from_port_key = resolve_fixture_port_key(
372 from_schema,
373 PortDirection::Out,
374 edge.from_port.as_deref(),
375 )?;
376 let to_port_key =
377 resolve_fixture_port_key(to_schema, PortDirection::In, edge.to_port.as_deref())?;
378 let from_port_id = alias_to_ports
379 .get(&edge.from)
380 .and_then(|ports| ports.get(&from_port_key))
381 .copied()
382 .ok_or_else(|| NodeKitFixtureError::MissingPort {
383 fixture: self.key.clone(),
384 alias: edge.from.clone(),
385 port: from_port_key.clone(),
386 })?;
387 let to_port_id = alias_to_ports
388 .get(&edge.to)
389 .and_then(|ports| ports.get(&to_port_key))
390 .copied()
391 .ok_or_else(|| NodeKitFixtureError::MissingPort {
392 fixture: self.key.clone(),
393 alias: edge.to.clone(),
394 port: to_port_key.clone(),
395 })?;
396
397 let edge_id = EdgeId::from_u128(stable_u128(&format!(
398 "kit.fixture.edge::{}::{}::{}:{}->{}:{}::{:?}",
399 kit_key.0, self.key, edge.from, from_port_key, edge.to, to_port_key, edge.kind
400 )));
401 let mut record = Edge::new(edge.kind, from_port_id, to_port_id);
402 if let Some(data) = &edge.data {
403 record.data = data.clone();
404 }
405 record.view = edge.view.clone();
406 builder = builder.with_edge(edge_id, record);
407 }
408
409 Ok(builder.build_unchecked())
410 }
411}
412
413#[derive(Debug, thiserror::Error)]
415pub enum NodeKitFixtureError {
416 #[error("node kit fixture `{fixture}` missing node schema for `{kind:?}`")]
417 MissingSchema { fixture: String, kind: NodeKindKey },
418 #[error("node kit `{kit}` missing fixture `{fixture}`")]
419 MissingFixture { kit: String, fixture: String },
420 #[error("node kit registry missing kit `{kit:?}`")]
421 MissingKit { kit: NodeKitKey },
422 #[error("node kit fixture `{fixture}` missing node alias `{alias}`")]
423 MissingNodeAlias { fixture: String, alias: String },
424 #[error("node kit fixture `{fixture}` missing port `{port}` on node `{alias}`")]
425 MissingPort {
426 fixture: String,
427 alias: String,
428 port: String,
429 },
430 #[error("node kit fixture `{fixture}` failed to instantiate `{kind:?}`: {error}")]
431 Instantiation {
432 fixture: String,
433 kind: NodeKindKey,
434 error: super::NodeInstantiationError,
435 },
436}
437
438#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
440pub struct NodeKitManifest {
441 pub key: NodeKitKey,
442 pub title: String,
443 pub version: u32,
444 #[serde(default, skip_serializing_if = "Vec::is_empty")]
445 pub supported_adapters: Vec<NodeKitAdapterKey>,
446 #[serde(default, skip_serializing_if = "Vec::is_empty")]
447 pub capabilities: Vec<String>,
448 #[serde(default)]
449 pub layout_hints: NodeKitLayoutHints,
450 #[serde(default, skip_serializing_if = "Vec::is_empty")]
451 pub recipes: Vec<NodeSchema>,
452 #[serde(default, skip_serializing_if = "Vec::is_empty")]
453 pub fixtures: Vec<NodeKitFixture>,
454}
455
456impl NodeKitManifest {
457 pub fn new(key: impl Into<NodeKitKey>, title: impl Into<String>) -> Self {
458 Self {
459 key: key.into(),
460 title: title.into(),
461 version: 1,
462 supported_adapters: Vec::new(),
463 capabilities: Vec::new(),
464 layout_hints: NodeKitLayoutHints::default(),
465 recipes: Vec::new(),
466 fixtures: Vec::new(),
467 }
468 }
469
470 pub fn with_version(mut self, version: u32) -> Self {
471 self.version = version;
472 self
473 }
474
475 pub fn with_supported_adapter(mut self, adapter: impl Into<NodeKitAdapterKey>) -> Self {
476 self.supported_adapters.push(adapter.into());
477 self
478 }
479
480 pub fn with_capability(mut self, capability: impl Into<String>) -> Self {
481 self.capabilities.push(capability.into());
482 self
483 }
484
485 pub fn with_layout_hints(mut self, layout_hints: NodeKitLayoutHints) -> Self {
486 self.layout_hints = layout_hints;
487 self
488 }
489
490 pub fn recipe(mut self, recipe: NodeSchema) -> Self {
491 self.recipes.push(recipe);
492 self
493 }
494
495 pub fn recipes(mut self, recipes: impl IntoIterator<Item = NodeSchema>) -> Self {
496 self.recipes.extend(recipes);
497 self
498 }
499
500 pub fn fixture(mut self, fixture: NodeKitFixture) -> Self {
501 self.fixtures.push(fixture);
502 self
503 }
504
505 pub fn fixtures(mut self, fixtures: impl IntoIterator<Item = NodeKitFixture>) -> Self {
506 self.fixtures.extend(fixtures);
507 self
508 }
509
510 pub fn recipe_for_kind(&self, kind: &NodeKindKey) -> Option<&NodeSchema> {
511 self.recipes.iter().find(|recipe| &recipe.kind == kind)
512 }
513
514 pub fn fixture_for_key(&self, key: &str) -> Option<&NodeKitFixture> {
515 self.fixtures.iter().find(|fixture| fixture.key == key)
516 }
517
518 pub fn layout_hints(&self) -> &NodeKitLayoutHints {
519 &self.layout_hints
520 }
521
522 pub fn node_registry(&self) -> NodeRegistry {
523 let mut registry = NodeRegistry::new();
524 for recipe in &self.recipes {
525 registry.register(recipe.clone());
526 }
527 registry
528 }
529
530 pub fn build_fixture_graph(&self, key: &str) -> Result<Graph, NodeKitFixtureError> {
531 let fixture =
532 self.fixture_for_key(key)
533 .ok_or_else(|| NodeKitFixtureError::MissingFixture {
534 kit: self.key.0.clone(),
535 fixture: key.to_owned(),
536 })?;
537 fixture.build_graph(&self.key, &self.node_registry())
538 }
539}
540
541#[derive(Default, Clone)]
543pub struct NodeKitRegistry {
544 kits: BTreeMap<NodeKitKey, NodeKitManifest>,
545}
546
547impl std::fmt::Debug for NodeKitRegistry {
548 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549 f.debug_struct("NodeKitRegistry")
550 .field("kit_count", &self.kits.len())
551 .finish()
552 }
553}
554
555impl NodeKitRegistry {
556 pub fn new() -> Self {
557 Self::default()
558 }
559
560 pub fn builtin() -> Self {
561 builtin_node_kits()
562 }
563
564 pub fn register(&mut self, manifest: NodeKitManifest) -> &mut Self {
565 self.kits.insert(manifest.key.clone(), manifest);
566 self
567 }
568
569 pub fn manifest(&self, key: &NodeKitKey) -> Option<&NodeKitManifest> {
570 self.kits.get(key)
571 }
572
573 pub fn manifest_for_kind(&self, kind: &NodeKindKey) -> Option<&NodeKitManifest> {
574 self.manifests()
575 .find(|manifest| manifest.recipe_for_kind(kind).is_some())
576 }
577
578 pub fn manifests(&self) -> impl Iterator<Item = &NodeKitManifest> {
579 self.kits.values()
580 }
581
582 pub fn node_registry(&self) -> NodeRegistry {
583 let mut registry = NodeRegistry::new();
584 for manifest in self.manifests() {
585 for recipe in &manifest.recipes {
586 registry.register(recipe.clone());
587 }
588 }
589 registry
590 }
591
592 pub fn recipe_for_kind(&self, kind: &NodeKindKey) -> Option<&NodeSchema> {
593 self.manifests()
594 .find_map(|manifest| manifest.recipe_for_kind(kind))
595 }
596
597 pub fn layout_hints_for_kind(&self, kind: &NodeKindKey) -> Option<&NodeKitLayoutHints> {
598 self.manifest_for_kind(kind)
599 .map(NodeKitManifest::layout_hints)
600 }
601
602 pub fn fixture_graph(
603 &self,
604 kit_key: &NodeKitKey,
605 fixture_key: &str,
606 ) -> Result<Graph, NodeKitFixtureError> {
607 let manifest = self
608 .manifest(kit_key)
609 .ok_or_else(|| NodeKitFixtureError::MissingKit {
610 kit: kit_key.clone(),
611 })?;
612 manifest.build_fixture_graph(fixture_key)
613 }
614}
615
616fn resolve_fixture_port_key(
617 schema: &NodeSchema,
618 direction: PortDirection,
619 requested: Option<&str>,
620) -> Result<String, NodeKitFixtureError> {
621 if let Some(requested) = requested {
622 if schema.ports.iter().any(|decl| decl.key.0 == requested) {
623 return Ok(requested.to_owned());
624 }
625 return Err(NodeKitFixtureError::MissingPort {
626 fixture: schema.kind.0.clone(),
627 alias: schema.title.clone(),
628 port: requested.to_owned(),
629 });
630 }
631
632 schema
633 .ports
634 .iter()
635 .find(|decl| decl.dir == direction)
636 .map(|decl| decl.key.0.clone())
637 .ok_or_else(|| NodeKitFixtureError::MissingPort {
638 fixture: schema.kind.0.clone(),
639 alias: schema.title.clone(),
640 port: format!("{direction:?}"),
641 })
642}
643
644fn stable_u128(text: &str) -> u128 {
645 const OFFSET: u128 = 0x6c62_272e_07bb_0142_62b8_2175_6295_c58d;
646 const PRIME: u128 = 0x0000_0000_0100_0000_0000_0000_0000_013b;
647 let mut hash = OFFSET;
648 for byte in text.as_bytes() {
649 hash ^= *byte as u128;
650 hash = hash.wrapping_mul(PRIME);
651 }
652 hash
653}