ausername-12345
sync with sebebeb-relay.hf.space: fix API URL to /v1/chat/completions, add debug endpoint, restore OpenAI payload format
0a988dd | <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <title>InferencePort Relay</title> | |
| <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet"/> | |
| <style> | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| :root { | |
| --bg: #0f0f0f; | |
| --surface: #1a1a1a; | |
| --border: #2a2a2a; | |
| --accent: #7c6af7; | |
| --accent-dim: #3d3470; | |
| --text: #e8e8e8; | |
| --muted: #666; | |
| --user-bg: #1e1b38; | |
| --assistant-bg: #1a1a1a; | |
| --green: #4ade80; | |
| --red: #f87171; | |
| } | |
| body { | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: 'IBM Plex Sans', sans-serif; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 2rem 1rem; | |
| } | |
| header { | |
| width: 100%; | |
| max-width: 760px; | |
| margin-bottom: 2rem; | |
| } | |
| header h1 { | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| color: var(--accent); | |
| letter-spacing: 0.05em; | |
| } | |
| header p { | |
| font-size: 0.8rem; | |
| color: var(--muted); | |
| margin-top: 0.3rem; | |
| font-family: 'IBM Plex Mono', monospace; | |
| } | |
| .card { | |
| width: 100%; | |
| max-width: 760px; | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 6px; | |
| padding: 1.5rem; | |
| margin-bottom: 1.25rem; | |
| } | |
| label { | |
| display: block; | |
| font-size: 0.72rem; | |
| font-family: 'IBM Plex Mono', monospace; | |
| color: var(--muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| margin-bottom: 0.5rem; | |
| } | |
| textarea, input[type="text"] { | |
| width: 100%; | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| border-radius: 4px; | |
| color: var(--text); | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 0.82rem; | |
| padding: 0.75rem; | |
| resize: vertical; | |
| outline: none; | |
| transition: border-color 0.15s; | |
| } | |
| textarea:focus, input:focus { | |
| border-color: var(--accent); | |
| } | |
| #system-prompt { min-height: 120px; } | |
| .messages-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| margin-bottom: 0.75rem; | |
| } | |
| .msg-row { | |
| display: flex; | |
| gap: 0.5rem; | |
| align-items: flex-start; | |
| } | |
| .msg-row select { | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| color: var(--text); | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 0.78rem; | |
| padding: 0.55rem 0.5rem; | |
| border-radius: 4px; | |
| outline: none; | |
| flex-shrink: 0; | |
| cursor: pointer; | |
| } | |
| .msg-row input { | |
| flex: 1; | |
| padding: 0.55rem 0.65rem; | |
| } | |
| .btn-icon { | |
| background: none; | |
| border: 1px solid var(--border); | |
| color: var(--muted); | |
| border-radius: 4px; | |
| width: 32px; | |
| height: 32px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| flex-shrink: 0; | |
| margin-top: 2px; | |
| transition: border-color 0.15s, color 0.15s; | |
| } | |
| .btn-icon:hover { border-color: var(--red); color: var(--red); } | |
| .row-actions { | |
| display: flex; | |
| gap: 0.5rem; | |
| margin-top: 0.5rem; | |
| } | |
| .btn { | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 0.78rem; | |
| font-weight: 600; | |
| border-radius: 4px; | |
| padding: 0.5rem 1rem; | |
| cursor: pointer; | |
| border: none; | |
| transition: opacity 0.15s; | |
| } | |
| .btn:hover { opacity: 0.85; } | |
| .btn:disabled { opacity: 0.4; cursor: not-allowed; } | |
| .btn-add { | |
| background: var(--border); | |
| color: var(--muted); | |
| } | |
| .btn-send { | |
| background: var(--accent); | |
| color: #fff; | |
| margin-left: auto; | |
| } | |
| .send-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| margin-top: 0.5rem; | |
| } | |
| .trim-note { | |
| font-size: 0.72rem; | |
| font-family: 'IBM Plex Mono', monospace; | |
| color: var(--muted); | |
| } | |
| .response-card { | |
| width: 100%; | |
| max-width: 760px; | |
| border: 1px solid var(--accent-dim); | |
| border-radius: 6px; | |
| background: #13112b; | |
| padding: 1.25rem 1.5rem; | |
| display: none; | |
| } | |
| .response-card.visible { display: block; } | |
| .response-label { | |
| font-size: 0.68rem; | |
| font-family: 'IBM Plex Mono', monospace; | |
| color: var(--accent); | |
| text-transform: uppercase; | |
| letter-spacing: 0.1em; | |
| margin-bottom: 0.75rem; | |
| } | |
| #reply-text { | |
| font-family: 'IBM Plex Mono', monospace; | |
| font-size: 0.9rem; | |
| line-height: 1.6; | |
| color: var(--text); | |
| white-space: pre-wrap; | |
| } | |
| .error { | |
| border-color: #5c2020; | |
| background: #1a0e0e; | |
| } | |
| .error .response-label { color: var(--red); } | |
| #reply-text.err { color: var(--red); } | |
| .spinner { | |
| width: 14px; height: 14px; | |
| border: 2px solid var(--accent-dim); | |
| border-top-color: var(--accent); | |
| border-radius: 50%; | |
| animation: spin 0.6s linear infinite; | |
| display: none; | |
| } | |
| .spinner.active { display: inline-block; } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>// inferenceport relay</h1> | |
| <p>sends trimmed conversation + system prompt → api → reply</p> | |
| </header> | |
| <div class="card"> | |
| <label>System Prompt</label> | |
| <textarea id="system-prompt">Respond to the following messages casually and concisely, with slight humor, in the style of a male american teenager: do not include quotation marks, and respond with lowercase capitalization, and use abbreviations commonly used by the american youth. Use from one word to 1 sentence and try not to repeat yourself too much with emojis or expressions like "bro"</textarea> | |
| </div> | |
| <div class="card"> | |
| <label>Conversation History</label> | |
| <div class="messages-list" id="messages-list"></div> | |
| <div class="row-actions"> | |
| <button class="btn btn-add" onclick="addMessage('user')">+ user</button> | |
| <button class="btn btn-add" onclick="addMessage('assistant')">+ assistant</button> | |
| </div> | |
| </div> | |
| <div style="width:100%;max-width:760px;"> | |
| <div class="send-row"> | |
| <span class="trim-note" id="trim-note"></span> | |
| <div class="spinner" id="spinner"></div> | |
| <button class="btn btn-send" id="send-btn" onclick="sendChat()">send →</button> | |
| </div> | |
| </div> | |
| <div class="response-card" id="response-card"> | |
| <div class="response-label" id="response-label">reply</div> | |
| <div id="reply-text"></div> | |
| </div> | |
| <script> | |
| const DEFAULT_MESSAGES = [ | |
| { role: "user", content: "hi" }, | |
| { role: "assistant", content: "yo wsp bro how's it going" }, | |
| { role: "user", content: "good hbu" }, | |
| { role: "assistant", content: "good as well" }, | |
| ]; | |
| function renderMessages() { | |
| const list = document.getElementById("messages-list"); | |
| list.innerHTML = ""; | |
| getMessages().forEach((m, i) => { | |
| const row = document.createElement("div"); | |
| row.className = "msg-row"; | |
| row.innerHTML = ` | |
| <select onchange="updateRole(${i}, this.value)"> | |
| <option value="user" ${m.role === "user" ? "selected" : ""}>user</option> | |
| <option value="assistant" ${m.role === "assistant" ? "selected" : ""}>asst</option> | |
| </select> | |
| <input type="text" value="${escHtml(m.content)}" oninput="updateContent(${i}, this.value)" placeholder="message content" /> | |
| <button class="btn-icon" onclick="removeMessage(${i})" title="remove"> | |
| <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <line x1="1" y1="1" x2="11" y2="11" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/> | |
| <line x1="11" y1="1" x2="1" y2="11" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/> | |
| </svg> | |
| </button>`; | |
| list.appendChild(row); | |
| }); | |
| updateTrimNote(); | |
| } | |
| function escHtml(s) { | |
| return s.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">"); | |
| } | |
| function getMessages() { | |
| return window._msgs || (window._msgs = JSON.parse(JSON.stringify(DEFAULT_MESSAGES))); | |
| } | |
| function updateRole(i, val) { getMessages()[i].role = val; updateTrimNote(); } | |
| function updateContent(i, val) { getMessages()[i].content = val; } | |
| function addMessage(role) { | |
| getMessages().push({ role, content: "" }); | |
| renderMessages(); | |
| } | |
| function removeMessage(i) { | |
| getMessages().splice(i, 1); | |
| renderMessages(); | |
| } | |
| function updateTrimNote() { | |
| const msgs = getMessages(); | |
| const pairs = Math.floor(msgs.filter(m => m.role === "user").length); | |
| const kept = Math.min(pairs, 3); | |
| document.getElementById("trim-note").textContent = | |
| `${msgs.length} messages → trimmed to last ${kept} pair${kept !== 1 ? "s" : ""} on send`; | |
| } | |
| async function sendChat() { | |
| const system = document.getElementById("system-prompt").value.trim(); | |
| const messages = getMessages(); | |
| const btn = document.getElementById("send-btn"); | |
| const spinner = document.getElementById("spinner"); | |
| const card = document.getElementById("response-card"); | |
| const label = document.getElementById("response-label"); | |
| const replyEl = document.getElementById("reply-text"); | |
| btn.disabled = true; | |
| spinner.classList.add("active"); | |
| card.className = "response-card visible"; | |
| label.textContent = "sending..."; | |
| replyEl.textContent = ""; | |
| replyEl.className = ""; | |
| try { | |
| const res = await fetch("/relay", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ system, messages }), | |
| }); | |
| const data = await res.json(); | |
| if (!res.ok) throw new Error(data.detail || res.statusText); | |
| label.textContent = `reply · ${data.trimmed_message_count} msgs sent`; | |
| replyEl.textContent = data.reply; | |
| card.classList.remove("error"); | |
| } catch (err) { | |
| card.classList.add("error"); | |
| label.textContent = "error"; | |
| replyEl.textContent = err.message; | |
| replyEl.className = "err"; | |
| } finally { | |
| btn.disabled = false; | |
| spinner.classList.remove("active"); | |
| } | |
| } | |
| renderMessages(); | |
| </script> | |
| </body> | |
| </html> | |