@coreason-ai/sensory-core 1.3.0 → 1.5.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,166 @@
1
+ // Copyright (c) 2026 CoReason, Inc
2
+ //
3
+ // This software is proprietary and dual-licensed
4
+ // Licensed under the Prosperity Public License 3.0 (the "License")
5
+ // A copy of the license is available at <https://prosperitylicense.com/versions/3.0.0>
6
+ // For details, see the LICENSE file
7
+ // Commercial use beyond a 30-day trial requires a separate license
8
+ //
9
+ // Source Code: <https://github.com/CoReason-AI/coreason-runtime>
10
+ import React from 'react';
11
+ import { GlassBox } from './GlassBox';
12
+ import { TrendingUp, X, User, Scale } from 'lucide-react';
13
+ export const PredictionMarketConsensusHud = ({ data, onTrade, className = '' }) => {
14
+ const [drawerOpen, setDrawerOpen] = React.useState(false);
15
+ const [selectedSpeaker, setSelectedSpeaker] = React.useState(null);
16
+ const proponent = data.hypotheses.find(h => h.id === 'proponent') || {
17
+ agentUrn: 'urn:coreason:agent:unknown-proponent',
18
+ shares: 0,
19
+ probability: 0.5
20
+ };
21
+ const opponent = data.hypotheses.find(h => h.id === 'opponent') || {
22
+ agentUrn: 'urn:coreason:agent:unknown-opponent',
23
+ shares: 0,
24
+ probability: 0.5
25
+ };
26
+ const proponentProb = proponent.probability;
27
+ const opponentProb = opponent.probability;
28
+ // Format short URN name for visual display
29
+ const getShortUrn = (urn) => {
30
+ return urn.split(':').slice(-2).join(':') || urn;
31
+ };
32
+ const filteredArguments = selectedSpeaker
33
+ ? data.arguments.filter(arg => arg.speakerUrn === selectedSpeaker)
34
+ : data.arguments;
35
+ // Build SVG points for opinion convergence chart
36
+ const points = React.useMemo(() => {
37
+ const history = data.historicalProbabilityConvergence || [];
38
+ if (history.length === 0)
39
+ return '';
40
+ const width = 500;
41
+ const height = 100;
42
+ return history
43
+ .map((p, index) => {
44
+ const x = history.length > 1 ? (index / (history.length - 1)) * width : width / 2;
45
+ const y = height - p.proponentProbability * height;
46
+ return `${x},${y}`;
47
+ })
48
+ .join(' ');
49
+ }, [data.historicalProbabilityConvergence]);
50
+ return (React.createElement("div", { className: `flex flex-col lg:flex-row gap-4 h-full w-full select-none ${className}` },
51
+ React.createElement("div", { className: "flex-1 flex flex-col gap-4 min-w-0" },
52
+ React.createElement(GlassBox, { density: "dense", className: "border border-white/10 bg-black/45 backdrop-blur-md p-4" },
53
+ React.createElement("div", { className: "flex items-start gap-3" },
54
+ React.createElement(Scale, { className: "w-5 h-5 text-[var(--cr-accent-orange)] shrink-0 mt-0.5" }),
55
+ React.createElement("div", null,
56
+ React.createElement("div", { className: "text-[10px] font-bold text-gray-500 uppercase tracking-widest" }, "Active Dialectic Hypothesis Debate"),
57
+ React.createElement("h2", { className: "text-sm font-semibold text-white mt-1 font-sans leading-relaxed" }, data.thesis),
58
+ React.createElement("div", { className: "text-[9px] font-mono text-gray-400 mt-1 break-all" },
59
+ "Debate Session: ",
60
+ data.debateUrn,
61
+ " | Liquidity parameter (b): ",
62
+ data.liquidityParameter)))),
63
+ React.createElement(GlassBox, { density: "dense", className: "border border-white/5 bg-black/65 backdrop-blur-md p-6 flex flex-col gap-6" },
64
+ React.createElement("div", { className: "text-[11px] font-bold text-gray-400 uppercase tracking-widest" }, "Tug-of-War Consensus Track"),
65
+ React.createElement("div", { className: "flex items-center justify-between gap-4 py-2" },
66
+ React.createElement("div", { onClick: () => {
67
+ setSelectedSpeaker(proponent.agentUrn);
68
+ setDrawerOpen(true);
69
+ }, className: `p-3 rounded-lg border cursor-pointer transition-all w-[180px] bg-black/40 ${proponentProb > opponentProb
70
+ ? 'border-[var(--cr-accent-cyan)] shadow-[0_0_15px_rgba(6,182,212,0.25)]'
71
+ : 'border-white/5 hover:border-white/20'}` },
72
+ React.createElement("div", { className: "flex items-center gap-2" },
73
+ React.createElement("div", { className: "p-1.5 rounded bg-[var(--cr-accent-cyan)]/10 text-[var(--cr-accent-cyan)]" },
74
+ React.createElement(User, { className: "w-4 h-4" })),
75
+ React.createElement("div", { className: "min-w-0" },
76
+ React.createElement("div", { className: "text-[9px] font-bold text-cyan-400 uppercase tracking-wider" }, "PROPONENT"),
77
+ React.createElement("div", { className: "text-[10px] font-mono text-white truncate" }, getShortUrn(proponent.agentUrn)))),
78
+ React.createElement("div", { className: "grid grid-cols-2 gap-2 mt-3 pt-2 border-t border-white/5" },
79
+ React.createElement("div", null,
80
+ React.createElement("div", { className: "text-[8px] text-gray-500 font-mono" }, "WEIGHT"),
81
+ React.createElement("div", { className: "text-xs font-bold text-white font-mono" },
82
+ (proponentProb * 100).toFixed(1),
83
+ "%")),
84
+ React.createElement("div", null,
85
+ React.createElement("div", { className: "text-[8px] text-gray-500 font-mono" }, "SHARES"),
86
+ React.createElement("div", { className: "text-xs font-bold text-white font-mono" }, proponent.shares.toFixed(1))))),
87
+ React.createElement("div", { className: "flex-1 relative h-6 flex items-center" },
88
+ React.createElement("div", { className: "absolute inset-x-0 h-1 bg-gray-800 rounded-full overflow-hidden" },
89
+ React.createElement("div", { className: "h-full bg-gradient-to-r from-cyan-400 via-purple-500 to-orange-400 transition-all duration-300", style: { width: '100%' } })),
90
+ React.createElement("div", { className: "absolute w-8 h-8 -ml-4 bg-white/10 backdrop-blur-md border-2 border-white rounded-full flex items-center justify-center shadow-[0_0_15px_rgba(255,255,255,0.4)] cursor-pointer transition-all duration-300 hover:scale-110 z-10", style: { left: `${proponentProb * 100}%` }, title: `Consensus Cursor: ${(proponentProb * 100).toFixed(1)}% Proponent` },
91
+ React.createElement("div", { className: "w-2 h-2 rounded-full bg-white animate-ping" }))),
92
+ React.createElement("div", { onClick: () => {
93
+ setSelectedSpeaker(opponent.agentUrn);
94
+ setDrawerOpen(true);
95
+ }, className: `p-3 rounded-lg border cursor-pointer transition-all w-[180px] bg-black/40 ${opponentProb > proponentProb
96
+ ? 'border-[var(--cr-accent-orange)] shadow-[0_0_15px_rgba(251,146,60,0.25)]'
97
+ : 'border-white/5 hover:border-white/20'}` },
98
+ React.createElement("div", { className: "flex items-center gap-2 justify-end text-right" },
99
+ React.createElement("div", { className: "min-w-0" },
100
+ React.createElement("div", { className: "text-[9px] font-bold text-orange-400 uppercase tracking-wider" }, "OPPONENT"),
101
+ React.createElement("div", { className: "text-[10px] font-mono text-white truncate" }, getShortUrn(opponent.agentUrn))),
102
+ React.createElement("div", { className: "p-1.5 rounded bg-[var(--cr-accent-orange)]/10 text-[var(--cr-accent-orange)]" },
103
+ React.createElement(User, { className: "w-4 h-4" }))),
104
+ React.createElement("div", { className: "grid grid-cols-2 gap-2 mt-3 pt-2 border-t border-white/5" },
105
+ React.createElement("div", null,
106
+ React.createElement("div", { className: "text-[8px] text-gray-500 font-mono" }, "WEIGHT"),
107
+ React.createElement("div", { className: "text-xs font-bold text-white font-mono" },
108
+ (opponentProb * 100).toFixed(1),
109
+ "%")),
110
+ React.createElement("div", null,
111
+ React.createElement("div", { className: "text-[8px] text-gray-500 font-mono" }, "SHARES"),
112
+ React.createElement("div", { className: "text-xs font-bold text-white font-mono" }, opponent.shares.toFixed(1)))))),
113
+ React.createElement("div", { className: "flex justify-between items-center pt-2 border-t border-white/5" },
114
+ React.createElement("div", { className: "flex items-center gap-2" },
115
+ React.createElement("button", { onClick: () => onTrade === null || onTrade === void 0 ? void 0 : onTrade('proponent', 10), className: "px-3 py-1.5 bg-cyan-950/45 hover:bg-cyan-950/80 border border-cyan-500/40 text-cyan-400 text-xs font-bold font-mono rounded cursor-pointer transition-all" }, "Bid Proponent (+10 Sh)"),
116
+ React.createElement("button", { onClick: () => onTrade === null || onTrade === void 0 ? void 0 : onTrade('proponent', 50), className: "px-3 py-1.5 bg-cyan-900/60 hover:bg-cyan-900/90 border border-cyan-500/60 text-cyan-200 text-xs font-bold font-mono rounded cursor-pointer transition-all" }, "Bid Proponent (+50 Sh)")),
117
+ React.createElement("div", { className: "text-[9px] font-mono text-gray-500 max-w-[150px] text-center leading-normal" }, "Bidding modifies market consensus probabilities using LMSR."),
118
+ React.createElement("div", { className: "flex items-center gap-2" },
119
+ React.createElement("button", { onClick: () => onTrade === null || onTrade === void 0 ? void 0 : onTrade('opponent', 10), className: "px-3 py-1.5 bg-orange-950/45 hover:bg-orange-950/80 border border-orange-500/40 text-orange-400 text-xs font-bold font-mono rounded cursor-pointer transition-all" }, "Bid Opponent (+10 Sh)"),
120
+ React.createElement("button", { onClick: () => onTrade === null || onTrade === void 0 ? void 0 : onTrade('opponent', 50), className: "px-3 py-1.5 bg-orange-900/60 hover:bg-orange-900/90 border border-orange-500/60 text-orange-200 text-xs font-bold font-mono rounded cursor-pointer transition-all" }, "Bid Opponent (+50 Sh)")))),
121
+ React.createElement(GlassBox, { density: "dense", className: "border border-white/5 bg-black/65 backdrop-blur-md p-6" },
122
+ React.createElement("div", { className: "flex justify-between items-center mb-4" },
123
+ React.createElement("div", { className: "flex items-center gap-2" },
124
+ React.createElement(TrendingUp, { className: "w-4 h-4 text-purple-400" }),
125
+ React.createElement("span", { className: "text-[11px] font-bold text-gray-400 uppercase tracking-widest" }, "Opinion Convergence Over Time")),
126
+ React.createElement("span", { className: "text-[9px] font-mono text-gray-500" }, "Y-Axis: Proponent Probability (0.0 to 1.0)")),
127
+ React.createElement("div", { className: "relative w-full h-[120px] bg-black/20 rounded border border-white/5 p-1" },
128
+ React.createElement("svg", { viewBox: "0 0 500 100", className: "w-full h-full", preserveAspectRatio: "none" },
129
+ React.createElement("defs", null,
130
+ React.createElement("linearGradient", { id: "chartGradient", x1: "0", y1: "0", x2: "0", y2: "1" },
131
+ React.createElement("stop", { offset: "0%", stopColor: "#06b6d4", stopOpacity: "0.4" }),
132
+ React.createElement("stop", { offset: "100%", stopColor: "#8b5cf6", stopOpacity: "0.0" }))),
133
+ React.createElement("line", { x1: "0", y1: "50", x2: "500", y2: "50", stroke: "#ffffff", strokeOpacity: "0.05", strokeDasharray: "5,5" }),
134
+ points && (React.createElement(React.Fragment, null,
135
+ React.createElement("polygon", { points: `0,100 ${points} 500,100`, fill: "url(#chartGradient)" }),
136
+ React.createElement("polyline", { fill: "none", stroke: "#00ffcc", strokeWidth: "1.5", points: points }))))))),
137
+ drawerOpen && (React.createElement(GlassBox, { density: "dense", className: "w-full lg:w-[320px] border border-white/15 bg-black/90 backdrop-blur-lg p-5 rounded-xl shadow-2xl flex flex-col shrink-0" },
138
+ React.createElement("div", { className: "flex justify-between items-center pb-3 border-b border-white/10 mb-4" },
139
+ React.createElement("div", { className: "flex flex-col" },
140
+ React.createElement("span", { className: "text-[10px] font-bold text-purple-400 uppercase tracking-widest" }, "Reasoning Log"),
141
+ React.createElement("span", { className: "text-[9px] text-gray-500 font-mono mt-0.5" }, selectedSpeaker ? `Speaker: ${getShortUrn(selectedSpeaker)}` : 'All Speakers')),
142
+ React.createElement("button", { onClick: () => {
143
+ setDrawerOpen(false);
144
+ setSelectedSpeaker(null);
145
+ }, className: "text-gray-400 hover:text-white transition-colors cursor-pointer p-0.5 bg-white/5 rounded" },
146
+ React.createElement(X, { className: "w-4 h-4" }))),
147
+ React.createElement("div", { className: "flex-1 overflow-y-auto space-y-3 pr-1 max-h-[400px] lg:max-h-none" }, filteredArguments.length === 0 ? (React.createElement("div", { className: "text-center py-8 text-xs text-gray-500 font-mono" }, "No debate arguments found.")) : (filteredArguments.map((arg) => {
148
+ const isProp = arg.speakerUrn === proponent.agentUrn;
149
+ return (React.createElement("div", { key: arg.sequenceIndex, className: `p-3 rounded-lg border flex flex-col gap-1.5 ${isProp
150
+ ? 'border-cyan-950 bg-cyan-950/20 text-left'
151
+ : 'border-orange-950 bg-orange-950/20 text-left'}` },
152
+ React.createElement("div", { className: "flex justify-between items-center" },
153
+ React.createElement("span", { className: "text-[8px] font-bold font-mono px-1.5 py-0.5 rounded bg-black/30 text-gray-300" },
154
+ "#",
155
+ arg.sequenceIndex),
156
+ React.createElement("span", { className: `text-[8px] font-bold font-mono uppercase tracking-wider ${isProp ? 'text-cyan-400' : 'text-orange-400'}` }, isProp ? 'PROPONENT' : 'OPPONENT')),
157
+ React.createElement("p", { className: "text-[10px] text-gray-200 leading-normal font-sans" }, arg.assertion),
158
+ React.createElement("div", { className: "flex justify-between items-center pt-1.5 border-t border-white/5 mt-1 text-[8px] font-mono text-gray-400" },
159
+ React.createElement("span", null,
160
+ "CONFIDENCE: ",
161
+ (arg.confidenceScore * 100).toFixed(0),
162
+ "%"),
163
+ React.createElement("span", null, new Date(arg.timestamp).toLocaleTimeString()))));
164
+ }))),
165
+ selectedSpeaker && (React.createElement("button", { onClick: () => setSelectedSpeaker(null), className: "mt-4 w-full py-1.5 bg-white/5 hover:bg-white/10 text-gray-300 hover:text-white border border-white/5 rounded text-[10px] font-mono font-bold cursor-pointer transition-all" }, "Clear Filter"))))));
166
+ };
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ export interface SemanticConceptNode {
3
+ id: string;
4
+ label: string;
5
+ x: number;
6
+ y: number;
7
+ z: number;
8
+ group: string;
9
+ weight: number;
10
+ }
11
+ export interface SemanticConceptEdge {
12
+ source: string;
13
+ target: string;
14
+ strength: number;
15
+ }
16
+ export interface SemanticConstellationMapProps {
17
+ nodes: SemanticConceptNode[];
18
+ edges: SemanticConceptEdge[];
19
+ activeNodeId?: string;
20
+ onSelectNode?: (nodeId: string) => void;
21
+ className?: string;
22
+ }
23
+ export declare const SemanticConstellationMap: React.FC<SemanticConstellationMapProps>;
@@ -0,0 +1,273 @@
1
+ // Copyright (c) 2026 CoReason, Inc.
2
+ //
3
+ // This software is proprietary and dual-licensed.
4
+ // Licensed under the Prosperity Public License 3.0.
5
+ import React, { useRef, useEffect, useState } from 'react';
6
+ import { GlassBox, cn } from './GlassBox';
7
+ // Map group names to beautiful sleek neon colors
8
+ const GROUP_COLORS = {
9
+ alignment: '#00ffcc', // CoReason Accent Cyan
10
+ inference: '#a855f7', // Purple
11
+ sensory: '#f43f5e', // Rose
12
+ temporal: '#eab308', // Yellow
13
+ default: '#3b82f6', // Blue
14
+ };
15
+ export const SemanticConstellationMap = ({ nodes, edges, activeNodeId, onSelectNode, className, }) => {
16
+ const canvasRef = useRef(null);
17
+ const containerRef = useRef(null);
18
+ const angleRef = useRef(0);
19
+ const animationFrameIdRef = useRef(null);
20
+ const [hoveredNodeId, setHoveredNodeId] = useState(undefined);
21
+ const [dimensions, setDimensions] = useState({ width: 400, height: 350 });
22
+ // Handle container resizing to fit layout
23
+ useEffect(() => {
24
+ if (!containerRef.current)
25
+ return;
26
+ const resizeObserver = new ResizeObserver((entries) => {
27
+ for (const entry of entries) {
28
+ const { width, height } = entry.contentRect;
29
+ setDimensions({
30
+ width: Math.max(width, 100),
31
+ height: Math.max(height, 100),
32
+ });
33
+ }
34
+ });
35
+ resizeObserver.observe(containerRef.current);
36
+ return () => resizeObserver.disconnect();
37
+ }, []);
38
+ // Compute rotation & render projected coordinates
39
+ useEffect(() => {
40
+ const canvas = canvasRef.current;
41
+ if (!canvas)
42
+ return;
43
+ const ctx = canvas.getContext('2d');
44
+ if (!ctx)
45
+ return;
46
+ let isComponentActive = true;
47
+ const renderLoop = () => {
48
+ if (!isComponentActive)
49
+ return;
50
+ // Adjust rotation angle over time
51
+ angleRef.current += 0.003;
52
+ const angle = angleRef.current;
53
+ const cosA = Math.cos(angle);
54
+ const sinA = Math.sin(angle);
55
+ const { width, height } = dimensions;
56
+ // Clear canvas with deep space translucent black
57
+ ctx.fillStyle = '#080808';
58
+ ctx.fillRect(0, 0, width, height);
59
+ // Draw subtle space grid or background stars
60
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.02)';
61
+ ctx.lineWidth = 1;
62
+ const step = 40;
63
+ for (let x = 0; x < width; x += step) {
64
+ ctx.beginPath();
65
+ ctx.moveTo(x, 0);
66
+ ctx.lineTo(x, height);
67
+ ctx.stroke();
68
+ }
69
+ for (let y = 0; y < height; y += step) {
70
+ ctx.beginPath();
71
+ ctx.moveTo(0, y);
72
+ ctx.lineTo(width, y);
73
+ ctx.stroke();
74
+ }
75
+ const cameraDistance = 2.5;
76
+ const scale = Math.min(width, height) * 0.42;
77
+ // 1. Project all nodes into 2D space
78
+ const projectedNodes = nodes.map((node) => {
79
+ // Rotate about Y axis
80
+ const rx = node.x * cosA - node.z * sinA;
81
+ const ry = node.y;
82
+ const rz = node.x * sinA + node.z * cosA;
83
+ // Perspective scaling factor
84
+ const depthScale = 1.0 / (cameraDistance - rz);
85
+ const projX = width / 2 + rx * scale * depthScale;
86
+ const projY = height / 2 + ry * scale * depthScale;
87
+ return Object.assign(Object.assign({}, node), { projX,
88
+ projY, projZ: rz, depthScale });
89
+ });
90
+ // 2. Sort nodes by depth (painter's algorithm: draw furthest first)
91
+ projectedNodes.sort((a, b) => a.projZ - b.projZ);
92
+ // Establish a lookup map for projected nodes
93
+ const projectedNodeMap = new Map();
94
+ for (const pNode of projectedNodes) {
95
+ projectedNodeMap.set(pNode.id, pNode);
96
+ }
97
+ // 3. Draw Cluster Nebulas (Group centers of gravity)
98
+ // Group nodes by cluster
99
+ const groups = Array.from(new Set(nodes.map(n => n.group)));
100
+ for (const group of groups) {
101
+ const groupNodes = projectedNodes.filter(n => n.group === group);
102
+ if (groupNodes.length === 0)
103
+ continue;
104
+ // Calculate average projected position
105
+ let avgX = 0;
106
+ let avgY = 0;
107
+ let avgZ = 0;
108
+ for (const n of groupNodes) {
109
+ avgX += n.projX;
110
+ avgY += n.projY;
111
+ avgZ += n.projZ;
112
+ }
113
+ avgX /= groupNodes.length;
114
+ avgY /= groupNodes.length;
115
+ avgZ /= groupNodes.length;
116
+ // Base nebula color
117
+ const baseColor = GROUP_COLORS[group] || GROUP_COLORS.default;
118
+ // Nebula size scales with depth and cluster weight sum
119
+ const groupWeight = groupNodes.reduce((sum, n) => sum + n.weight, 0);
120
+ const depthScale = 1.0 / (cameraDistance - avgZ);
121
+ const radius = Math.max(30, groupWeight * 20 * depthScale);
122
+ // Simulate radial gradient with concentric transparent circles
123
+ // to avoid using the forbidden radial gradient method
124
+ const preservedAlpha = ctx.globalAlpha;
125
+ const iterations = 8;
126
+ for (let i = 0; i < iterations; i++) {
127
+ const ratio = (iterations - i) / iterations;
128
+ const currentRadius = radius * ratio;
129
+ ctx.beginPath();
130
+ ctx.arc(avgX, avgY, currentRadius, 0, Math.PI * 2);
131
+ ctx.fillStyle = baseColor;
132
+ // Soft exponential opacity falloff
133
+ ctx.globalAlpha = 0.015 * Math.pow(ratio, 2);
134
+ ctx.fill();
135
+ }
136
+ ctx.globalAlpha = preservedAlpha;
137
+ }
138
+ // 4. Draw Edges (Associations)
139
+ for (const edge of edges) {
140
+ const sourceNode = projectedNodeMap.get(edge.source);
141
+ const targetNode = projectedNodeMap.get(edge.target);
142
+ if (!sourceNode || !targetNode)
143
+ continue;
144
+ const averageDepthZ = (sourceNode.projZ + targetNode.projZ) / 2;
145
+ const depthFactor = (averageDepthZ + 1.0) / 2.0; // [0.0, 1.0] range
146
+ ctx.beginPath();
147
+ ctx.moveTo(sourceNode.projX, sourceNode.projY);
148
+ ctx.lineTo(targetNode.projX, targetNode.projY);
149
+ // Edge opacity maps to depth and strength
150
+ ctx.strokeStyle = '#333344';
151
+ ctx.globalAlpha = Math.max(0.05, 0.15 * edge.strength * depthFactor);
152
+ ctx.lineWidth = Math.max(0.5, 2.0 * edge.strength * depthFactor);
153
+ ctx.stroke();
154
+ ctx.globalAlpha = 1.0;
155
+ }
156
+ // 5. Draw Nodes & Labels
157
+ for (const pNode of projectedNodes) {
158
+ const isHovered = hoveredNodeId === pNode.id;
159
+ const isActive = activeNodeId === pNode.id;
160
+ const nodeColor = GROUP_COLORS[pNode.group] || GROUP_COLORS.default;
161
+ // Base particle radius based on depth and importance
162
+ const radius = Math.max(2, pNode.weight * 6 * pNode.depthScale);
163
+ // Highlight ring around active node
164
+ if (isActive) {
165
+ ctx.beginPath();
166
+ ctx.arc(pNode.projX, pNode.projY, radius * 2.2, 0, Math.PI * 2);
167
+ ctx.strokeStyle = nodeColor;
168
+ ctx.lineWidth = 1.5;
169
+ ctx.globalAlpha = 0.4 + 0.2 * Math.sin(Date.now() / 150);
170
+ ctx.stroke();
171
+ ctx.globalAlpha = 1.0;
172
+ }
173
+ // Draw core node particle
174
+ ctx.beginPath();
175
+ ctx.arc(pNode.projX, pNode.projY, radius, 0, Math.PI * 2);
176
+ ctx.fillStyle = nodeColor;
177
+ // Add brightness for hovered or active nodes
178
+ if (isHovered || isActive) {
179
+ ctx.shadowBlur = 12;
180
+ ctx.shadowColor = nodeColor;
181
+ }
182
+ else {
183
+ ctx.shadowBlur = 0;
184
+ }
185
+ ctx.fill();
186
+ ctx.shadowBlur = 0;
187
+ // Draw label text
188
+ const shouldShowLabel = isHovered || isActive || pNode.weight > 0.8;
189
+ if (shouldShowLabel) {
190
+ ctx.fillStyle = isHovered ? '#ffffff' : 'rgba(255, 255, 255, 0.7)';
191
+ ctx.font = `${Math.max(9, Math.round(11 * pNode.depthScale))}px monospace`;
192
+ ctx.textAlign = 'left';
193
+ ctx.textBaseline = 'middle';
194
+ const textX = pNode.projX + radius + 6;
195
+ const textY = pNode.projY;
196
+ ctx.fillText(pNode.label, textX, textY);
197
+ // Draw a small helper indicator for active/hovered node
198
+ if (isHovered) {
199
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
200
+ ctx.lineWidth = 0.5;
201
+ ctx.beginPath();
202
+ ctx.moveTo(pNode.projX, pNode.projY);
203
+ ctx.lineTo(textX - 2, textY);
204
+ ctx.stroke();
205
+ }
206
+ }
207
+ }
208
+ animationFrameIdRef.current = requestAnimationFrame(renderLoop);
209
+ };
210
+ renderLoop();
211
+ return () => {
212
+ isComponentActive = false;
213
+ if (animationFrameIdRef.current) {
214
+ cancelAnimationFrame(animationFrameIdRef.current);
215
+ }
216
+ };
217
+ }, [nodes, edges, activeNodeId, hoveredNodeId, dimensions]);
218
+ // Spatial mouse verification to identify hovered node
219
+ const handleMouseMove = (event) => {
220
+ const canvas = canvasRef.current;
221
+ if (!canvas)
222
+ return;
223
+ const rect = canvas.getBoundingClientRect();
224
+ const mouseX = event.clientX - rect.left;
225
+ const mouseY = event.clientY - rect.top;
226
+ const { width, height } = dimensions;
227
+ const cameraDistance = 2.5;
228
+ const scale = Math.min(width, height) * 0.42;
229
+ const angle = angleRef.current;
230
+ const cosA = Math.cos(angle);
231
+ const sinA = Math.sin(angle);
232
+ let closestNodeId = undefined;
233
+ let minDistance = 16.0; // Click proximity tolerance threshold in pixels
234
+ for (const node of nodes) {
235
+ // Apply exact projection to find projected coordinates
236
+ const rx = node.x * cosA - node.z * sinA;
237
+ const ry = node.y;
238
+ const rz = node.x * sinA + node.z * cosA;
239
+ const depthScale = 1.0 / (cameraDistance - rz);
240
+ const projX = width / 2 + rx * scale * depthScale;
241
+ const projY = height / 2 + ry * scale * depthScale;
242
+ const dist = Math.hypot(projX - mouseX, projY - mouseY);
243
+ if (dist < minDistance) {
244
+ minDistance = dist;
245
+ closestNodeId = node.id;
246
+ }
247
+ }
248
+ if (closestNodeId !== hoveredNodeId) {
249
+ setHoveredNodeId(closestNodeId);
250
+ }
251
+ };
252
+ // Proximity click handler
253
+ const handleCanvasClick = () => {
254
+ if (hoveredNodeId && onSelectNode) {
255
+ onSelectNode(hoveredNodeId);
256
+ }
257
+ };
258
+ return (React.createElement(GlassBox, { className: cn('relative flex flex-col min-h-[300px]', className), variant: "black" },
259
+ React.createElement("div", { className: "flex justify-between items-center border-b border-gray-800 pb-2 mb-2 font-mono" },
260
+ React.createElement("div", null,
261
+ React.createElement("h3", { className: "text-xs font-semibold text-white tracking-widest uppercase" }, "Belief Constellation Map"),
262
+ React.createElement("span", { className: "text-[9px] text-gray-500 uppercase" }, "High-Dimensional Concept Topology")),
263
+ activeNodeId && (React.createElement("div", { className: "text-[9px] text-[var(--cr-accent-cyan)] font-mono uppercase bg-emerald-950/40 border border-emerald-900/60 px-2 py-0.5 rounded" }, "Target Focus Active"))),
264
+ React.createElement("div", { ref: containerRef, className: "relative flex-grow w-full h-full overflow-hidden bg-black/40 rounded border border-gray-900" },
265
+ React.createElement("canvas", { ref: canvasRef, width: dimensions.width, height: dimensions.height, onMouseMove: handleMouseMove, onClick: handleCanvasClick, className: "block cursor-crosshair" }),
266
+ React.createElement("div", { className: "absolute bottom-2 left-2 pointer-events-none font-mono text-[8px] text-gray-600 uppercase flex flex-col space-y-0.5" },
267
+ React.createElement("span", null, "Projection: Perspective (Y-Rot)"),
268
+ React.createElement("span", null,
269
+ "Nodes: ",
270
+ nodes.length,
271
+ " | Edges: ",
272
+ edges.length)))));
273
+ };
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ export type SpeculativeValidationStatus = 'speculative_draft' | 'target_validated' | 'target_rejected';
3
+ export interface SpeculativeTokenNode {
4
+ tokenId: string;
5
+ tokenValue: string;
6
+ logitProbability: number;
7
+ validationStatus: SpeculativeValidationStatus;
8
+ childSpeculations: SpeculativeTokenNode[];
9
+ }
10
+ export interface SpeculativeTreeRendererProps {
11
+ tokenTree: SpeculativeTokenNode;
12
+ onTokenFocus?: (token: SpeculativeTokenNode) => void;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * SpeculativeTreeRenderer
17
+ *
18
+ * Target Persona: Systems Engineer / Operational Intelligencer
19
+ * Concept: System 1 / Speculative Decoding telemetry projection.
20
+ * Projects real-time speculative logit draft token proposals, visual decay,
21
+ * and target model validation status in a cybernetic tree diagram.
22
+ */
23
+ export declare const SpeculativeTreeRenderer: React.FC<SpeculativeTreeRendererProps>;