@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.
- package/CHANGELOG.md +24 -0
- package/dist/components/DlpParticleShield.d.ts +9 -0
- package/dist/components/DlpParticleShield.js +197 -0
- package/dist/components/EpistemicDeficitRadar.d.ts +19 -0
- package/dist/components/EpistemicDeficitRadar.js +108 -0
- package/dist/components/KeysigningCeremonyModal.d.ts +25 -0
- package/dist/components/KeysigningCeremonyModal.js +139 -0
- package/dist/components/PredictionMarketConsensusHud.d.ts +32 -0
- package/dist/components/PredictionMarketConsensusHud.js +166 -0
- package/dist/components/SemanticConstellationMap.d.ts +23 -0
- package/dist/components/SemanticConstellationMap.js +273 -0
- package/dist/components/SpeculativeTreeRenderer.d.ts +23 -0
- package/dist/components/SpeculativeTreeRenderer.js +202 -0
- package/dist/components/TemporalScrubber.d.ts +3 -0
- package/dist/components/TemporalScrubber.js +19 -7
- package/dist/components/TopologicalCanvas.d.ts +2 -6
- package/dist/components/TopologicalCanvas.js +159 -18
- package/dist/components/WasmSandboxCagingHud.d.ts +21 -0
- package/dist/components/WasmSandboxCagingHud.js +167 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +7 -1
- package/package.json +1 -1
|
@@ -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>;
|