Created
December 31, 2024 06:18
-
-
Save kalomaze/0656410178c2526a31b1f531167c0c73 to your computer and use it in GitHub Desktop.
Branching LLM frontend react widget prototype
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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