vision_llm_agent / static /openai-chat.html
David Ko
build(frontend): copy flattened CRA build to backend static (fix 404 for /static/js/*); translate UI to English
1fb1877
raw
history blame
4.46 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpenAI Chat UI</title>
<style>
:root { color-scheme: light dark; }
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, 'Apple Color Emoji', 'Segoe UI Emoji'; margin: 0; padding: 20px; }
.container { max-width: 960px; margin: 0 auto; }
h1 { font-size: 1.6rem; margin: 0 0 1rem; }
.card { border: 1px solid #4443; border-radius: 12px; padding: 16px; margin: 12px 0; background: #00000008; }
label { display: block; margin: 8px 0 4px; font-weight: 600; }
input[type="text"], input[type="password"], textarea, select { width: 100%; padding: 10px; border-radius: 8px; border: 1px solid #6664; background: transparent; color: inherit; }
textarea { min-height: 120px; resize: vertical; }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.actions { display: flex; gap: 8px; margin-top: 12px; }
button { padding: 10px 16px; border-radius: 8px; border: 1px solid #4446; background: #2d6cdf; color: #fff; cursor: pointer; }
button.secondary { background: #666; }
.log { white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; }
.muted { opacity: .8; font-size: .9rem; }
.header { display:flex; align-items:center; justify-content: space-between; gap: 8px; }
a { color: #2d6cdf; text-decoration: none; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>OpenAI Chat</h1>
<div>
<a href="/index.html">Home</a>
</div>
</div>
<div class="card">
<div class="row">
<div>
<label for="model">Model</label>
<input id="model" type="text" value="gpt-4o-mini" />
</div>
<div>
<label for="apiKey">OpenAI API Key (optional)</label>
<input id="apiKey" type="password" placeholder="sk-... (OPENAI_API_KEY env var supported)" />
</div>
</div>
<label for="system">System Prompt (optional)</label>
<input id="system" type="text" placeholder="You are a helpful assistant." />
<label for="prompt">User Question</label>
<textarea id="prompt" placeholder="Type your question here"></textarea>
<div class="actions">
<button id="sendBtn">Send Question</button>
<button class="secondary" id="clearBtn">Clear</button>
</div>
</div>
<div class="card">
<div class="muted">Response</div>
<div id="response" class="log"></div>
</div>
</div>
<script>
const $ = (id) => document.getElementById(id);
const responseEl = $('response');
function setLoading(on) {
$('sendBtn').disabled = on;
$('sendBtn').textContent = on ? 'Sending...' : 'Send Question';
}
$('clearBtn').addEventListener('click', () => {
$('prompt').value = '';
responseEl.textContent = '';
});
$('sendBtn').addEventListener('click', async () => {
const prompt = $('prompt').value.trim();
const model = $('model').value.trim() || 'gpt-4o-mini';
const api_key = $('apiKey').value.trim();
const system = $('system').value.trim();
if (!prompt) {
alert('Please enter a question.');
return;
}
setLoading(true);
responseEl.textContent = '';
try {
const res = await fetch('/api/openai/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ prompt, model, api_key: api_key || undefined, system: system || undefined })
});
if (!res.ok) {
let errTxt = await res.text();
try { errTxt = JSON.stringify(JSON.parse(errTxt), null, 2); } catch {}
throw new Error(errTxt);
}
const data = await res.json();
const { response, model: usedModel, usage, latency_sec } = data;
const meta = `Model: ${usedModel} | Latency: ${latency_sec}s` + (usage ? ` | Usage: ${JSON.stringify(usage)}` : '');
responseEl.textContent = response ? (response + '\n\n---\n' + meta) : '(Empty response)\n' + meta;
} catch (e) {
responseEl.textContent = 'Error: ' + e.message;
} finally {
setLoading(false);
}
});
</script>
</body>
</html>