@clypra/ui 1.0.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.
@@ -0,0 +1,16 @@
1
+ {
2
+ "branches": ["main"],
3
+ "plugins": [
4
+ "@semantic-release/commit-analyzer",
5
+ "@semantic-release/release-notes-generator",
6
+ "@semantic-release/npm",
7
+ [
8
+ "@semantic-release/git",
9
+ {
10
+ "assets": ["package.json"],
11
+ "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
12
+ }
13
+ ],
14
+ "@semantic-release/github"
15
+ ]
16
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@clypra/ui",
3
+ "version": "1.0.0",
4
+ "description": "Shared UI components for all Clypra Studio Labs",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js",
10
+ "./components/*": "./dist/components/*/index.js"
11
+ },
12
+ "scripts": {
13
+ "build": "tsup",
14
+ "dev": "tsup --watch",
15
+ "lint": "tsc --noEmit",
16
+ "clean": "rm -rf dist"
17
+ },
18
+ "dependencies": {
19
+ "@clypra/runtime": "^1.0.0",
20
+ "react": "^18.3.1",
21
+ "react-dom": "^18.3.1"
22
+ },
23
+ "devDependencies": {
24
+ "@types/react": "^18.3.12",
25
+ "@types/react-dom": "^18.3.1",
26
+ "@types/node": "^22.14.0",
27
+ "typescript": "~5.8.2",
28
+ "tsup": "^8.3.5"
29
+ },
30
+ "peerDependencies": {
31
+ "react": "^18.0.0",
32
+ "react-dom": "^18.0.0"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/AIEraDev/clypra-studio.git",
37
+ "directory": "packages/ui"
38
+ },
39
+ "homepage": "https://github.com/AIEraDev/clypra-studio/tree/main/packages/ui#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/AIEraDev/clypra-studio/issues"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "registry": "https://registry.npmjs.org/"
46
+ }
47
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * GraphInspector Component
3
+ *
4
+ * Visualizes the media processing graph structure.
5
+ * Shows nodes, edges, capabilities, and dependencies.
6
+ */
7
+
8
+ import React from "react";
9
+ import type { MediaProcessingGraph, GraphNode } from "@clypra/runtime/graph";
10
+
11
+ export interface GraphInspectorProps {
12
+ graph: MediaProcessingGraph;
13
+ selectedNodeId?: string;
14
+ onNodeSelect?: (nodeId: string) => void;
15
+ }
16
+
17
+ export const GraphInspector: React.FC<GraphInspectorProps> = ({ graph, selectedNodeId, onNodeSelect }) => {
18
+ return (
19
+ <div className="graph-inspector" style={{ padding: "16px", fontFamily: "monospace" }}>
20
+ <div style={{ marginBottom: "16px", fontWeight: "bold", fontSize: "14px" }}>Graph Inspector</div>
21
+
22
+ <div style={{ marginBottom: "12px" }}>
23
+ <div style={{ fontSize: "12px", color: "#666" }}>Graph ID: {graph.id}</div>
24
+ <div style={{ fontSize: "12px", color: "#666" }}>
25
+ Nodes: {graph.nodes.length} | Edges: {graph.edges.length}
26
+ </div>
27
+ </div>
28
+
29
+ <div style={{ marginBottom: "16px" }}>
30
+ <div style={{ fontSize: "13px", fontWeight: "600", marginBottom: "8px" }}>Nodes</div>
31
+ <div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
32
+ {graph.nodes.map((node) => (
33
+ <NodeItem key={node.id} node={node} selected={node.id === selectedNodeId} onClick={() => onNodeSelect?.(node.id)} />
34
+ ))}
35
+ </div>
36
+ </div>
37
+
38
+ <div>
39
+ <div style={{ fontSize: "13px", fontWeight: "600", marginBottom: "8px" }}>Edges</div>
40
+ <div style={{ display: "flex", flexDirection: "column", gap: "2px" }}>
41
+ {graph.edges.map((edge, idx) => (
42
+ <div
43
+ key={idx}
44
+ style={{
45
+ fontSize: "11px",
46
+ color: "#555",
47
+ padding: "4px 8px",
48
+ backgroundColor: "#f8f8f8",
49
+ borderRadius: "4px",
50
+ }}
51
+ >
52
+ {edge.fromNodeId}.{edge.fromPinId} → {edge.toNodeId}.{edge.toPinId}
53
+ </div>
54
+ ))}
55
+ </div>
56
+ </div>
57
+ </div>
58
+ );
59
+ };
60
+
61
+ interface NodeItemProps {
62
+ node: GraphNode;
63
+ selected: boolean;
64
+ onClick: () => void;
65
+ }
66
+
67
+ const NodeItem: React.FC<NodeItemProps> = ({ node, selected, onClick }) => {
68
+ return (
69
+ <div
70
+ onClick={onClick}
71
+ style={{
72
+ padding: "8px",
73
+ backgroundColor: selected ? "#e3f2fd" : "#fafafa",
74
+ border: selected ? "1px solid #2196f3" : "1px solid #e0e0e0",
75
+ borderRadius: "4px",
76
+ cursor: "pointer",
77
+ fontSize: "12px",
78
+ }}
79
+ >
80
+ <div style={{ fontWeight: "600", marginBottom: "4px" }}>
81
+ {node.id} <span style={{ color: "#666", fontWeight: "normal" }}>({node.type})</span>
82
+ </div>
83
+ <div style={{ fontSize: "11px", color: "#777" }}>
84
+ Inputs: {Object.keys(node.inputs).length} | Outputs: {Object.keys(node.outputs).length}
85
+ </div>
86
+ {node.capabilities.temporal && <div style={{ fontSize: "10px", color: "#f57c00", marginTop: "4px" }}>⏱ Temporal</div>}
87
+ {node.capabilities.stateful && <div style={{ fontSize: "10px", color: "#7b1fa2", marginTop: "2px" }}>💾 Stateful</div>}
88
+ {node.requirements.multipass && <div style={{ fontSize: "10px", color: "#d32f2f", marginTop: "2px" }}>🔄 Multi-pass</div>}
89
+ </div>
90
+ );
91
+ };
@@ -0,0 +1,2 @@
1
+ export { GraphInspector } from "./GraphInspector";
2
+ export type { GraphInspectorProps } from "./GraphInspector";
@@ -0,0 +1,128 @@
1
+ /**
2
+ * PassInspector Component
3
+ *
4
+ * Shows render passes from the frame graph.
5
+ * Displays execution order, shaders, uniforms, and textures.
6
+ */
7
+
8
+ import React from "react";
9
+ import type { FrameGraph, RenderPass } from "@clypra/runtime/planner";
10
+
11
+ export interface PassInspectorProps {
12
+ frameGraph: FrameGraph;
13
+ selectedPassId?: string;
14
+ onPassSelect?: (passId: string) => void;
15
+ }
16
+
17
+ export const PassInspector: React.FC<PassInspectorProps> = ({ frameGraph, selectedPassId, onPassSelect }) => {
18
+ return (
19
+ <div className="pass-inspector" style={{ padding: "16px", fontFamily: "monospace" }}>
20
+ <div style={{ marginBottom: "16px", fontWeight: "bold", fontSize: "14px" }}>Pass Inspector</div>
21
+
22
+ <div style={{ marginBottom: "12px" }}>
23
+ <div style={{ fontSize: "12px", color: "#666" }}>
24
+ Frame: {frameGraph.frameNumber} | Time: {frameGraph.timelineTimeMs.toFixed(2)}ms
25
+ </div>
26
+ <div style={{ fontSize: "12px", color: "#666" }}>Total Passes: {frameGraph.passes.length}</div>
27
+ </div>
28
+
29
+ <div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
30
+ {frameGraph.passes.map((pass, index) => (
31
+ <PassCard key={pass.id} pass={pass} index={index} selected={pass.id === selectedPassId} onClick={() => onPassSelect?.(pass.id)} />
32
+ ))}
33
+ </div>
34
+ </div>
35
+ );
36
+ };
37
+
38
+ interface PassCardProps {
39
+ pass: RenderPass;
40
+ index: number;
41
+ selected: boolean;
42
+ onClick: () => void;
43
+ }
44
+
45
+ const PassCard: React.FC<PassCardProps> = ({ pass, index, selected, onClick }) => {
46
+ const uniformCount = Object.keys(pass.uniforms).length;
47
+ const inputCount = pass.inputs.length;
48
+
49
+ return (
50
+ <div
51
+ onClick={onClick}
52
+ style={{
53
+ padding: "12px",
54
+ backgroundColor: selected ? "#e8f5e9" : "#fafafa",
55
+ border: selected ? "2px solid #4caf50" : "1px solid #e0e0e0",
56
+ borderRadius: "6px",
57
+ cursor: "pointer",
58
+ fontSize: "12px",
59
+ }}
60
+ >
61
+ {/* Header */}
62
+ <div style={{ display: "flex", alignItems: "center", marginBottom: "8px" }}>
63
+ <div
64
+ style={{
65
+ width: "24px",
66
+ height: "24px",
67
+ borderRadius: "50%",
68
+ backgroundColor: "#2196f3",
69
+ color: "white",
70
+ display: "flex",
71
+ alignItems: "center",
72
+ justifyContent: "center",
73
+ fontSize: "11px",
74
+ fontWeight: "bold",
75
+ marginRight: "8px",
76
+ }}
77
+ >
78
+ {index + 1}
79
+ </div>
80
+ <div style={{ flex: 1 }}>
81
+ <div style={{ fontWeight: "600" }}>{pass.name || pass.id}</div>
82
+ <div style={{ fontSize: "10px", color: "#666" }}>Shader: {pass.shaderId}</div>
83
+ </div>
84
+ </div>
85
+
86
+ {/* Resources */}
87
+ <div style={{ marginBottom: "8px", paddingLeft: "32px" }}>
88
+ <div style={{ fontSize: "11px", color: "#555" }}>
89
+ <span style={{ fontWeight: "500" }}>Inputs:</span> {inputCount > 0 ? pass.inputs.join(", ") : "none"}
90
+ </div>
91
+ <div style={{ fontSize: "11px", color: "#555" }}>
92
+ <span style={{ fontWeight: "500" }}>Output:</span> {pass.output}
93
+ </div>
94
+ </div>
95
+
96
+ {/* Uniforms */}
97
+ {uniformCount > 0 && (
98
+ <div style={{ paddingLeft: "32px", fontSize: "10px", color: "#777" }}>
99
+ <div style={{ fontWeight: "500", marginBottom: "4px" }}>Uniforms ({uniformCount}):</div>
100
+ <div style={{ display: "flex", flexWrap: "wrap", gap: "4px" }}>
101
+ {Object.entries(pass.uniforms)
102
+ .slice(0, 5)
103
+ .map(([key, value]) => (
104
+ <div
105
+ key={key}
106
+ style={{
107
+ padding: "2px 6px",
108
+ backgroundColor: "#fff",
109
+ border: "1px solid #ddd",
110
+ borderRadius: "3px",
111
+ }}
112
+ >
113
+ {key}: {JSON.stringify(value)}
114
+ </div>
115
+ ))}
116
+ {uniformCount > 5 && <div style={{ padding: "2px 6px", color: "#999" }}>+{uniformCount - 5} more</div>}
117
+ </div>
118
+ </div>
119
+ )}
120
+
121
+ {/* Flags */}
122
+ <div style={{ paddingLeft: "32px", marginTop: "8px", fontSize: "10px" }}>
123
+ {pass.clearBeforeRender && <span style={{ padding: "2px 6px", backgroundColor: "#fff3e0", color: "#e65100", borderRadius: "3px", marginRight: "4px" }}>Clear</span>}
124
+ {pass.blendMode && <span style={{ padding: "2px 6px", backgroundColor: "#f3e5f5", color: "#6a1b9a", borderRadius: "3px" }}>Blend: {pass.blendMode}</span>}
125
+ </div>
126
+ </div>
127
+ );
128
+ };
@@ -0,0 +1,2 @@
1
+ export { PassInspector } from "./PassInspector";
2
+ export type { PassInspectorProps } from "./PassInspector";
@@ -0,0 +1,129 @@
1
+ /**
2
+ * PerformanceMonitor Component
3
+ *
4
+ * Shows real-time performance metrics: GPU/CPU time, FPS, pass breakdown.
5
+ */
6
+
7
+ import React from "react";
8
+
9
+ export interface PerformanceMetrics {
10
+ gpuTime: number; // ms
11
+ cpuTime: number; // ms
12
+ passCount: number;
13
+ fps: number;
14
+ memoryUsage: number; // bytes
15
+ passTimes?: Array<{ passId: string; time: number }>;
16
+ }
17
+
18
+ export interface PerformanceMonitorProps {
19
+ metrics: PerformanceMetrics;
20
+ targetFps?: number;
21
+ }
22
+
23
+ export const PerformanceMonitor: React.FC<PerformanceMonitorProps> = ({ metrics, targetFps = 60 }) => {
24
+ const targetFrameTime = 1000 / targetFps;
25
+ const totalTime = metrics.gpuTime + metrics.cpuTime;
26
+ const isOverBudget = totalTime > targetFrameTime;
27
+
28
+ return (
29
+ <div className="performance-monitor" style={{ padding: "16px", fontFamily: "monospace" }}>
30
+ <div style={{ marginBottom: "16px", fontWeight: "bold", fontSize: "14px" }}>Performance Monitor</div>
31
+
32
+ {/* FPS Display */}
33
+ <div
34
+ style={{
35
+ marginBottom: "16px",
36
+ padding: "16px",
37
+ backgroundColor: isOverBudget ? "#ffebee" : "#e8f5e9",
38
+ borderRadius: "8px",
39
+ textAlign: "center",
40
+ }}
41
+ >
42
+ <div style={{ fontSize: "32px", fontWeight: "bold", color: isOverBudget ? "#c62828" : "#2e7d32" }}>{metrics.fps.toFixed(1)} FPS</div>
43
+ <div style={{ fontSize: "12px", color: "#666", marginTop: "4px" }}>
44
+ Target: {targetFps} FPS ({targetFrameTime.toFixed(2)}ms)
45
+ </div>
46
+ </div>
47
+
48
+ {/* Timing Breakdown */}
49
+ <div style={{ marginBottom: "16px" }}>
50
+ <div style={{ fontSize: "13px", fontWeight: "600", marginBottom: "8px" }}>Frame Time: {totalTime.toFixed(2)}ms</div>
51
+
52
+ <MetricBar label="GPU" value={metrics.gpuTime} max={targetFrameTime} color="#2196f3" />
53
+ <MetricBar label="CPU" value={metrics.cpuTime} max={targetFrameTime} color="#ff9800" />
54
+ </div>
55
+
56
+ {/* Pass Count */}
57
+ <div style={{ marginBottom: "16px", fontSize: "12px" }}>
58
+ <div style={{ marginBottom: "4px" }}>
59
+ <span style={{ fontWeight: "600" }}>Render Passes:</span> {metrics.passCount}
60
+ </div>
61
+ <div>
62
+ <span style={{ fontWeight: "600" }}>Memory Usage:</span> {formatBytes(metrics.memoryUsage)}
63
+ </div>
64
+ </div>
65
+
66
+ {/* Pass Time Breakdown */}
67
+ {metrics.passTimes && metrics.passTimes.length > 0 && (
68
+ <div>
69
+ <div style={{ fontSize: "13px", fontWeight: "600", marginBottom: "8px" }}>Pass Breakdown</div>
70
+ <div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
71
+ {metrics.passTimes.map((pass, index) => (
72
+ <div
73
+ key={pass.passId}
74
+ style={{
75
+ display: "flex",
76
+ alignItems: "center",
77
+ fontSize: "11px",
78
+ padding: "4px 8px",
79
+ backgroundColor: "#f5f5f5",
80
+ borderRadius: "4px",
81
+ }}
82
+ >
83
+ <div style={{ width: "20px", fontWeight: "600", color: "#666" }}>{index + 1}</div>
84
+ <div style={{ flex: 1, marginRight: "8px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{pass.passId}</div>
85
+ <div style={{ fontWeight: "600", color: "#1976d2" }}>{pass.time.toFixed(2)}ms</div>
86
+ </div>
87
+ ))}
88
+ </div>
89
+ </div>
90
+ )}
91
+ </div>
92
+ );
93
+ };
94
+
95
+ interface MetricBarProps {
96
+ label: string;
97
+ value: number;
98
+ max: number;
99
+ color: string;
100
+ }
101
+
102
+ const MetricBar: React.FC<MetricBarProps> = ({ label, value, max, color }) => {
103
+ const percentage = Math.min((value / max) * 100, 100);
104
+
105
+ return (
106
+ <div style={{ marginBottom: "12px" }}>
107
+ <div style={{ display: "flex", justifyContent: "space-between", fontSize: "11px", marginBottom: "4px" }}>
108
+ <span style={{ fontWeight: "600" }}>{label}</span>
109
+ <span>{value.toFixed(2)}ms</span>
110
+ </div>
111
+ <div style={{ width: "100%", height: "8px", backgroundColor: "#e0e0e0", borderRadius: "4px", overflow: "hidden" }}>
112
+ <div
113
+ style={{
114
+ width: `${percentage}%`,
115
+ height: "100%",
116
+ backgroundColor: color,
117
+ transition: "width 0.3s ease",
118
+ }}
119
+ />
120
+ </div>
121
+ </div>
122
+ );
123
+ };
124
+
125
+ function formatBytes(bytes: number): string {
126
+ if (bytes < 1024) return `${bytes} B`;
127
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
128
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
129
+ }
@@ -0,0 +1,2 @@
1
+ export { PerformanceMonitor } from "./PerformanceMonitor";
2
+ export type { PerformanceMonitorProps, PerformanceMetrics } from "./PerformanceMonitor";