Switch to static Space with HTML chat UI
Browse files- .gitignore +2 -3
- Dockerfile +0 -43
- README.md +3 -24
- entrypoint.sh +0 -75
- index.html +241 -0
- opencode.json +0 -73
.gitignore
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
.env
|
| 4 |
-
*.log
|
| 5 |
.DS_Store
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
.env
|
|
|
|
| 4 |
.DS_Store
|
Dockerfile
DELETED
|
@@ -1,43 +0,0 @@
|
|
| 1 |
-
FROM node:20-slim
|
| 2 |
-
|
| 3 |
-
# Install system dependencies
|
| 4 |
-
RUN apt-get update && apt-get install -y \
|
| 5 |
-
git \
|
| 6 |
-
curl \
|
| 7 |
-
procps \
|
| 8 |
-
lsof \
|
| 9 |
-
python3 \
|
| 10 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 11 |
-
|
| 12 |
-
# Install OpenCode
|
| 13 |
-
RUN npm install -g opencode-ai
|
| 14 |
-
|
| 15 |
-
# Create app directory
|
| 16 |
-
WORKDIR /app
|
| 17 |
-
|
| 18 |
-
# Copy package.json and install dependencies
|
| 19 |
-
COPY package.json ./
|
| 20 |
-
RUN npm install --production
|
| 21 |
-
|
| 22 |
-
# Copy OpenCode config
|
| 23 |
-
COPY opencode.json /root/.config/opencode/opencode.json
|
| 24 |
-
|
| 25 |
-
# Copy bot code
|
| 26 |
-
COPY bot.js ./
|
| 27 |
-
|
| 28 |
-
# Create entrypoint script
|
| 29 |
-
COPY entrypoint.sh /entrypoint.sh
|
| 30 |
-
RUN chmod +x /entrypoint.sh
|
| 31 |
-
|
| 32 |
-
# Create data directories
|
| 33 |
-
RUN mkdir -p /app/data /root/.local/share/opencode
|
| 34 |
-
|
| 35 |
-
# Expose port 7860 for HF Spaces
|
| 36 |
-
EXPOSE 7860
|
| 37 |
-
|
| 38 |
-
# Health check
|
| 39 |
-
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
| 40 |
-
CMD curl -f http://localhost:7860/ || exit 1
|
| 41 |
-
|
| 42 |
-
# Run entrypoint
|
| 43 |
-
ENTRYPOINT ["/entrypoint.sh"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -3,31 +3,10 @@ title: OpenCode AI Agent
|
|
| 3 |
emoji: 🤖
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
-
sdk:
|
| 7 |
-
app_port: 7860
|
| 8 |
pinned: true
|
| 9 |
---
|
| 10 |
|
| 11 |
-
# OpenCode AI Agent
|
| 12 |
|
| 13 |
-
AI coding agent with Web UI
|
| 14 |
-
|
| 15 |
-
## Features
|
| 16 |
-
|
| 17 |
-
- **Web UI**: Access OpenCode directly in your browser
|
| 18 |
-
- **Telegram Bot**: Chat with OpenCode via Telegram
|
| 19 |
-
- **Multiple Models**: GPT-5, Claude, Gemini, DeepSeek and more
|
| 20 |
-
- **Free Models Available**: DeepSeek V4 Flash Free, MiMo-V2.5 Free
|
| 21 |
-
|
| 22 |
-
## Access
|
| 23 |
-
|
| 24 |
-
- **Web UI**: [https://abbasyk60-opencode.hf.space](https://abbasyk60-opencode.hf.space)
|
| 25 |
-
- **Telegram**: Send messages to your bot in the configured supergroup
|
| 26 |
-
|
| 27 |
-
## Configuration
|
| 28 |
-
|
| 29 |
-
Environment variables (set in HF Space secrets):
|
| 30 |
-
|
| 31 |
-
- `OPENCODE_ZEN_KEY` - OpenCode Zen API key
|
| 32 |
-
- `TELEGRAM_BOT_TOKEN` - Telegram bot token
|
| 33 |
-
- `TELEGRAM_CHAT_ID` - Telegram supergroup chat ID
|
|
|
|
| 3 |
emoji: 🤖
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
+
sdk: static
|
|
|
|
| 7 |
pinned: true
|
| 8 |
---
|
| 9 |
|
| 10 |
+
# OpenCode AI Agent
|
| 11 |
|
| 12 |
+
AI coding agent with Web UI powered by OpenCode Zen.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
entrypoint.sh
DELETED
|
@@ -1,75 +0,0 @@
|
|
| 1 |
-
#!/bin/bash
|
| 2 |
-
set -e
|
| 3 |
-
|
| 4 |
-
echo "========================================="
|
| 5 |
-
echo " OpenCode AI Agent - Starting"
|
| 6 |
-
echo "========================================="
|
| 7 |
-
|
| 8 |
-
# Create OpenCode config directory
|
| 9 |
-
mkdir -p /root/.config/opencode
|
| 10 |
-
|
| 11 |
-
# Write OpenCode config if OPENCODE_ZEN_KEY is provided
|
| 12 |
-
if [ -n "$OPENCODE_ZEN_KEY" ]; then
|
| 13 |
-
cat > /root/.config/opencode/opencode.json <<EOF
|
| 14 |
-
{
|
| 15 |
-
"\$schema": "https://opencode.ai/config.json",
|
| 16 |
-
"model": "opencode/gpt-5.4",
|
| 17 |
-
"small_model": "opencode/gpt-5-nano",
|
| 18 |
-
"provider": {
|
| 19 |
-
"opencode": {
|
| 20 |
-
"npm": "@ai-sdk/openai-compatible",
|
| 21 |
-
"name": "OpenCode Zen",
|
| 22 |
-
"options": {
|
| 23 |
-
"baseURL": "https://opencode.ai/zen/v1",
|
| 24 |
-
"apiKey": "$OPENCODE_ZEN_KEY"
|
| 25 |
-
}
|
| 26 |
-
}
|
| 27 |
-
},
|
| 28 |
-
"server": {
|
| 29 |
-
"port": 7860,
|
| 30 |
-
"hostname": "0.0.0.0"
|
| 31 |
-
}
|
| 32 |
-
}
|
| 33 |
-
EOF
|
| 34 |
-
echo "[OK] OpenCode config written"
|
| 35 |
-
else
|
| 36 |
-
echo "[WARN] OPENCODE_ZEN_KEY not set - using bundled config"
|
| 37 |
-
cp /app/opencode.json /root/.config/opencode/opencode.json 2>/dev/null || true
|
| 38 |
-
fi
|
| 39 |
-
|
| 40 |
-
# Create data directories
|
| 41 |
-
mkdir -p /root/.local/share/opencode
|
| 42 |
-
mkdir -p /app/data
|
| 43 |
-
|
| 44 |
-
# Start OpenCode web server in background
|
| 45 |
-
echo "[OK] Starting OpenCode web server on port 7860..."
|
| 46 |
-
opencode web --port 7860 --hostname 0.0.0.0 &
|
| 47 |
-
OPENCODE_PID=$!
|
| 48 |
-
|
| 49 |
-
# Wait for OpenCode to be ready
|
| 50 |
-
echo "[OK] Waiting for OpenCode to start..."
|
| 51 |
-
for i in $(seq 1 30); do
|
| 52 |
-
if curl -s http://localhost:7860 > /dev/null 2>&1; then
|
| 53 |
-
echo "[OK] OpenCode is ready!"
|
| 54 |
-
break
|
| 55 |
-
fi
|
| 56 |
-
sleep 2
|
| 57 |
-
done
|
| 58 |
-
|
| 59 |
-
# Start Telegram bot if token is provided
|
| 60 |
-
if [ -n "$TELEGRAM_BOT_TOKEN" ]; then
|
| 61 |
-
echo "[OK] Starting Telegram bot..."
|
| 62 |
-
node /app/bot.js &
|
| 63 |
-
TELEGRAM_PID=$!
|
| 64 |
-
echo "[OK] Telegram bot started!"
|
| 65 |
-
else
|
| 66 |
-
echo "[WARN] TELEGRAM_BOT_TOKEN not set - Telegram bot disabled"
|
| 67 |
-
fi
|
| 68 |
-
|
| 69 |
-
echo "========================================="
|
| 70 |
-
echo " All services started!"
|
| 71 |
-
echo " Web UI: http://localhost:7860"
|
| 72 |
-
echo "========================================="
|
| 73 |
-
|
| 74 |
-
# Keep the container running
|
| 75 |
-
wait -n $OPENCODE_PID ${TELEGRAM_PID:-}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index.html
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>OpenCode AI Agent</title>
|
| 7 |
+
<style>
|
| 8 |
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 9 |
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #fff; height: 100vh; display: flex; flex-direction: column; }
|
| 10 |
+
.header { background: #16213e; padding: 15px 20px; border-bottom: 1px solid #0f3460; }
|
| 11 |
+
.header h1 { font-size: 1.4em; color: #e94560; }
|
| 12 |
+
.header p { font-size: 0.85em; color: #888; margin-top: 4px; }
|
| 13 |
+
.model-select { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
|
| 14 |
+
.model-select label { font-size: 0.9em; color: #aaa; }
|
| 15 |
+
.model-select select { background: #0f3460; color: #fff; border: 1px solid #e94560; padding: 6px 12px; border-radius: 6px; font-size: 0.9em; }
|
| 16 |
+
.chat-container { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 12px; }
|
| 17 |
+
.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; }
|
| 18 |
+
.message.user { align-self: flex-end; background: #e94560; color: #fff; border-bottom-right-radius: 4px; }
|
| 19 |
+
.message.bot { align-self: flex-start; background: #16213e; color: #ddd; border-bottom-left-radius: 4px; border: 1px solid #0f3460; }
|
| 20 |
+
.message.error { align-self: flex-start; background: #5c1a1a; color: #ff6b6b; }
|
| 21 |
+
.input-area { padding: 15px 20px; background: #16213e; border-top: 1px solid #0f3460; display: flex; gap: 10px; }
|
| 22 |
+
.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; }
|
| 23 |
+
.input-area textarea:focus { outline: none; border-color: #e94560; }
|
| 24 |
+
.input-area button { background: #e94560; color: #fff; border: none; border-radius: 8px; padding: 12px 24px; font-size: 0.95em; cursor: pointer; font-weight: 600; }
|
| 25 |
+
.input-area button:hover { background: #c73650; }
|
| 26 |
+
.input-area button:disabled { background: #555; cursor: not-allowed; }
|
| 27 |
+
.api-key-area { padding: 10px 20px; background: #0d1117; border-top: 1px solid #333; }
|
| 28 |
+
.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; }
|
| 29 |
+
.api-key-area input::placeholder { color: #484f58; }
|
| 30 |
+
.loading { display: inline-block; }
|
| 31 |
+
.loading::after { content: ''; animation: dots 1.5s infinite; }
|
| 32 |
+
@keyframes dots { 0%, 20% { content: '.'; } 40% { content: '..'; } 60%, 100% { content: '...'; } }
|
| 33 |
+
.free-tag { background: #2ea043; color: #fff; font-size: 0.7em; padding: 2px 6px; border-radius: 4px; margin-left: 6px; }
|
| 34 |
+
</style>
|
| 35 |
+
</head>
|
| 36 |
+
<body>
|
| 37 |
+
<div class="header">
|
| 38 |
+
<h1>🤖 OpenCode AI Agent</h1>
|
| 39 |
+
<p>Chat with AI models powered by OpenCode Zen</p>
|
| 40 |
+
<div class="model-select">
|
| 41 |
+
<label>Model:</label>
|
| 42 |
+
<select id="model-select">
|
| 43 |
+
<optgroup label="GPT Models">
|
| 44 |
+
<option value="gpt-5.6-sol">GPT 5.6 Sol</option>
|
| 45 |
+
<option value="gpt-5.6-terra">GPT 5.6 Terra</option>
|
| 46 |
+
<option value="gpt-5.6-luna">GPT 5.6 Luna</option>
|
| 47 |
+
<option value="gpt-5.5" selected>GPT 5.5</option>
|
| 48 |
+
<option value="gpt-5.5-pro">GPT 5.5 Pro</option>
|
| 49 |
+
<option value="gpt-5.4">GPT 5.4</option>
|
| 50 |
+
<option value="gpt-5.4-pro">GPT 5.4 Pro</option>
|
| 51 |
+
<option value="gpt-5.4-mini">GPT 5.4 Mini</option>
|
| 52 |
+
<option value="gpt-5.4-nano">GPT 5.4 Nano</option>
|
| 53 |
+
<option value="gpt-5.3-codex">GPT 5.3 Codex</option>
|
| 54 |
+
<option value="gpt-5.3-codex-spark">GPT 5.3 Codex Spark</option>
|
| 55 |
+
<option value="gpt-5.2">GPT 5.2</option>
|
| 56 |
+
<option value="gpt-5.1">GPT 5.1</option>
|
| 57 |
+
<option value="gpt-5">GPT 5</option>
|
| 58 |
+
<option value="gpt-5-nano">GPT 5 Nano</option>
|
| 59 |
+
</optgroup>
|
| 60 |
+
<optgroup label="Claude Models">
|
| 61 |
+
<option value="claude-fable-5">Claude Fable 5</option>
|
| 62 |
+
<option value="claude-opus-5">Claude Opus 5</option>
|
| 63 |
+
<option value="claude-opus-4-8">Claude Opus 4.8</option>
|
| 64 |
+
<option value="claude-opus-4-7">Claude Opus 4.7</option>
|
| 65 |
+
<option value="claude-opus-4-5">Claude Opus 4.5</option>
|
| 66 |
+
<option value="claude-sonnet-5">Claude Sonnet 5</option>
|
| 67 |
+
<option value="claude-sonnet-4-6">Claude Sonnet 4.6</option>
|
| 68 |
+
<option value="claude-sonnet-4-5">Claude Sonnet 4.5</option>
|
| 69 |
+
<option value="claude-haiku-4-5">Claude Haiku 4.5</option>
|
| 70 |
+
</optgroup>
|
| 71 |
+
<optgroup label="Gemini Models">
|
| 72 |
+
<option value="gemini-3.6-flash">Gemini 3.6 Flash</option>
|
| 73 |
+
<option value="gemini-3.5-flash">Gemini 3.5 Flash</option>
|
| 74 |
+
<option value="gemini-3.5-flash-lite">Gemini 3.5 Flash Lite</option>
|
| 75 |
+
<option value="gemini-3.1-pro">Gemini 3.1 Pro</option>
|
| 76 |
+
<option value="gemini-3-flash">Gemini 3 Flash</option>
|
| 77 |
+
</optgroup>
|
| 78 |
+
<optgroup label="Other Models">
|
| 79 |
+
<option value="grok-4.5">Grok 4.5</option>
|
| 80 |
+
<option value="qwen3.7-max">Qwen3.7 Max</option>
|
| 81 |
+
<option value="qwen3.7-plus">Qwen3.7 Plus</option>
|
| 82 |
+
<option value="deepseek-v4-pro">DeepSeek V4 Pro</option>
|
| 83 |
+
<option value="deepseek-v4-flash">DeepSeek V4 Flash</option>
|
| 84 |
+
<option value="kimi-k2.7-code">Kimi K2.7 Code</option>
|
| 85 |
+
<option value="minimax-m3">MiniMax M3</option>
|
| 86 |
+
<option value="glm-5.2">GLM 5.2</option>
|
| 87 |
+
</optgroup>
|
| 88 |
+
<optgroup label="Free Models">
|
| 89 |
+
<option value="deepseek-v4-flash-free">DeepSeek V4 Flash Free <span class="free-tag">FREE</span></option>
|
| 90 |
+
<option value="mimo-v2.5-free">MiMo-V2.5 Free <span class="free-tag">FREE</span></option>
|
| 91 |
+
<option value="big-pickle">Big Pickle Free <span class="free-tag">FREE</span></option>
|
| 92 |
+
<option value="laguna-s-2.1-free">Laguna S 2.1 Free <span class="free-tag">FREE</span></option>
|
| 93 |
+
<option value="ling-3.0-flash-free">Ling-3.0 Flash Free <span class="free-tag">FREE</span></option>
|
| 94 |
+
<option value="north-mini-code-free">North Mini Code Free <span class="free-tag">FREE</span></option>
|
| 95 |
+
<option value="nemotron-3-ultra-free">Nemotron 3 Ultra Free <span class="free-tag">FREE</span></option>
|
| 96 |
+
</optgroup>
|
| 97 |
+
</select>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
<div class="chat-container" id="chat"></div>
|
| 101 |
+
<div class="api-key-area">
|
| 102 |
+
<input type="password" id="api-key" placeholder="Enter your OpenCode Zen API key (sk-...)" />
|
| 103 |
+
</div>
|
| 104 |
+
<div class="input-area">
|
| 105 |
+
<textarea id="user-input" placeholder="Type your message..." rows="2"></textarea>
|
| 106 |
+
<button id="send-btn" onclick="sendMessage()">Send</button>
|
| 107 |
+
</div>
|
| 108 |
+
|
| 109 |
+
<script>
|
| 110 |
+
const chatEl = document.getElementById('chat');
|
| 111 |
+
const inputEl = document.getElementById('user-input');
|
| 112 |
+
const sendBtn = document.getElementById('send-btn');
|
| 113 |
+
const apiKeyEl = document.getElementById('api-key');
|
| 114 |
+
const modelSelect = document.getElementById('model-select');
|
| 115 |
+
let history = [];
|
| 116 |
+
|
| 117 |
+
// Load saved API key
|
| 118 |
+
const savedKey = localStorage.getItem('opencode_zen_key');
|
| 119 |
+
if (savedKey) apiKeyEl.value = savedKey;
|
| 120 |
+
|
| 121 |
+
inputEl.addEventListener('keydown', (e) => {
|
| 122 |
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
|
| 123 |
+
});
|
| 124 |
+
|
| 125 |
+
function addMessage(role, content, isError = false) {
|
| 126 |
+
const div = document.createElement('div');
|
| 127 |
+
div.className = `message ${role}${isError ? ' error' : ''}`;
|
| 128 |
+
div.textContent = content;
|
| 129 |
+
chatEl.appendChild(div);
|
| 130 |
+
chatEl.scrollTop = chatEl.scrollHeight;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
function addLoading() {
|
| 134 |
+
const div = document.createElement('div');
|
| 135 |
+
div.className = 'message bot';
|
| 136 |
+
div.id = 'loading';
|
| 137 |
+
div.innerHTML = '<span class="loading">Thinking</span>';
|
| 138 |
+
chatEl.appendChild(div);
|
| 139 |
+
chatEl.scrollTop = chatEl.scrollHeight;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
function removeLoading() {
|
| 143 |
+
const el = document.getElementById('loading');
|
| 144 |
+
if (el) el.remove();
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
function getEndpoint(modelId) {
|
| 148 |
+
const openaiPrefixes = ['gpt-', 'deepseek-', 'grok-', 'minimax-', 'glm-', 'kimi-', 'big-pickle', 'mimo-', 'laguna-', 'ling-', 'north-', 'nemotron-'];
|
| 149 |
+
if (openaiPrefixes.some(p => modelId.startsWith(p) || modelId.includes(p))) {
|
| 150 |
+
return 'https://opencode.ai/zen/v1/chat/completions';
|
| 151 |
+
}
|
| 152 |
+
if (modelId.startsWith('claude-') || modelId.startsWith('qwen3')) {
|
| 153 |
+
return 'https://opencode.ai/zen/v1/messages';
|
| 154 |
+
}
|
| 155 |
+
if (modelId.startsWith('gemini-')) {
|
| 156 |
+
return 'https://opencode.ai/zen/v1/models/' + modelId;
|
| 157 |
+
}
|
| 158 |
+
return 'https://opencode.ai/zen/v1/chat/completions';
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
async function sendMessage() {
|
| 162 |
+
const message = inputEl.value.trim();
|
| 163 |
+
const apiKey = apiKeyEl.value.trim();
|
| 164 |
+
|
| 165 |
+
if (!apiKey) { addMessage('error', 'Please enter your OpenCode Zen API key first.'); return; }
|
| 166 |
+
if (!message) return;
|
| 167 |
+
|
| 168 |
+
localStorage.setItem('opencode_zen_key', apiKey);
|
| 169 |
+
|
| 170 |
+
addMessage('user', message);
|
| 171 |
+
inputEl.value = '';
|
| 172 |
+
sendBtn.disabled = true;
|
| 173 |
+
addLoading();
|
| 174 |
+
|
| 175 |
+
const modelId = modelSelect.value;
|
| 176 |
+
const endpoint = getEndpoint(modelId);
|
| 177 |
+
const messages = [...history, { role: 'user', content: message }];
|
| 178 |
+
|
| 179 |
+
try {
|
| 180 |
+
let response, data;
|
| 181 |
+
|
| 182 |
+
if (endpoint.includes('/messages')) {
|
| 183 |
+
response = await fetch(endpoint, {
|
| 184 |
+
method: 'POST',
|
| 185 |
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01' },
|
| 186 |
+
body: JSON.stringify({ model: modelId, messages: messages, max_tokens: 4096 })
|
| 187 |
+
});
|
| 188 |
+
} else if (endpoint.includes('/models/')) {
|
| 189 |
+
response = await fetch(endpoint, {
|
| 190 |
+
method: 'POST',
|
| 191 |
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
| 192 |
+
body: JSON.stringify({ contents: [{ parts: [{ text: message }] }] })
|
| 193 |
+
});
|
| 194 |
+
} else {
|
| 195 |
+
response = await fetch(endpoint, {
|
| 196 |
+
method: 'POST',
|
| 197 |
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
| 198 |
+
body: JSON.stringify({ model: modelId, messages: messages, max_tokens: 4096 })
|
| 199 |
+
});
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
removeLoading();
|
| 203 |
+
|
| 204 |
+
if (!response.ok) {
|
| 205 |
+
const errText = await response.text();
|
| 206 |
+
addMessage('error', `API Error ${response.status}: ${errText.substring(0, 500)}`);
|
| 207 |
+
sendBtn.disabled = false;
|
| 208 |
+
return;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
data = await response.json();
|
| 212 |
+
let botMessage = '';
|
| 213 |
+
|
| 214 |
+
if (endpoint.includes('/messages') && data.content) {
|
| 215 |
+
botMessage = data.content.map(c => c.text || '').join('');
|
| 216 |
+
} else if (data.choices && data.choices[0]) {
|
| 217 |
+
botMessage = data.choices[0].message.content;
|
| 218 |
+
} else if (data.candidates && data.candidates[0]) {
|
| 219 |
+
botMessage = data.candidates[0].content.parts.map(p => p.text).join('');
|
| 220 |
+
} else {
|
| 221 |
+
botMessage = JSON.stringify(data, null, 2).substring(0, 2000);
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
addMessage('bot', botMessage);
|
| 225 |
+
history.push({ role: 'user', content: message });
|
| 226 |
+
history.push({ role: 'assistant', content: botMessage });
|
| 227 |
+
|
| 228 |
+
// Keep history manageable
|
| 229 |
+
if (history.length > 20) history = history.slice(-20);
|
| 230 |
+
|
| 231 |
+
} catch (err) {
|
| 232 |
+
removeLoading();
|
| 233 |
+
addMessage('error', `Error: ${err.message}`);
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
sendBtn.disabled = false;
|
| 237 |
+
inputEl.focus();
|
| 238 |
+
}
|
| 239 |
+
</script>
|
| 240 |
+
</body>
|
| 241 |
+
</html>
|
opencode.json
DELETED
|
@@ -1,73 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"$schema": "https://opencode.ai/config.json",
|
| 3 |
-
"model": "opencode/gpt-5.4",
|
| 4 |
-
"small_model": "opencode/gpt-5-nano",
|
| 5 |
-
"provider": {
|
| 6 |
-
"opencode": {
|
| 7 |
-
"npm": "@ai-sdk/openai-compatible",
|
| 8 |
-
"name": "OpenCode Zen",
|
| 9 |
-
"options": {
|
| 10 |
-
"baseURL": "https://opencode.ai/zen/v1",
|
| 11 |
-
"apiKey": "{env:OPENCODE_ZEN_KEY}"
|
| 12 |
-
},
|
| 13 |
-
"models": {
|
| 14 |
-
"gpt-5.4": { "name": "GPT 5.4" },
|
| 15 |
-
"gpt-5.4-pro": { "name": "GPT 5.4 Pro" },
|
| 16 |
-
"gpt-5.4-mini": { "name": "GPT 5.4 Mini" },
|
| 17 |
-
"gpt-5.4-nano": { "name": "GPT 5.4 Nano" },
|
| 18 |
-
"gpt-5.5": { "name": "GPT 5.5" },
|
| 19 |
-
"gpt-5.5-pro": { "name": "GPT 5.5 Pro" },
|
| 20 |
-
"gpt-5.3-codex": { "name": "GPT 5.3 Codex" },
|
| 21 |
-
"gpt-5.3-codex-spark": { "name": "GPT 5.3 Codex Spark" },
|
| 22 |
-
"gpt-5.2": { "name": "GPT 5.2" },
|
| 23 |
-
"gpt-5.1": { "name": "GPT 5.1" },
|
| 24 |
-
"gpt-5": { "name": "GPT 5" },
|
| 25 |
-
"gpt-5-nano": { "name": "GPT 5 Nano" },
|
| 26 |
-
"gpt-5.6-sol": { "name": "GPT 5.6 Sol" },
|
| 27 |
-
"gpt-5.6-terra": { "name": "GPT 5.6 Terra" },
|
| 28 |
-
"gpt-5.6-luna": { "name": "GPT 5.6 Luna" },
|
| 29 |
-
"claude-sonnet-5": { "name": "Claude Sonnet 5" },
|
| 30 |
-
"claude-opus-5": { "name": "Claude Opus 5" },
|
| 31 |
-
"claude-opus-4-8": { "name": "Claude Opus 4.8" },
|
| 32 |
-
"claude-opus-4-7": { "name": "Claude Opus 4.7" },
|
| 33 |
-
"claude-opus-4-5": { "name": "Claude Opus 4.5" },
|
| 34 |
-
"claude-sonnet-4-6": { "name": "Claude Sonnet 4.6" },
|
| 35 |
-
"claude-sonnet-4-5": { "name": "Claude Sonnet 4.5" },
|
| 36 |
-
"claude-haiku-4-5": { "name": "Claude Haiku 4.5" },
|
| 37 |
-
"claude-fable-5": { "name": "Claude Fable 5" },
|
| 38 |
-
"gemini-3.6-flash": { "name": "Gemini 3.6 Flash" },
|
| 39 |
-
"gemini-3.5-flash": { "name": "Gemini 3.5 Flash" },
|
| 40 |
-
"gemini-3.5-flash-lite": { "name": "Gemini 3.5 Flash Lite" },
|
| 41 |
-
"gemini-3.1-pro": { "name": "Gemini 3.1 Pro" },
|
| 42 |
-
"gemini-3-flash": { "name": "Gemini 3 Flash" },
|
| 43 |
-
"grok-4.5": { "name": "Grok 4.5" },
|
| 44 |
-
"grok-build-0.1": { "name": "Grok Build 0.1" },
|
| 45 |
-
"qwen3.7-max": { "name": "Qwen3.7 Max" },
|
| 46 |
-
"qwen3.7-plus": { "name": "Qwen3.7 Plus" },
|
| 47 |
-
"qwen3.6-plus": { "name": "Qwen3.6 Plus" },
|
| 48 |
-
"qwen3.5-plus": { "name": "Qwen3.5 Plus" },
|
| 49 |
-
"deepseek-v4-pro": { "name": "DeepSeek V4 Pro" },
|
| 50 |
-
"deepseek-v4-flash": { "name": "DeepSeek V4 Flash" },
|
| 51 |
-
"deepseek-v4-flash-free": { "name": "DeepSeek V4 Flash Free" },
|
| 52 |
-
"minimax-m3": { "name": "MiniMax M3" },
|
| 53 |
-
"minimax-m2.7": { "name": "MiniMax M2.7" },
|
| 54 |
-
"glm-5.2": { "name": "GLM 5.2" },
|
| 55 |
-
"glm-5.1": { "name": "GLM 5.1" },
|
| 56 |
-
"kimi-k2.5": { "name": "Kimi K2.5" },
|
| 57 |
-
"kimi-k2.6": { "name": "Kimi K2.6" },
|
| 58 |
-
"kimi-k2.7-code": { "name": "Kimi K2.7 Code" },
|
| 59 |
-
"big-pickle": { "name": "Big Pickle" },
|
| 60 |
-
"mimo-v2.5-free": { "name": "MiMo-V2.5 Free" },
|
| 61 |
-
"laguna-s-2.1-free": { "name": "Laguna S 2.1 Free" },
|
| 62 |
-
"ling-3.0-flash-free": { "name": "Ling-3.0 Flash Free" },
|
| 63 |
-
"north-mini-code-free": { "name": "North Mini Code Free" },
|
| 64 |
-
"nemotron-3-ultra-free": { "name": "Nemotron 3 Ultra Free" }
|
| 65 |
-
}
|
| 66 |
-
}
|
| 67 |
-
},
|
| 68 |
-
"server": {
|
| 69 |
-
"port": 7860,
|
| 70 |
-
"hostname": "0.0.0.0",
|
| 71 |
-
"cors": ["*"]
|
| 72 |
-
}
|
| 73 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|