Spaces:
Running
Running
| <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> |