Semantic Cube
Maps natural language onto a three-axis semantic space grounded in up-hierarchy theory. An LLM parses input into structured JSON; the app renders the result as an interactive 3D cube.
Purpose
Meaning is not a fixed definition — it is a position in a multidimensional space describing how a signal propagates from bodily, proximal experience toward broader contextual integration. The cube makes that position visible.
UI Components
Settings Panel
- Appears on first load when no API key is configured, or via gear icon
- Fields for OPENAI_API_KEY, GEMINI_API_KEY, DEEPSEEK_API_KEY (enter one or more)
- Keys stored in localStorage; provider auto-detected (OpenAI → DeepSeek → Gemini priority)
- Save button persists keys and dismisses panel
3D Cube (main area)
- Three axes: factual (X), charge (Y), stream (Z)
- Axis labels at each end with value range annotations
- Nodes rendered as spheres; size uniform
- Multiple nodes in a path connected by a line
- Multiple paths rendered in distinct colors (cycling palette)
- Hover over a node shows a tooltip with the node label
- OrbitControls for rotate, zoom, pan
Input Bar (bottom)
- Textarea for natural language input
- Submit button (or Enter key)
- Reset button — clears conversation history and scene
Status / Error Area
- Shows loading state while LLM call is in progress
- Shows parse errors or API errors inline
Three Axes
| Axis | Range | Meaning |
|---|---|---|
| STREAM | 0.0–1.0 | 0 = proximal/bodily, 1 = distal/symbolic |
| FACTUAL | 0.0–1.0 | 0 = no coherence beyond origin, 1 = confirmed across scales |
| CHARGE | −1.0–1.0 | −1 = propagation blocked, 0 = neutral, 1 = open/expansive |
Dependencies
| Package | Version | Purpose |
|---|---|---|
react |
19 | UI framework |
react-dom |
19 | DOM rendering |
@react-three/fiber |
9 | React renderer for Three.js |
@react-three/drei |
10 | OrbitControls, Text, Html helpers |
three |
0.175 | 3D engine (peer dep) |
| esm.sh/tsx | — | In-browser TSX compiler (script loader) |
| daisyui | 5 | Component library with light/dark theming |
| @tailwindcss/browser | 4 | Utility classes |
LLM Integration
Provider Detection
Checks localStorage for keys in order: OPENAI_API_KEY → DEEPSEEK_API_KEY → GEMINI_API_KEY. Uses first found.
| Provider | Endpoint | Model |
|---|---|---|
| OpenAI | https://api.openai.com/v1/chat/completions |
gpt-4o |
| DeepSeek | https://api.deepseek.com/v1/chat/completions |
deepseek-chat |
| Gemini | https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent |
— |
Conversation History
Maintains a messages array across submissions. Each submission appends {role: "user", content: input} and the LLM response {role: "assistant", content: rawJson}. The full history is sent on every call so the LLM can coherently expand and reposition all paths.
System Prompt Compression
On load, the raw SYSTEM_PROMPT string is hashed with crypto.subtle.digest('SHA-256', ...). The hash is compared to localStorage['semantic-cube:prompt-hash']. On mismatch or missing, calls https://tokenshrink.com/api/compress and caches both hash and compressed prompt in localStorage. All LLM calls use the compressed prompt.
JSON Schema
{
"input": "raw user input",
"concept": "concept being mapped",
"paths": [
{
"id": "what this path represents",
"nodes": [
{ "factual": 0.0, "charge": 0.0, "stream": 0.0, "label": "..." }
]
}
]
}
Key Functions
| Function | Responsibility |
|---|---|
hashPrompt(text) |
SHA-256 hash as hex string |
getCompressedPrompt() |
Returns cached prompt or fetches from tokenshrink |
detectProvider() |
Reads localStorage, returns {type, key} |
callLLM(messages, systemPrompt) |
Dispatches to correct provider adapter |
callOpenAI / callDeepSeek |
OpenAI-compatible chat completions |
callGemini |
Gemini generateContent adapter |
parseResponse(text) |
JSON.parse with error handling |
Scene |
R3F Canvas with cube, axes, paths, controls |
CubeFrame |
Wireframe unit cube |
AxisLabels |
Text labels at axis ends |
PathObjects |
Spheres and lines for all paths |
NodeTooltip |
Html overlay on hover |
App |
Root component, manages state |
Theming
DaisyUI v5 CSS is loaded via CDN. An inline script sets data-theme on <html> to light or dark based on prefers-color-scheme and listens for changes. The Three.js Canvas uses gl={{ alpha: true }} so the DaisyUI bg-base-200 of its parent div shows through as the scene background.
User Workflow
- First load → settings panel if no key stored
- Enter key → saved to localStorage
- Type input → Submit
- App compresses prompt (once, cached), builds message history, calls LLM
- LLM returns full JSON with all paths so far
- Scene re-renders with updated paths
- Repeat — each submission adds to history; LLM maintains semantic coherence across all paths
- Reset → clears messages array and scene
Future Directions: Alternative Visualizations
Parallel coordinates — three vertical rails (factual, charge, stream); each node is a polyline crossing all three. Patterns across many paths become visible as crossing/parallel lines.
2D projection triptych — three side-by-side scatter plots showing each pair of axes (factual/charge, factual/stream, charge/stream). Useful for reading two axes at a time without 3D navigation.
Ternary triangle — equilateral triangle, each vertex = one axis pole. Node position reflects the interplay of all three simultaneously. Good for single-concept snapshots.
Radial/compass — charge as angle (−1 = south, +1 = north), factual as radius, stream as color temperature. One circle per path; journeys draw arcs.
Body silhouette — stream → body region (head/chest/gut), factual → field density (close/diffuse), charge → color temperature (warm/cool). Meaning reads as a somatic field.
Gauge dashboard — three arc gauges + a bidirectional charge bar. Minimal, readable at a glance. Best for single-node responses; journeys step through nodes.
Typographic layout — node labels as text, positioned vertically by stream, sized by factual, colored by charge. No geometry — meaning reads through the words themselves.
Sankey/flow ribbon — for journeys (one path, many nodes): nodes as columns, flow width = charge magnitude, vertical band = factual range. Shows propagation as a physical flow.
Force-directed graph — nodes as points attracting/repelling by coordinate proximity. Paths as edges. Across multiple submissions, semantic clusters emerge spatially.
Timeline strip — x = submission order, y = stream value, dot size = factual, dot color = charge. Shows how the space being explored evolves across a session.