-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
369 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,369 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Chat with LoLLMS</title> | ||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> | ||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism.min.css" /> | ||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script> | ||
<style> | ||
#chat { | ||
max-height: 400px; | ||
overflow-y: auto; | ||
border: 1px solid #e2e8f0; /* Light gray border */ | ||
border-radius: 0.5rem; /* Rounded corners */ | ||
background-color: #f7fafc; /* Light gray background */ | ||
padding: 1rem; /* Padding inside the chat area */ | ||
} | ||
.message { | ||
padding: 0.5rem; | ||
border-radius: 0.375rem; /* Rounded corners for messages */ | ||
margin-bottom: 0.5rem; /* Space between messages */ | ||
display: flex; /* Flexbox for icon and message */ | ||
align-items: center; /* Center items vertically */ | ||
} | ||
.user { | ||
background-color: #cbd5e0; /* Light blue for user messages */ | ||
text-align: right; /* Align user messages to the right */ | ||
} | ||
.assistant { | ||
background-color: #e6fffa; /* Light green for assistant messages */ | ||
text-align: left; /* Align assistant messages to the left */ | ||
} | ||
.icon { | ||
width: 30px; /* Icon size */ | ||
height: 30px; /* Icon size */ | ||
border-radius: 50%; /* Circular icon */ | ||
margin-right: 0.5rem; /* Space between icon and message */ | ||
} | ||
.settings-modal { | ||
display: none; /* Hidden by default */ | ||
position: fixed; | ||
top: 0; | ||
left: 0; | ||
right: 0; | ||
bottom: 0; | ||
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */ | ||
justify-content: center; | ||
align-items: center; | ||
} | ||
.settings-content { | ||
background-color: white; | ||
padding: 2rem; | ||
border-radius: 0.5rem; | ||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); | ||
} | ||
.discussion-list { | ||
max-height: 200px; | ||
overflow-y: auto; | ||
border: 1px solid #e2e8f0; /* Light gray border */ | ||
border-radius: 0.5rem; /* Rounded corners */ | ||
background-color: #f7fafc; /* Light gray background */ | ||
padding: 1rem; /* Padding inside the discussion list */ | ||
margin-bottom: 1rem; /* Space below the discussion list */ | ||
} | ||
.discussion-item { | ||
padding: 0.5rem; | ||
border-radius: 0.375rem; /* Rounded corners for discussion items */ | ||
margin-bottom: 0.5rem; /* Space between discussion items */ | ||
display: flex; /* Flexbox for discussion item */ | ||
justify-content: space-between; /* Space between text and button */ | ||
align-items: center; /* Center items vertically */ | ||
background-color: #e2e8f0; /* Light gray background for items */ | ||
} | ||
</style> | ||
</head> | ||
<body class="bg-gray-100 flex items-center justify-center h-screen"> | ||
<div class="bg-white rounded-lg shadow-lg p-6 w-full max-w-md"> | ||
<h1 class="text-xl font-bold mb-4">Chat with LoLLMS</h1> | ||
<div class="discussion-list" id="discussionList"> | ||
<!-- Old discussions will be listed here --> | ||
</div> | ||
<button id="newDiscussionButton" class="mt-2 w-full bg-green-500 text-white p-2 rounded-lg">New Discussion</button> | ||
<div id="chat" class="mb-4 p-4"> | ||
<!-- Chat messages will be appended here --> | ||
</div> | ||
<textarea id="userInput" class="w-full p-2 border border-gray-300 rounded-lg" rows="3" placeholder="Type your message..."></textarea> | ||
<button id="sendButton" class="mt-2 w-full bg-blue-500 text-white p-2 rounded-lg">Send</button> | ||
<button id="settingsButton" class="mt-2 w-full bg-gray-500 text-white p-2 rounded-lg">Settings</button> | ||
<div class="mt-4"> | ||
<label class="block mb-2">Select Mode:</label> | ||
<select id="modeSelect" class="w-full p-2 border border-gray-300 rounded-lg"> | ||
<option value="instruct">Instruct Mode</option> | ||
<option value="chat">Chat Mode</option> | ||
</select> | ||
</div> | ||
</div> | ||
|
||
<div id="settingsModal" class="settings-modal flex"> | ||
<div class="settings-content"> | ||
<h2 class="text-lg font-bold mb-4">Settings</h2> | ||
<label for="apiKey" class="block mb-2">API Key:</label> | ||
<input type="text" id="apiKey" class="w-full p-2 border border-gray-300 rounded-lg mb-4" placeholder="Enter API Key"> | ||
<label for="serverAddress" class="block mb-2">Select Server Address:</label> | ||
<select id="serverSelect" class="w-full p-2 border border-gray-300 rounded-lg mb-4"></select> | ||
<input type="text" id="newServerInput" class="w-full p-2 border border-gray-300 rounded-lg mb-4" placeholder="Enter new server address"> | ||
<button id="addServerButton" class="mt-2 w-full bg-yellow-500 text-white p-2 rounded-lg">Add Server</button> | ||
<label for="modelSelect" class="block mb-2">Select Model:</label> | ||
<select id="modelSelect" class="w-full p-2 border border-gray-300 rounded-lg mb-4"></select> | ||
<button id="addModelButton" class="mt-2 w-full bg-yellow-500 text-white p-2 rounded-lg">Add Model</button> | ||
<input type="text" id="newModelInput" class="w-full p-2 border border-gray-300 rounded-lg mb-4" placeholder="Enter new model name"> | ||
<label for="userIcon" class="block mb-2">Your Icon:</label> | ||
<input type="file" id="userIcon" class="w-full mb-4" accept="image/*"> | ||
<label for="assistantIcon" class="block mb-2">Assistant Icon:</label> | ||
<input type="file" id="assistantIcon" class="w-full mb-4" accept="image/*"> | ||
<button id="saveSettings" class="w-full bg-blue-500 text-white p-2 rounded-lg">Save</button> | ||
<button id="closeSettings" class="w-full bg-red-500 text-white p-2 rounded-lg mt-2">Close</button> | ||
</div> | ||
</div> | ||
|
||
<script> | ||
const defaultApiKey = localStorage.getItem('apiKey') || 'your_api_key_here'; // Default API key | ||
const defaultApiUrl = localStorage.getItem('apiUrl') || 'http://localhost:9600/v1/completions'; // Default server address | ||
const defaultUserIcon = localStorage.getItem('userIcon') || 'https://via.placeholder.com/30'; // Default user icon | ||
const defaultAssistantIcon = localStorage.getItem('assistantIcon') || 'https://via.placeholder.com/30'; // Default assistant icon | ||
const defaultModel = localStorage.getItem('model') || 'gpt-4o-mini'; // Default model | ||
let apiKey = defaultApiKey; | ||
let apiUrl = defaultApiUrl; | ||
let userIcon = defaultUserIcon; | ||
let assistantIcon = defaultAssistantIcon; | ||
let conversationHistory = ""; // To store the conversation history | ||
let discussions = JSON.parse(localStorage.getItem('discussions')) || []; // Load discussions from localStorage | ||
let models = JSON.parse(localStorage.getItem('models')) || ['gpt-4o-mini', 'lollms']; // Load models from localStorage | ||
let servers = JSON.parse(localStorage.getItem('servers')) || ['http://localhost:9600/v1/completions']; // Load servers from localStorage | ||
|
||
function renderDiscussionList() { | ||
const discussionList = document.getElementById('discussionList'); | ||
discussionList.innerHTML = ''; // Clear the list | ||
discussions.forEach((discussion, index) => { | ||
const discussionItem = document.createElement('div'); | ||
discussionItem.className = 'discussion-item'; | ||
discussionItem.innerHTML = ` | ||
<span>Discussion ${index + 1}</span> | ||
<div> | ||
<button class="view-button bg-blue-500 text-white p-1 rounded-lg" data-index="${index}">View</button> | ||
<button class="remove-button bg-red-500 text-white p-1 rounded-lg ml-2" data-index="${index}">Remove</button> | ||
</div> | ||
`; | ||
discussionList.appendChild(discussionItem); | ||
}); | ||
} | ||
|
||
function renderModelSelect() { | ||
const modelSelect = document.getElementById('modelSelect'); | ||
modelSelect.innerHTML = ''; // Clear existing options | ||
models.forEach(model => { | ||
const option = document.createElement('option'); | ||
option.value = model; | ||
option.textContent = model.charAt(0).toUpperCase() + model.slice(1); // Capitalize first letter | ||
modelSelect.appendChild(option); | ||
}); | ||
modelSelect.value = defaultModel; // Set the default model | ||
} | ||
|
||
function renderServerSelect() { | ||
const serverSelect = document.getElementById('serverSelect'); | ||
serverSelect.innerHTML = ''; // Clear existing options | ||
servers.forEach(server => { | ||
const option = document.createElement('option'); | ||
option.value = server; | ||
option.textContent = server; // Display server address | ||
serverSelect.appendChild(option); | ||
}); | ||
serverSelect.value = apiUrl; // Set the default server | ||
} | ||
|
||
document.getElementById('addServerButton').addEventListener('click', () => { | ||
const newServerInput = document.getElementById('newServerInput'); | ||
const newServer = newServerInput.value.trim(); | ||
if (newServer && !servers.includes(newServer)) { | ||
servers.push(newServer); | ||
localStorage.setItem('servers', JSON.stringify(servers)); // Save servers to localStorage | ||
renderServerSelect(); // Refresh server select options | ||
newServerInput.value = ''; // Clear input | ||
} else { | ||
alert('Server address is either empty or already exists.'); | ||
} | ||
}); | ||
|
||
document.getElementById('addModelButton').addEventListener('click', () => { | ||
const newModelInput = document.getElementById('newModelInput'); | ||
const newModel = newModelInput.value.trim(); | ||
if (newModel && !models.includes(newModel)) { | ||
models.push(newModel); | ||
localStorage.setItem('models', JSON.stringify(models)); // Save models to localStorage | ||
renderModelSelect(); // Refresh model select options | ||
newModelInput.value = ''; // Clear input | ||
} else { | ||
alert('Model name is either empty or already exists.'); | ||
} | ||
}); | ||
|
||
document.getElementById('newDiscussionButton').addEventListener('click', () => { | ||
conversationHistory = ""; // Reset conversation history for new discussion | ||
renderDiscussionList(); // Refresh the discussion list | ||
document.getElementById('chat').innerHTML = ''; // Clear chat messages | ||
}); | ||
|
||
document.getElementById('sendButton').addEventListener('click', async () => { | ||
const userInput = document.getElementById('userInput'); | ||
const prompt = userInput.value; | ||
const selectedModel = document.getElementById('modelSelect').value; | ||
const selectedMode = document.getElementById('modeSelect').value; | ||
if (!prompt) return; | ||
|
||
appendMessage('You: ' + prompt, 'user'); | ||
userInput.value = ''; | ||
|
||
const responseText = await generateText(prompt, selectedModel, selectedMode); | ||
appendMessage('LoLLMS: ' + responseText, 'assistant'); | ||
}); | ||
|
||
document.getElementById('settingsButton').addEventListener('click', () => { | ||
document.getElementById('settingsModal').style.display = 'flex'; | ||
document.getElementById('apiKey').value = apiKey; | ||
renderServerSelect(); // Populate server select options | ||
renderModelSelect(); // Populate model select options | ||
}); | ||
|
||
document.getElementById('saveSettings').addEventListener('click', () => { | ||
apiKey = document.getElementById('apiKey').value; | ||
apiUrl = document.getElementById('serverSelect').value; // Get selected server address | ||
const selectedModel = document.getElementById('modelSelect').value; | ||
localStorage.setItem('apiKey', apiKey); | ||
localStorage.setItem('apiUrl', apiUrl); | ||
localStorage.setItem('model', selectedModel); // Save selected model to local storage | ||
alert('Settings saved!'); | ||
document.getElementById('settingsModal').style.display = 'none'; | ||
}); | ||
|
||
document.getElementById('closeSettings').addEventListener('click', () => { | ||
document.getElementById('settingsModal').style.display = 'none'; | ||
}); | ||
|
||
async function generateText(userPrompt, model, mode) { | ||
let data; | ||
if (mode === "chat") { | ||
data = { | ||
model: model, | ||
messages: [{ role: "user", content: userPrompt }], | ||
temperature: 0.7 | ||
}; | ||
} else { | ||
// Instruct mode | ||
const systemPrompt = "You are a helpful assistant."; | ||
const fullPrompt = `${systemPrompt}\n${conversationHistory}<@>user: ${userPrompt}<@>assistant:`; | ||
data = { | ||
model: model, | ||
prompt: fullPrompt, | ||
stream: true, | ||
temperature: 0.7, | ||
max_tokens: 100 | ||
}; | ||
} | ||
|
||
try { | ||
const response = await fetch(apiUrl, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'Authorization': `Bearer ${apiKey}` | ||
}, | ||
body: JSON.stringify(data) | ||
}); | ||
|
||
const responseData = await response.json(); | ||
|
||
if (mode === "chat") { | ||
return responseData.choices[0].message.content; // Return content from chat mode | ||
} else { | ||
// Handle instruct mode response | ||
let text = ''; | ||
const reader = response.body.getReader(); | ||
const decoder = new TextDecoder('utf-8'); | ||
|
||
while (true) { | ||
const { done, value } = await reader.read(); | ||
if (done) break; | ||
const chunk = decoder.decode(value, { stream: true }); | ||
const lines = chunk.split('\n').filter(line => line.trim() !== ''); | ||
|
||
for (const line of lines) { | ||
if (line.startsWith('data: ')) { | ||
const jsonData = line.substring(6).trim(); // Remove 'data: ' prefix | ||
if (jsonData === '[DONE]') { | ||
break; // Stop processing on '[DONE]' | ||
} | ||
try { | ||
const parsedData = JSON.parse(jsonData); | ||
text += parsedData.choices[0].text; | ||
} catch (e) { | ||
console.error('Error parsing JSON:', e); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Update conversation history | ||
conversationHistory += `<@>user: ${userPrompt}<@>assistant: ${text}\n`; | ||
return text; | ||
} | ||
} catch (error) { | ||
console.error('Error:', error); | ||
return 'Error occurred while generating text.'; | ||
} | ||
} | ||
|
||
function appendMessage(message, sender) { | ||
const chat = document.getElementById('chat'); | ||
const messageElement = document.createElement('div'); | ||
messageElement.className = `message ${sender}`; // Add class based on sender | ||
|
||
const iconUrl = sender === 'user' ? userIcon : assistantIcon; | ||
const name = sender === 'user' ? 'You' : 'LoLLMS'; | ||
|
||
// Render Markdown and highlight code blocks | ||
const renderedMessage = marked.parse(message); | ||
messageElement.innerHTML = ` | ||
<img src="${iconUrl}" alt="${name}'s icon" class="icon"> | ||
<div> | ||
<strong>${name}:</strong> | ||
<div>${renderedMessage}</div> | ||
</div> | ||
`; // Render Markdown | ||
chat.appendChild(messageElement); | ||
chat.scrollTop = chat.scrollHeight; // Scroll to the bottom | ||
|
||
// Highlight code blocks | ||
Prism.highlightAll(); | ||
} | ||
|
||
document.getElementById('discussionList').addEventListener('click', (event) => { | ||
if (event.target.classList.contains('view-button')) { | ||
const index = event.target.getAttribute('data-index'); | ||
conversationHistory = discussions[index]; // Load the selected discussion | ||
document.getElementById('chat').innerHTML = ''; // Clear chat messages | ||
const messages = conversationHistory.split('\n'); | ||
messages.forEach(msg => { | ||
if (msg) { | ||
const [role, content] = msg.split(': '); | ||
appendMessage(`${role}: ${content}`, role.trim()); | ||
} | ||
}); | ||
} else if (event.target.classList.contains('remove-button')) { | ||
const index = event.target.getAttribute('data-index'); | ||
discussions.splice(index, 1); // Remove the selected discussion | ||
localStorage.setItem('discussions', JSON.stringify(discussions)); // Update localStorage | ||
renderDiscussionList(); // Refresh the discussion list | ||
} | ||
}); | ||
|
||
// Initial render of discussions, models, and servers | ||
renderDiscussionList(); | ||
renderModelSelect(); | ||
renderServerSelect(); | ||
</script> | ||
</body> | ||
</html> |