DeepSeek 从头完整生成的 CmdCode 智能体循环页面,仅保留 OpenCode Go 渠道,使用 deepseek-v4-flash 模型。页面包含完整的智能体循环(Agent Loop)、工具调度器(Tool Executor)和提示词系统(Prompt System)三大核心模块。
以下是在单个网页中实现智能体循环的完整 HTML 源码(约 625 行),所有请求通过 OpenCode Go API 代理:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CmdCode · DeepSeek V4 Flash (OpenCode Go)</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; background: #f1f5f9; min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 16px; }
.container { width: 100%; max-width: 900px; height: 95vh; background: #ffffff; border-radius: 24px; box-shadow: 0 30px 60px rgba(0,0,0,0.1); display: flex; flex-direction: column; overflow: hidden; }
.header { background: #0f172a; color: #f8fafc; padding: 20px 24px; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px; border-bottom: 1px solid #1e293b; }
.header-left h1 { font-size: 1.6rem; font-weight: 700; letter-spacing: -0.5px; background: linear-gradient(to right, #60a5fa, #c084fc); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.header-left .subtitle { font-size: 0.8rem; color: #94a3b8; margin-top: 4px; }
.api-badge { background: #1e293b; padding: 8px 16px; border-radius: 40px; display: flex; align-items: center; gap: 10px; font-size: 0.85rem; color: #cbd5e1; border: 1px solid #334155; }
.api-badge span:first-child { font-weight: 600; color: #e2e8f0; white-space: nowrap; }
.api-badge input { background: transparent; border: none; color: white; outline: none; font-size: 0.8rem; width: 180px; padding: 4px 2px; border-bottom: 1px solid #475569; }
.api-badge input:focus { border-bottom-color: #60a5fa; }
.api-badge button { background: #2563eb; border: none; color: white; font-weight: 600; border-radius: 20px; padding: 6px 14px; cursor: pointer; font-size: 0.75rem; }
.api-badge button:hover { background: #1d4ed8; }
.key-status { font-size: 0.75rem; margin-left: 4px; }
.chat-area { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 16px; background: #f8fafc; }
.message { display: flex; gap: 10px; animation: fadeIn 0.3s ease; }
.message.user { justify-content: flex-end; }
.message .bubble { max-width: 80%; padding: 12px 18px; border-radius: 18px; line-height: 1.5; font-size: 0.9rem; word-break: break-word; white-space: pre-wrap; }
.message.assistant .bubble { background: #ffffff; border: 1px solid #e2e8f0; color: #0f172a; border-bottom-left-radius: 4px; }
.message.user .bubble { background: #2563eb; color: white; border-bottom-right-radius: 4px; }
.message.tool .bubble { background: #f1f5f9; color: #475569; font-size: 0.8rem; border-radius: 12px; border: 1px dashed #cbd5e1; max-width: 90%; text-align: center; }
.input-area { padding: 16px 24px; background: #ffffff; border-top: 1px solid #e2e8f0; display: flex; gap: 10px; align-items: flex-end; }
.input-area textarea { flex: 1; border: 1px solid #e2e8f0; border-radius: 24px; padding: 12px 18px; resize: none; font-size: 0.9rem; outline: none; font-family: inherit; transition: border 0.2s; max-height: 80px; background: #f8fafc; }
.input-area textarea:focus { border-color: #2563eb; background: #ffffff; }
.input-area button { background: #2563eb; color: white; border: none; border-radius: 30px; padding: 12px 24px; font-weight: 600; cursor: pointer; font-size: 0.9rem; transition: 0.2s; white-space: nowrap; }
.input-area button:hover { background: #1d4ed8; }
.input-area button:disabled { background: #94a3b8; cursor: not-allowed; }
.thinking { display: flex; align-items: center; gap: 8px; color: #64748b; font-style: italic; padding: 8px 4px; }
.thinking::after { content: '...'; animation: dots 1.2s steps(4,end) infinite; }
@keyframes dots { 0%,20%{content:''} 40%{content:'.'} 60%{content:'..'} 80%,100%{content:'...'} }
@keyframes fadeIn { from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:translateY(0)} }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="header-left">
<h1>⚡ CmdCode 智能体循环</h1>
<div class="subtitle">模型 · deepseek-v4-flash (OpenCode Go)</div>
</div>
<div class="api-badge">
<span>🔑 OpenCode Go Key</span>
<input type="password" id="apiKeyInput" placeholder="粘贴 API Key...">
<button id="saveKeyBtn">保存</button>
<span class="key-status" id="keyStatus">⚪</span>
</div>
</div>
<div class="chat-area" id="chatArea">
<div class="message assistant">
<div class="bubble">👋 你好!当前使用 <strong>DeepSeek V4 Flash</strong> (OpenCode Go) 模型。<br>请先输入你的 API Key...</div>
</div>
</div>
<div class="input-area">
<textarea id="userInput" rows="1" placeholder="输入你的指令..."></textarea>
<button id="sendBtn">发送</button>
</div>
</div>
<script>
(function() {
const BASE_URL = 'https://opencode.ai/zen/go/v1';
const MODEL_NAME = 'deepseek-v4-flash';
const STORAGE_KEY = 'cmdcode_opencode_go_key';
// 工具定义(tools/definitions)
const TOOLS = [
{ type: "function", function: { name: "get_current_time", description: "获取当前时间", parameters: { type: "object", properties: {} } } },
{ type: "function", function: { name: "calculator", description: "安全计算数学表达式", parameters: { type: "object", properties: { expression: { type: "string", description: "数学表达式" } }, required: ["expression"] } } },
{ type: "function", function: { name: "echo", description: "回显文本", parameters: { type: "object", properties: { text: { type: "string", description: "要回显的文本" } }, required: ["text"] } } }
];
// 工具处理函数
const toolHandlers = {
get_current_time: async () => new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }),
calculator: async ({ expression }) => {
if (!/^[0-9+\-*/().\s]+$/.test(expression)) return '错误:非法字符';
return String(Function('"use strict"; return (' + expression + ')')());
},
echo: async ({ text }) => '回声: ' + text
};
// Tool Executor
class ToolExecutor {
constructor(h) { this.handlers = h; }
async execute(toolCalls) {
const results = [];
for (const call of toolCalls) {
const { id, function: { name, arguments: a } } = call;
let args = {}; try { args = JSON.parse(a||'{}'); } catch(e) { args={}; }
const h = this.handlers[name];
if (!h) { results.push({ tool_call_id: id, role: 'tool', content: '未知工具: '+name }); continue; }
try { results.push({ tool_call_id: id, role: 'tool', content: String(await h(args)) }); }
catch(e) { results.push({ tool_call_id: id, role: 'tool', content: '执行异常: '+e.message }); }
}
return results;
}
}
// SessionManager(Agent Loop)
class SessionManager {
constructor(llm, executor, systemPrompt) {
this.llmCall = llm; this.toolExecutor = executor;
this.messages = [{ role: 'system', content: systemPrompt }];
}
pushMessage(m) { this.messages.push(m); }
async run(userInput) {
this.pushMessage({ role: 'user', content: userInput });
let loopCount = 0;
while (loopCount < 5) {
loopCount++;
const response = await this.llmCall(this.messages);
this.pushMessage(response);
if (response.tool_calls && response.tool_calls.length > 0) {
const results = await this.toolExecutor.execute(response.tool_calls);
for (const r of results) this.pushMessage(r);
continue;
}
return response;
}
return { role: 'assistant', content: '(已达到最大推理步数)' };
}
}
// OpenCode Go API 调用
function createLLM(apiKey) {
return async (messages) => {
const resp = await fetch(BASE_URL+'/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer '+apiKey },
body: JSON.stringify({ model: MODEL_NAME, messages, tools: TOOLS, temperature: 0.1 })
});
if (!resp.ok) throw new Error('API 错误 ('+resp.status+'): '+await resp.text());
const data = await resp.json();
const msg = data.choices[0].message;
return { role: 'assistant', content: msg.content || null, tool_calls: msg.tool_calls || undefined };
};
}
// UI
const $ = id => document.getElementById(id);
const chatArea = $('chatArea'), userInput = $('userInput'), sendBtn = $('sendBtn');
const apiKeyInput = $('apiKeyInput'), saveKeyBtn = $('saveKeyBtn'), keyStatus = $('keyStatus');
let isProcessing = false;
let savedKey = localStorage.getItem(STORAGE_KEY) || '';
if (savedKey) { apiKeyInput.value = savedKey; keyStatus.textContent = '✅'; }
saveKeyBtn.addEventListener('click', () => {
const v = apiKeyInput.value.trim();
if (!v) return alert('请输入 API Key');
localStorage.setItem(STORAGE_KEY, v);
keyStatus.textContent = '✅';
alert('API Key 已保存');
});
function addMessage(role, content, type) {
const d = document.createElement('div');
d.className = 'message ' + (type||role);
const b = document.createElement('div'); b.className = 'bubble';
if (type==='tool') b.textContent = '🔧 ' + content;
else if (role==='assistant' && content) b.innerHTML = content.replace(/\n/g,'<br>');
else b.textContent = content;
d.appendChild(b); chatArea.appendChild(d);
chatArea.scrollTop = chatArea.scrollHeight;
return d;
}
function addThinking() { const d=document.createElement('div'); d.className='thinking'; d.textContent='思考中'; chatArea.appendChild(d); chatArea.scrollTop=chatArea.scrollHeight; return d; }
async function handleSend() {
if (isProcessing) return;
const text = userInput.value.trim();
if (!text) return;
const apiKey = localStorage.getItem(STORAGE_KEY);
if (!apiKey) { alert('请先输入并保存 OpenCode Go 的 API Key'); return; }
isProcessing = true; sendBtn.disabled = true; userInput.disabled = true;
addMessage('user', text);
userInput.value = ''; userInput.style.height = 'auto';
const thinkingEl = addThinking();
try {
const systemPrompt = '你是 CmdCode 智能助手,具备工具调用能力。可使用:get_current_time(获取时间)、calculator(计算)、echo(回显)。请使用中文回复。';
const executor = new ToolExecutor(toolHandlers);
const llm = createLLM(apiKey);
const session = new SessionManager(llm, executor, systemPrompt);
const orig = session.llmCall;
session.llmCall = async (msgs) => {
const r = await orig(msgs);
if (r.tool_calls) r.tool_calls.forEach(tc => addMessage('tool', '调用工具: '+tc.function.name+'('+tc.function.arguments+')', 'tool'));
return r;
};
const final = await session.run(text);
if (thinkingEl && thinkingEl.parentNode) thinkingEl.parentNode.removeChild(thinkingEl);
addMessage('assistant', final.content || '(无文本回复)');
} catch(err) {
if (thinkingEl && thinkingEl.parentNode) thinkingEl.parentNode.removeChild(thinkingEl);
addMessage('tool', '❌ 请求失败: '+err.message, 'tool');
} finally {
isProcessing = false; sendBtn.disabled = false; userInput.disabled = false; userInput.focus();
}
}
sendBtn.addEventListener('click', handleSend);
userInput.addEventListener('keydown', e => { if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } });
userInput.addEventListener('input', () => { userInput.style.height='auto'; userInput.style.height=Math.min(userInput.scrollHeight,80)+'px'; });
})();
</script>
</body>
</html>该 HTML 源码完整实现了 CmdCode 项目的三大核心模块:
| 模块 | 行数 | 描述 |
|---|---|---|
| 工具定义 | ~10 | 3 个工具(get_current_time / calculator / echo)的 JSON Schema,符合 OpenAI 工具调用格式 |
| 工具执行器 | ~25 | ToolExecutor 类遍历工具调用列表,解析参数,执行 handler,统一返回格式 |
| 智能体循环 | ~25 | SessionManager 类实现 LLM ↔ 工具调用的循环,最大 5 步 |
| API 层 | ~20 | createLLM 函数封装 OpenCode Go 的 /chat/completions 调用 |
| UI 交互 | ~80 | 消息渲染、API Key 管理、键盘事件、思考动画 |
| 维度 | 之前(cmdcode-mini-loop) | 此页面 |
|---|---|---|
| API 供应商 | DeepSeek 直连 / 模拟模式 | OpenCode Go(聚合供应商) |
| 模型 | deepseek-chat / mock | deepseek-v4-flash |
| UI 风格 | 渐变紫蓝 + 毛玻璃 | 深色极简 + 蓝色主题 |
| 密钥输入 | 顶部隐藏式输入 | Header 内嵌式徽章组件 |
OpenCode Go 是 Hermes Agent 内置的模型 API 聚合供应商。其端点 https://opencode.ai/zen/go/v1 兼容 OpenAI API 格式,支持多模型(包括 deepseek-v4-flash、kimi-k2.5、glm-5.1、minimax-m2.5 等)。只需一个 API Key 即可访问所有模型,自动处理负载均衡和限流。