multi-response-chat / index.html
gghfez's picture
Update index.html
dfdad2a verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multi-Response Chat</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a1a;
color: #e0e0e0;
padding: 20px;
line-height: 1.6;
}
#container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
margin-bottom: 20px;
color: #fff;
}
.config-section {
background: #2a2a2a;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.config-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin-bottom: 15px;
}
.config-field {
display: flex;
flex-direction: column;
}
label {
display: block;
margin-bottom: 6px;
font-weight: bold;
font-size: 0.9em;
}
input[type="text"],
input[type="password"],
select {
background: #1a1a1a;
border: 2px solid #3a3a3a;
border-radius: 6px;
padding: 10px;
color: #e0e0e0;
font-size: 14px;
font-family: inherit;
}
input:focus,
select:focus,
textarea:focus {
outline: none;
border-color: #4a7c59;
}
#chat-history {
background: #2a2a2a;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
min-height: 200px;
max-height: 500px;
overflow-y: auto;
}
.message {
margin-bottom: 15px;
padding: 10px 15px;
border-radius: 6px;
}
.message.user {
background: #1e3a5f;
margin-left: 20%;
}
.message.assistant {
background: #2a4a2a;
margin-right: 20%;
}
.message-label {
font-weight: bold;
margin-bottom: 5px;
font-size: 0.85em;
opacity: 0.7;
}
#responses-container {
display: none;
margin-bottom: 20px;
}
.response-option {
background: #2a2a2a;
border: 2px solid #3a3a3a;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
cursor: pointer;
transition: all 0.2s;
position: relative;
}
.response-option:hover {
border-color: #4a7c59;
background: #2f2f2f;
}
.response-option.selected {
border-color: #5a9a6a;
background: #2a3f2a;
}
.response-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-size: 0.85em;
opacity: 0.7;
}
.response-text {
white-space: pre-wrap;
}
#input-container {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
#user-input {
flex: 1;
background: #2a2a2a;
border: 2px solid #3a3a3a;
border-radius: 6px;
padding: 12px;
color: #e0e0e0;
font-size: 14px;
font-family: inherit;
resize: vertical;
min-height: 80px;
}
button {
background: #4a7c59;
border: none;
border-radius: 6px;
padding: 12px 24px;
color: white;
font-weight: bold;
cursor: pointer;
transition: background 0.2s;
}
button:hover:not(:disabled) {
background: #5a9a6a;
}
button:disabled {
background: #3a3a3a;
cursor: not-allowed;
opacity: 0.5;
}
#confirm-selection {
width: 100%;
margin-top: 10px;
}
.loading {
text-align: center;
padding: 20px;
opacity: 0.7;
}
.error {
background: #5a2a2a;
border: 2px solid #7a3a3a;
border-radius: 6px;
padding: 15px;
margin-bottom: 15px;
}
#system-prompt {
background: #1a1a1a;
border: 2px solid #3a3a3a;
border-radius: 6px;
padding: 12px;
color: #e0e0e0;
font-size: 13px;
font-family: 'Courier New', monospace;
width: 100%;
min-height: 100px;
resize: vertical;
}
.info-text {
font-size: 0.85em;
opacity: 0.7;
margin-top: 5px;
}
.github-link {
text-align: center;
margin-top: 20px;
opacity: 0.5;
font-size: 0.9em;
}
.github-link a {
color: #4a7c59;
text-decoration: none;
}
.github-link a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div id="container">
<h1>Multi-Response Chat</h1>
<div class="config-section">
<h3 style="margin-bottom: 15px;">Configuration</h3>
<div class="config-row">
<div class="config-field">
<label for="api-url">API URL:</label>
<input type="text" id="api-url" value="https://openrouter.ai/api/v1/chat/completions" placeholder="API endpoint URL">
</div>
<div class="config-field">
<label for="api-key">API Key:</label>
<input type="password" id="api-key" placeholder="sk-or-v1-...">
<div class="info-text">Your key is stored locally and never sent anywhere except the API</div>
</div>
</div>
<div class="config-row">
<div class="config-field">
<label for="model-name">Model:</label>
<select id="model-name">
<optgroup label="Anthropic">
<option value="anthropic/claude-sonnet-4.5">Claude Sonnet 4.5</option>
<option value="anthropic/claude-3.5-sonnet">Claude 3.5 Sonnet</option>
<option value="anthropic/claude-3-opus">Claude 3 Opus</option>
</optgroup>
<optgroup label="OpenAI">
<option value="openai/gpt-4-turbo">GPT-4 Turbo</option>
<option value="openai/gpt-4">GPT-4</option>
<option value="openai/gpt-3.5-turbo">GPT-3.5 Turbo</option>
</optgroup>
<optgroup label="Google">
<option value="google/gemini-pro-1.5">Gemini Pro 1.5</option>
<option value="google/gemini-pro">Gemini Pro</option>
</optgroup>
<optgroup label="Meta">
<option value="meta-llama/llama-3.1-70b-instruct">Llama 3.1 70B</option>
<option value="meta-llama/llama-3.1-8b-instruct">Llama 3.1 8B</option>
</optgroup>
<optgroup label="Other">
<option value="custom">Custom (edit below)</option>
</optgroup>
</select>
</div>
<div class="config-field">
<label for="custom-model">Custom Model ID:</label>
<input type="text" id="custom-model" placeholder="provider/model-name">
<div class="info-text">Used when "Custom" is selected above</div>
</div>
</div>
<div class="config-field">
<label for="system-prompt">System Prompt:</label>
<textarea id="system-prompt">You are a helpful assistant. For each query, please generate a set of five possible responses, each within a separate <response> tag. Responses should each include a <text> and a numeric <probability>. Please sample at random from the tails of the distribution, such that the probability of each response is less than 0.10.</textarea>
</div>
</div>
<div id="chat-history"></div>
<div id="responses-container"></div>
<div id="input-container">
<textarea id="user-input" placeholder="Type your message here..."></textarea>
<button id="send-btn" onclick="sendMessage()">Send</button>
</div>
<div class="github-link">
Multi-response sampling technique inspired by creative prompt engineering
</div>
</div>
<script>
let conversationHistory = [];
let currentResponses = [];
let selectedResponseIndex = null;
const chatHistory = document.getElementById('chat-history');
const responsesContainer = document.getElementById('responses-container');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const systemPromptEl = document.getElementById('system-prompt');
const apiUrlEl = document.getElementById('api-url');
const apiKeyEl = document.getElementById('api-key');
const modelNameEl = document.getElementById('model-name');
const customModelEl = document.getElementById('custom-model');
// Load saved config from localStorage
if (localStorage.getItem('apiKey')) {
apiKeyEl.value = localStorage.getItem('apiKey');
}
if (localStorage.getItem('apiUrl')) {
apiUrlEl.value = localStorage.getItem('apiUrl');
}
if (localStorage.getItem('modelName')) {
modelNameEl.value = localStorage.getItem('modelName');
}
if (localStorage.getItem('customModel')) {
customModelEl.value = localStorage.getItem('customModel');
}
// Save config to localStorage on change
apiKeyEl.addEventListener('change', () => localStorage.setItem('apiKey', apiKeyEl.value));
apiUrlEl.addEventListener('change', () => localStorage.setItem('apiUrl', apiUrlEl.value));
modelNameEl.addEventListener('change', () => localStorage.setItem('modelName', modelNameEl.value));
customModelEl.addEventListener('change', () => localStorage.setItem('customModel', customModelEl.value));
// Allow Enter to send (Shift+Enter for newline)
userInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
async function sendMessage() {
const message = userInput.value.trim();
if (!message) return;
const apiKey = apiKeyEl.value.trim();
const apiUrl = apiUrlEl.value.trim();
if (!apiKey) {
alert('Please enter your API key');
return;
}
if (!apiUrl) {
alert('Please enter the API URL');
return;
}
const model = modelNameEl.value === 'custom' ? customModelEl.value : modelNameEl.value;
if (!model) {
alert('Please select or enter a model');
return;
}
// Add user message to history
conversationHistory.push({
role: 'user',
content: message
});
displayMessage('user', message);
userInput.value = '';
sendBtn.disabled = true;
// Show loading
responsesContainer.innerHTML = '<div class="loading">Generating responses...</div>';
responsesContainer.style.display = 'block';
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'HTTP-Referer': window.location.href,
'X-Title': 'Multi-Response Chat'
},
body: JSON.stringify({
model: model,
messages: [
{
role: 'system',
content: systemPromptEl.value
},
...conversationHistory
]
})
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(`API error: ${response.status} ${response.statusText}${errorData.error ? ' - ' + JSON.stringify(errorData.error) : ''}`);
}
const data = await response.json();
const assistantMessage = data.choices[0].message.content;
// Parse responses
parseAndDisplayResponses(assistantMessage);
} catch (error) {
responsesContainer.innerHTML = `<div class="error">Error: ${error.message}</div>`;
sendBtn.disabled = false;
}
}
function parseAndDisplayResponses(content) {
// Extract response blocks
const responseRegex = /<response>\s*<text>([\s\S]*?)<\/text>\s*<probability>([\s\S]*?)<\/probability>\s*<\/response>/g;
const responses = [];
let match;
while ((match = responseRegex.exec(content)) !== null) {
responses.push({
text: match[1].trim(),
probability: match[2].trim()
});
}
if (responses.length === 0) {
// Fallback: treat entire response as single option
responses.push({
text: content,
probability: '1.0'
});
}
currentResponses = responses;
selectedResponseIndex = null;
// Display response options
responsesContainer.innerHTML = responses.map((resp, index) => `
<div class="response-option" onclick="selectResponse(${index})">
<div class="response-header">
<span>Response ${index + 1}</span>
<span>Probability: ${resp.probability}</span>
</div>
<div class="response-text">${escapeHtml(resp.text)}</div>
</div>
`).join('') + '<button id="confirm-selection" onclick="confirmSelection()" disabled>Confirm Selection</button>';
responsesContainer.style.display = 'block';
}
function selectResponse(index) {
selectedResponseIndex = index;
// Update visual selection
document.querySelectorAll('.response-option').forEach((el, i) => {
if (i === index) {
el.classList.add('selected');
} else {
el.classList.remove('selected');
}
});
document.getElementById('confirm-selection').disabled = false;
}
function confirmSelection() {
if (selectedResponseIndex === null) return;
const selectedResponse = currentResponses[selectedResponseIndex];
// Add to conversation history
conversationHistory.push({
role: 'assistant',
content: selectedResponse.text
});
// Display in chat
displayMessage('assistant', selectedResponse.text);
// Clear responses
responsesContainer.innerHTML = '';
responsesContainer.style.display = 'none';
currentResponses = [];
selectedResponseIndex = null;
sendBtn.disabled = false;
userInput.focus();
}
function displayMessage(role, content) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}`;
messageDiv.innerHTML = `
<div class="message-label">${role === 'user' ? 'You' : 'Assistant'}</div>
<div>${escapeHtml(content)}</div>
`;
chatHistory.appendChild(messageDiv);
chatHistory.scrollTop = chatHistory.scrollHeight;
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
</body>
</html>