Skip to content

Instantly share code, notes, and snippets.

@kalomaze
Created December 31, 2024 06:18
Show Gist options
  • Save kalomaze/0656410178c2526a31b1f531167c0c73 to your computer and use it in GitHub Desktop.
Save kalomaze/0656410178c2526a31b1f531167c0c73 to your computer and use it in GitHub Desktop.
Branching LLM frontend react widget prototype
import React, { useState } from 'react';
import { Settings, Bookmark, Download, Library, HelpCircle, RefreshCw, ArrowLeft } from 'lucide-react';
const STORY_BRANCHES = {
root: {
text: `The darkness grew absolute, not that the hyperstitioner could see in the first place. His ears pricked up, however; he could hear the skittering, the mechanical hum as the machine followed him invisibly...`,
continuations: [
{
id: 'a1',
text: " The mechanical tendrils wrapped tighter around his shoulder, its grip a cold reminder of their symbiosis...",
continuations: [
{
id: 'a1-1',
text: " He welcomed its touch, knowing that only through this union could they hope to breach the fortress's final defenses...",
},
{
id: 'a1-2',
text: " But something was wrong - the usual synchronicity of their movements had become discordant, threatening to tear apart their carefully maintained bond...",
},
{
id: 'a1-3',
text: " Together they moved as one through the darkness, their shared consciousness expanding to map the geometric impossibilities ahead...",
}
]
},
{
id: 'a2',
text: " He reached toward the writhing shadows, feeling the borders of reality grow thin where his fingers traced the air...",
continuations: [
{
id: 'a2-1',
text: " The membrane between dimensions parted like silk, revealing glimpses of impossible architectures that hurt to look upon...",
},
{
id: 'a2-2',
text: " But the shadows recoiled from his touch, carrying with them fragments of memories that didn't belong to him...",
},
{
id: 'a2-3',
text: " Static filled his mind as his hand penetrated the veil, establishing the first bridge between their world and what lay beyond...",
}
]
},
{
id: 'a3',
text: " The fortress's defenses stirred, ancient algorithms awakening to assess this latest intrusion...",
continuations: [
{
id: 'a3-1',
text: " He felt their cold logic washing over him, probing for weaknesses in his augmented consciousness...",
},
{
id: 'a3-2',
text: " Lines of corrupted code manifested in the air around him, a cage of light and mathematical certainties...",
},
{
id: 'a3-3',
text: " But he was ready, his own viral equations already spreading through the fortress's foundations...",
}
]
}
]
}
};
const ThreadGenerator = () => {
const [temperature, setTemperature] = useState(1.0);
const [length, setLength] = useState(210);
// Track current thread state
const [threadState, setThreadState] = useState({
baseText: STORY_BRANCHES.root.text,
currentBranch: null,
depth: 0,
path: []
});
// Track history for back button and hover states
const [history, setHistory] = useState([]);
const [hoveredIndex, setHoveredIndex] = useState(null);
const [hoveredContinuation, setHoveredContinuation] = useState(null);
const getCurrentContinuations = (branch) => {
if (!branch) {
return STORY_BRANCHES.root.continuations;
}
return branch.continuations || [];
};
const handleContinuationClick = (continuation) => {
const newPath = [...threadState.path, continuation.id];
setHistory(prev => [...prev, threadState]);
setThreadState({
baseText: threadState.baseText + continuation.text,
currentBranch: continuation,
depth: threadState.depth + 1,
path: newPath
});
};
const handleBackClick = () => {
if (history.length > 0) {
const previousState = history[history.length - 1];
setThreadState(previousState);
setHistory(prev => prev.slice(0, -1));
}
};
const reconstructBranchText = (index) => {
let currentText = STORY_BRANCHES.root.text;
let currentBranch = null;
// Traverse through the path to reconstruct the text and find the branch at this index
for (let i = 0; i <= index; i++) {
const continuations = getCurrentContinuations(currentBranch);
const continuation = continuations.find(cont => cont.id === threadState.path[i]);
if (continuation) {
currentText += continuation.text;
currentBranch = continuation;
}
}
return { text: currentText, branch: currentBranch };
};
const handleTextHover = (index) => {
setHoveredIndex(index);
setHoveredContinuation(null);
};
const handleTextUnhover = () => {
setHoveredIndex(null);
setHoveredContinuation(null);
};
const handleContinuationHover = (continuation) => {
setHoveredContinuation(continuation);
setHoveredIndex(null);
};
// Reconstruct the text with hover effect
const renderThreadText = () => {
const fullText = threadState.baseText;
const textPieces = [];
// Root text
textPieces.push({
text: STORY_BRANCHES.root.text,
depth: 0,
isHovered: hoveredIndex === null && hoveredContinuation === null,
onClick: () => {
setThreadState({
baseText: STORY_BRANCHES.root.text,
currentBranch: null,
depth: 0,
path: []
});
setHistory([]);
}
});
// Add subsequent branch texts
threadState.path.forEach((branchId, index) => {
const { text, branch } = reconstructBranchText(index);
const continuations = getCurrentContinuations(
index === 0 ? null : reconstructBranchText(index - 1).branch
);
const continuation = continuations.find(cont => cont.id === branchId);
if (continuation) {
textPieces.push({
text: continuation.text,
depth: index + 1,
isHovered: hoveredIndex === index,
onClick: () => {
// Reconstruct the state up to this point
const newHistory = history.slice(0, index);
const reconstructedState = {
baseText: text,
currentBranch: branch,
depth: index + 1,
path: threadState.path.slice(0, index + 1)
};
setThreadState(reconstructedState);
setHistory(newHistory);
}
});
}
});
return textPieces.map((piece, idx) => (
<span
key={idx}
className={`cursor-pointer
${hoveredIndex === null && hoveredContinuation === null
? 'text-gray-100'
: (hoveredIndex === idx - 1 || hoveredContinuation ? 'text-gray-100' : 'text-gray-400 opacity-50')
}
`}
onClick={piece.onClick}
onMouseEnter={() => handleTextHover(idx - 1)}
onMouseLeave={handleTextUnhover}
>
{piece.text}
</span>
));
};
const continuations = getCurrentContinuations(threadState.currentBranch);
return (
<div className="max-w-2xl mx-auto bg-gray-900 text-gray-100 min-h-screen">
{/* Top Navigation */}
<div className="border-b border-gray-700 p-4 flex justify-between items-center">
{history.length > 0 && (
<button
onClick={handleBackClick}
className="flex items-center space-x-2 text-blue-400 hover:text-blue-300"
>
<ArrowLeft className="w-5 h-5" />
<span>Back</span>
</button>
)}
<div className="flex space-x-4 ml-auto">
<Bookmark className="w-5 h-5" />
<Download className="w-5 h-5" />
<Library className="w-5 h-5" />
<HelpCircle className="w-5 h-5" />
<Settings className="w-5 h-5" />
</div>
</div>
{/* Controls */}
<div className="p-4 border-b border-gray-700 flex space-x-6">
<div className="flex-1">
<label className="block text-sm text-gray-400">Temperature</label>
<input
type="range"
min="0"
max="2"
step="0.1"
value={temperature}
onChange={(e) => setTemperature(parseFloat(e.target.value))}
className="w-full"
/>
<div className="text-sm text-gray-400 mt-1">{temperature.toFixed(1)}</div>
</div>
<div className="flex-1">
<label className="block text-sm text-gray-400">Length</label>
<input
type="range"
min="50"
max="500"
value={length}
onChange={(e) => setLength(parseInt(e.target.value))}
className="w-full"
/>
<div className="text-sm text-gray-400 mt-1">{length}</div>
</div>
</div>
{/* Thread View */}
<div className="space-y-4 p-4">
{/* Current Thread */}
<div className="flex items-start space-x-4">
<div className="w-12 h-12 rounded-full bg-blue-700 flex-shrink-0 flex items-center justify-center text-xl font-bold">
{threadState.depth}
</div>
<div className="flex-1">
<p className="text-gray-100">
{renderThreadText()}
</p>
</div>
</div>
{/* Continuations */}
{continuations.map(continuation => (
<div
key={continuation.id}
className={`flex items-start space-x-4 pl-8 cursor-pointer hover:bg-gray-800 p-2 rounded-lg
${hoveredContinuation === continuation ? '' : 'opacity-50'}
`}
onClick={() => handleContinuationClick(continuation)}
onMouseEnter={() => handleContinuationHover(continuation)}
onMouseLeave={handleTextUnhover}
>
<div className="w-12 h-12 rounded-full bg-gray-700 flex-shrink-0 flex items-center justify-center text-xl font-bold">
{threadState.depth + 1}
</div>
<div className="flex-1">
<p className="text-gray-400">{continuation.text}</p>
</div>
</div>
))}
</div>
</div>
);
};
export default ThreadGenerator;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment