opencode / index.html
abbasyk60's picture
Update to Qwen2.5-Coder-3B
a69619e
Raw
History Blame Contribute Delete
14.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenCode AI Agent</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #fff; height: 100vh; display: flex; flex-direction: column; }
.header { background: #16213e; padding: 15px 20px; border-bottom: 1px solid #0f3460; }
.header h1 { font-size: 1.4em; color: #e94560; }
.header p { font-size: 0.85em; color: #888; margin-top: 4px; }
.model-select { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.model-select label { font-size: 0.9em; color: #aaa; }
.model-select select { background: #0f3460; color: #fff; border: 1px solid #e94560; padding: 6px 12px; border-radius: 6px; font-size: 0.9em; }
.chat-container { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 12px; }
.message { max-width: 80%; padding: 12px 16px; border-radius: 12px; line-height: 1.5; font-size: 0.95em; white-space: pre-wrap; word-wrap: break-word; }
.message.user { align-self: flex-end; background: #e94560; color: #fff; border-bottom-right-radius: 4px; }
.message.bot { align-self: flex-start; background: #16213e; color: #ddd; border-bottom-left-radius: 4px; border: 1px solid #0f3460; }
.message.error { align-self: flex-start; background: #5c1a1a; color: #ff6b6b; }
.input-area { padding: 15px 20px; background: #16213e; border-top: 1px solid #0f3460; display: flex; gap: 10px; }
.input-area textarea { flex: 1; background: #0f3460; color: #fff; border: 1px solid #333; border-radius: 8px; padding: 12px; font-size: 0.95em; resize: none; font-family: inherit; }
.input-area textarea:focus { outline: none; border-color: #e94560; }
.input-area button { background: #e94560; color: #fff; border: none; border-radius: 8px; padding: 12px 24px; font-size: 0.95em; cursor: pointer; font-weight: 600; }
.input-area button:hover { background: #c73650; }
.input-area button:disabled { background: #555; cursor: not-allowed; }
.api-key-area { padding: 10px 20px; background: #0d1117; border-top: 1px solid #333; }
.api-key-area input { width: 100%; background: #161b22; color: #c9d1d9; border: 1px solid #30363d; border-radius: 6px; padding: 8px 12px; font-size: 0.85em; font-family: monospace; }
.api-key-area input::placeholder { color: #484f58; }
.loading { display: inline-block; }
.loading::after { content: ''; animation: dots 1.5s infinite; }
@keyframes dots { 0%, 20% { content: '.'; } 40% { content: '..'; } 60%, 100% { content: '...'; } }
.free-tag { background: #2ea043; color: #fff; font-size: 0.7em; padding: 2px 6px; border-radius: 4px; margin-left: 6px; }
</style>
</head>
<body>
<div class="header">
<h1>🤖 OpenCode AI Agent</h1>
<p>Chat with AI models powered by OpenCode Zen + Local DeepSeek R1 7B</p>
<div class="model-select">
<label>Model:</label>
<select id="model-select">
<optgroup label="GPT Models">
<option value="gpt-5.6-sol">GPT 5.6 Sol</option>
<option value="gpt-5.6-terra">GPT 5.6 Terra</option>
<option value="gpt-5.6-luna">GPT 5.6 Luna</option>
<option value="gpt-5.5" selected>GPT 5.5</option>
<option value="gpt-5.5-pro">GPT 5.5 Pro</option>
<option value="gpt-5.4">GPT 5.4</option>
<option value="gpt-5.4-pro">GPT 5.4 Pro</option>
<option value="gpt-5.4-mini">GPT 5.4 Mini</option>
<option value="gpt-5.4-nano">GPT 5.4 Nano</option>
<option value="gpt-5.3-codex">GPT 5.3 Codex</option>
<option value="gpt-5.3-codex-spark">GPT 5.3 Codex Spark</option>
<option value="gpt-5.2">GPT 5.2</option>
<option value="gpt-5.1">GPT 5.1</option>
<option value="gpt-5">GPT 5</option>
<option value="gpt-5-nano">GPT 5 Nano</option>
</optgroup>
<optgroup label="Claude Models">
<option value="claude-fable-5">Claude Fable 5</option>
<option value="claude-opus-5">Claude Opus 5</option>
<option value="claude-opus-4-8">Claude Opus 4.8</option>
<option value="claude-opus-4-7">Claude Opus 4.7</option>
<option value="claude-opus-4-5">Claude Opus 4.5</option>
<option value="claude-sonnet-5">Claude Sonnet 5</option>
<option value="claude-sonnet-4-6">Claude Sonnet 4.6</option>
<option value="claude-sonnet-4-5">Claude Sonnet 4.5</option>
<option value="claude-haiku-4-5">Claude Haiku 4.5</option>
</optgroup>
<optgroup label="Gemini Models">
<option value="gemini-3.6-flash">Gemini 3.6 Flash</option>
<option value="gemini-3.5-flash">Gemini 3.5 Flash</option>
<option value="gemini-3.5-flash-lite">Gemini 3.5 Flash Lite</option>
<option value="gemini-3.1-pro">Gemini 3.1 Pro</option>
<option value="gemini-3-flash">Gemini 3 Flash</option>
</optgroup>
<optgroup label="Other Models">
<option value="grok-4.5">Grok 4.5</option>
<option value="qwen3.7-max">Qwen3.7 Max</option>
<option value="qwen3.7-plus">Qwen3.7 Plus</option>
<option value="deepseek-v4-pro">DeepSeek V4 Pro</option>
<option value="deepseek-v4-flash">DeepSeek V4 Flash</option>
<option value="kimi-k2.7-code">Kimi K2.7 Code</option>
<option value="minimax-m3">MiniMax M3</option>
<option value="glm-5.2">GLM 5.2</option>
</optgroup>
<optgroup label="Local Models (Your Server)">
<option value="bartowski/Qwen2.5-Coder-3B-Instruct-GGUF">Qwen2.5-Coder-3B (Local) <span class="free-tag">FREE</span></option>
</optgroup>
<optgroup label="Free Models">
<option value="deepseek-v4-flash-free">DeepSeek V4 Flash Free <span class="free-tag">FREE</span></option>
<option value="mimo-v2.5-free">MiMo-V2.5 Free <span class="free-tag">FREE</span></option>
<option value="big-pickle">Big Pickle Free <span class="free-tag">FREE</span></option>
<option value="laguna-s-2.1-free">Laguna S 2.1 Free <span class="free-tag">FREE</span></option>
<option value="ling-3.0-flash-free">Ling-3.0 Flash Free <span class="free-tag">FREE</span></option>
<option value="north-mini-code-free">North Mini Code Free <span class="free-tag">FREE</span></option>
<option value="nemotron-3-ultra-free">Nemotron 3 Ultra Free <span class="free-tag">FREE</span></option>
</optgroup>
</select>
</div>
</div>
<div class="chat-container" id="chat"></div>
<div class="api-key-area">
<input type="password" id="api-key" placeholder="Enter your OpenCode Zen API key (sk-...)" />
</div>
<div class="input-area">
<textarea id="user-input" placeholder="Type your message..." rows="2"></textarea>
<button id="send-btn" onclick="sendMessage()">Send</button>
</div>
<script>
const chatEl = document.getElementById('chat');
const inputEl = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const apiKeyEl = document.getElementById('api-key');
const modelSelect = document.getElementById('model-select');
let history = [];
// Load saved API key
const savedKey = localStorage.getItem('opencode_zen_key');
if (savedKey) apiKeyEl.value = savedKey;
inputEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
});
function addMessage(role, content, isError = false) {
const div = document.createElement('div');
div.className = `message ${role}${isError ? ' error' : ''}`;
div.textContent = content;
chatEl.appendChild(div);
chatEl.scrollTop = chatEl.scrollHeight;
}
function addLoading() {
const div = document.createElement('div');
div.className = 'message bot';
div.id = 'loading';
div.innerHTML = '<span class="loading">Thinking</span>';
chatEl.appendChild(div);
chatEl.scrollTop = chatEl.scrollHeight;
}
function removeLoading() {
const el = document.getElementById('loading');
if (el) el.remove();
}
const LOCAL_API = 'https://abbasyk60-ai-telegram-bot.hf.space/v1/chat/completions';
const LOCAL_MODEL = 'bartowski/Qwen2.5-Coder-3B-Instruct-GGUF';
function getEndpoint(modelId) {
if (modelId === LOCAL_MODEL) return 'local';
const openaiPrefixes = ['gpt-', 'deepseek-', 'grok-', 'minimax-', 'glm-', 'kimi-', 'big-pickle', 'mimo-', 'laguna-', 'ling-', 'north-', 'nemotron-'];
if (openaiPrefixes.some(p => modelId.startsWith(p) || modelId.includes(p))) {
return 'https://opencode.ai/zen/v1/chat/completions';
}
if (modelId.startsWith('claude-') || modelId.startsWith('qwen3')) {
return 'https://opencode.ai/zen/v1/messages';
}
if (modelId.startsWith('gemini-')) {
return 'https://opencode.ai/zen/v1/models/' + modelId;
}
return 'https://opencode.ai/zen/v1/chat/completions';
}
async function sendMessage() {
const message = inputEl.value.trim();
const apiKey = apiKeyEl.value.trim();
const modelId = modelSelect.value;
const isLocal = modelId === LOCAL_MODEL;
if (!isLocal && !apiKey) { addMessage('error', 'Please enter your OpenCode Zen API key first. (Not needed for local model)'); return; }
if (!message) return;
if (!isLocal) localStorage.setItem('opencode_zen_key', apiKey);
addMessage('user', message);
inputEl.value = '';
sendBtn.disabled = true;
addLoading();
const endpoint = getEndpoint(modelId);
const messages = [...history, { role: 'user', content: message }];
try {
let response, data;
if (isLocal) {
response = await fetch(LOCAL_API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: LOCAL_MODEL, messages: messages, max_tokens: 4096 })
});
} else if (endpoint.includes('/messages')) {
response = await fetch(endpoint, {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01' },
body: JSON.stringify({ model: modelId, messages: messages, max_tokens: 4096 })
});
} else if (endpoint.includes('/models/')) {
response = await fetch(endpoint, {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ contents: [{ parts: [{ text: message }] }] })
});
} else {
response = await fetch(endpoint, {
method: 'POST',
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model: modelId, messages: messages, max_tokens: 4096 })
});
}
removeLoading();
if (!response.ok) {
const errText = await response.text();
addMessage('error', `API Error ${response.status}: ${errText.substring(0, 500)}`);
sendBtn.disabled = false;
return;
}
data = await response.json();
let botMessage = '';
if (data.choices && data.choices[0]) {
const msg = data.choices[0].message;
botMessage = msg.content || msg.reasoning_content || '';
} else if (endpoint.includes('/messages') && data.content) {
botMessage = data.content.map(c => c.text || '').join('');
} else if (data.candidates && data.candidates[0]) {
botMessage = data.candidates[0].content.parts.map(p => p.text).join('');
} else {
botMessage = JSON.stringify(data, null, 2).substring(0, 2000);
}
if (!botMessage) botMessage = '(empty response - try again)';
addMessage('bot', botMessage);
history.push({ role: 'user', content: message });
history.push({ role: 'assistant', content: botMessage });
// Keep history manageable
if (history.length > 20) history = history.slice(-20);
} catch (err) {
removeLoading();
addMessage('error', `Error: ${err.message}`);
}
sendBtn.disabled = false;
inputEl.focus();
}
</script>
</body>
</html>