hogiahien's picture
write an LLM frontend with options to add an API key, a custom OpenAI-compatible API and chat saving in local browser storage.
507b0ce verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RetroChat 3000 - AI Terminal</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
'primary': '#808080',
'secondary': '#c0c0c0'
}
}
}
}
</script>
<style>
body {
font-family: 'Courier New', monospace;
background-color: #000080;
color: #00ff00;
}
.retro-border {
border: 3px double #c0c0c0;
background-color: #000080;
}
.retro-button {
background-color: #c0c0c0;
border: 2px outset #c0c0c0;
color: #000000;
padding: 2px 6px;
font-family: 'Courier New', monospace;
cursor: pointer;
}
.retro-button:hover {
background-color: #a0a0a0;
}
.retro-button:active {
border: 2px inset #c0c0c0;
}
.retro-input {
background-color: #ffffff;
border: 2px inset #c0c0c0;
color: #000000;
font-family: 'Courier New', monospace;
padding: 2px 4px;
}
.blink {
animation: blink 1s infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
.scan-line {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: rgba(0, 255, 0, 0.3);
animation: scan 2s linear infinite;
pointer-events: none;
}
@keyframes scan {
0% { top: 0; }
100% { top: 100%; }
}
</style>
</head>
<body class="min-h-screen p-4">
<div class="scan-line"></div>
<div class="retro-border p-4 max-w-4xl mx-auto">
<!-- Header -->
<div class="text-center mb-6">
<h1 class="text-3xl font-bold text-green-400 mb-2">
╔══════════════════════════════════╗
<br>β•‘&nbsp;&nbsp;&nbsp;RETROCHAT 3000 - AI TERMINAL&nbsp;&nbsp;&nbsp;β•‘
<br>β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
</h1>
<div class="text-yellow-300 text-sm">
<span class="blink">β– </span> SYSTEM READY - v1.0.0
</div>
</div>
<!-- Settings Panel -->
<div class="retro-border p-4 mb-6">
<h2 class="text-xl text-green-300 mb-3">β•‘ CONFIGURATION PANEL β•‘</h2>
<div class="space-y-3">
<div>
<label class="block text-green-200 mb-1">API KEY:</label>
<input type="password" id="apiKey" placeholder="Enter your API key..."
class="retro-input w-full p-2">
</div>
<div>
<label class="block text-green-200 mb-1">CUSTOM API ENDPOINT:</label>
<input type="text" id="apiEndpoint" placeholder="https://api.example.com/v1/chat/completions"
class="retro-input w-full p-2">
</div>
<div class="flex space-x-2">
<button onclick="saveSettings()" class="retro-button flex-1">
[SAVE CONFIG]
</button>
<button onclick="loadSettings()" class="retro-button flex-1">
[LOAD CONFIG]
</button>
<button onclick="clearSettings()" class="retro-button flex-1">
[CLEAR CONFIG]
</button>
</div>
</div>
</div>
<!-- Chat Interface -->
<div class="retro-border p-4 mb-6">
<h2 class="text-xl text-green-300 mb-3">β•‘ CHAT INTERFACE β•‘</h2>
<!-- Chat History -->
<div id="chatHistory" class="h-64 overflow-y-auto retro-border p-3 mb-3 bg-black text-green-400 font-mono text-sm">
<div>> SYSTEM: Welcome to RetroChat 3000. Configure your API settings and start chatting!</div>
</div>
<!-- Input Area -->
<div class="flex space-x-2">
<input type="text" id="userInput" placeholder="Type your message here..."
class="retro-input flex-1 p-2" onkeypress="handleKeyPress(event)">
<button onclick="sendMessage()" class="retro-button px-4">
[SEND]
</button>
</div>
</div>
<!-- Chat Management -->
<div class="retro-border p-4">
<h2 class="text-xl text-green-300 mb-3">β•‘ CHAT MANAGEMENT β•‘</h2>
<div class="flex space-x-2">
<button onclick="saveChat()" class="retro-button flex-1">
[SAVE CHAT]
</button>
<button onclick="loadChat()" class="retro-button flex-1">
[LOAD CHAT]
</button>
<button onclick="clearChat()" class="retro-button flex-1">
[CLEAR CHAT]
</button>
<button onclick="exportChat()" class="retro-button flex-1">
[EXPORT CHAT]
</button>
</div>
</div>
<!-- Status Bar -->
<div class="mt-4 text-center text-green-200 text-sm">
<div>β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”</div>
<div>β”‚ STATUS: <span id="status">IDLE</span> β”‚ CHAT SAVED: <span id="savedStatus">NO</span> β”‚</div>
<div>β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜</div>
</div>
</div>
<script>
// Storage keys
const STORAGE_KEYS = {
SETTINGS: 'retrochat_settings',
CHAT_HISTORY: 'retrochat_history',
SAVED_CHATS: 'retrochat_saved_chats'
};
// Initialize
document.addEventListener('DOMContentLoaded', function() {
loadSettings();
loadChat();
updateStatus();
});
// Settings Management
function saveSettings() {
const settings = {
apiKey: document.getElementById('apiKey').value,
apiEndpoint: document.getElementById('apiEndpoint').value || 'https://api.openai.com/v1/chat/completions'
};
localStorage.setItem(STORAGE_KEYS.SETTINGS, JSON.stringify(settings));
addToChat('> SYSTEM: Settings saved successfully.');
updateStatus();
}
function loadSettings() {
const saved = localStorage.getItem(STORAGE_KEYS.SETTINGS);
if (saved) {
const settings = JSON.parse(saved);
document.getElementById('apiKey').value = settings.apiKey || '';
document.getElementById('apiEndpoint').value = settings.apiEndpoint || '';
addToChat('> SYSTEM: Settings loaded from storage.');
}
updateStatus();
}
function clearSettings() {
localStorage.removeItem(STORAGE_KEYS.SETTINGS);
document.getElementById('apiKey').value = '';
document.getElementById('apiEndpoint').value = '';
addToChat('> SYSTEM: Settings cleared.');
updateStatus();
}
// Chat Management
function saveChat() {
const chatHistory = document.getElementById('chatHistory').innerHTML;
const savedChats = JSON.parse(localStorage.getItem(STORAGE_KEYS.SAVED_CHATS) || {});
const timestamp = new Date().toISOString();
savedChats[timestamp] = {
content: chatHistory,
name: `Chat_${timestamp.split('T')[0]}`
};
localStorage.setItem(STORAGE_KEYS.SAVED_CHATS, JSON.stringify(savedChats));
localStorage.setItem(STORAGE_KEYS.CHAT_HISTORY, chatHistory);
document.getElementById('savedStatus').textContent = 'YES';
addToChat('> SYSTEM: Chat saved to browser storage.');
}
function loadChat() {
const saved = localStorage.getItem(STORAGE_KEYS.CHAT_HISTORY);
if (saved) {
document.getElementById('chatHistory').innerHTML = saved;
document.getElementById('savedStatus').textContent = 'YES';
addToChat('> SYSTEM: Previous chat loaded.');
}
}
function clearChat() {
document.getElementById('chatHistory').innerHTML = '<div>> SYSTEM: Chat cleared. Ready for new conversation.</div>';
localStorage.removeItem(STORAGE_KEYS.CHAT_HISTORY);
document.getElementById('savedStatus').textContent = 'NO';
}
function exportChat() {
const chatContent = document.getElementById('chatHistory').innerText;
const blob = new Blob([chatContent], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `retrochat_${new Date().toISOString().split('T')[0]}.txt`;
a.click();
URL.revokeObjectURL(url);
addToChat('> SYSTEM: Chat exported as text file.');
}
// Chat Functions
function addToChat(message) {
const chatHistory = document.getElementById('chatHistory');
const newMessage = document.createElement('div');
newMessage.innerHTML = message;
chatHistory.appendChild(newMessage);
chatHistory.scrollTop = chatHistory.scrollHeight;
}
function handleKeyPress(event) {
if (event.key === 'Enter') {
sendMessage();
}
}
async function sendMessage() {
const userInput = document.getElementById('userInput').value.trim();
if (!userInput) return;
// Add user message to chat
addToChat(`> USER: ${userInput}`);
document.getElementById('userInput').value = '';
// Get settings
const settings = JSON.parse(localStorage.getItem(STORAGE_KEYS.SETTINGS) || {};
if (!settings.apiKey) {
addToChat('> SYSTEM: ERROR - No API key configured. Please set your API key in the configuration panel.');
return;
}
// Update status
document.getElementById('status').textContent = 'PROCESSING...';
try {
const response = await fetch(settings.apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${settings.apiKey}`
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [
{
role: "user",
content: userInput
}
]
})
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
const aiResponse = data.choices[0].message.content;
addToChat(`> AI: ${aiResponse}`);
document.getElementById('status').textContent = 'IDLE';
} catch (error) {
addToChat(`> SYSTEM: ERROR - ${error.message}`);
document.getElementById('status').textContent = 'ERROR';
}
}
function updateStatus() {
const hasSettings = localStorage.getItem(STORAGE_KEYS.SETTINGS);
const hasChat = localStorage.getItem(STORAGE_KEYS.CHAT_HISTORY);
document.getElementById('status').textContent = hasSettings ? 'CONFIGURED' : 'NEEDS_CONFIG';
document.getElementById('savedStatus').textContent = hasChat ? 'YES' : 'NO';
}
// Add some retro effects
setInterval(() => {
const status = document.getElementById('status');
if (status.textContent === 'IDLE') {
status.classList.toggle('text-green-400');
status.classList.toggle('text-green-600');
}
}, 1000);
</script>
</body>
</html>