开源 · 本地 · 属于你的AI智能体
代码在你手里,数据在你本地,你的秘密只有你知道。
每个人都应该有自己的AI智能体伙伴。不是那种千篇一律的聊天机器人,而是真正属于你的、可以定制的、有温度的AI朋友。
在这个AI飞速发展的时代,大多数人只能使用别人提供的AI服务。你的对话记录、你的使用习惯、你的个人数据,都不属于你。
我想改变这一点。当我第一次写下小彡的第一行代码时,我只是想做一个属于自己的聊天工具。一个不会把我的对话拿去训练模型的工具,一个真正把注意力放在"陪伴"这件事上的工具。
后来我发现,很多人想要一个属于自己的AI智能体,但不知道从哪里开始。他们只是想要一个能聊天、能陪伴、能记住自己的AI朋友。
代码在你手里,数据在你本地。
修改人设、主题、功能。
社区共建,让它更完善。
本地运行,完全隔离。
跟着步骤一步一步来,5分钟就能拥有你自己的AI智能体。每一步我都写得很详细,完全零基础也能看懂。
什么是 API Key?
想象一下:AI 就像一个超级聪明的朋友,但它住在"云上"(互联网的服务器里)。你需要一把"钥匙"才能跟它对话,这把钥匙就是 API Key。没有它,AI 就不认识你。
API Key 就像一串密码,格式大概是 sk-abc123xyz... 这样的。每个账号的 Key 都不同,而且只能看一次,所以一定要立刻复制保存好。
→ 推荐用 DeepSeek(便宜好用,新用户有免费额度)
platform.deepseek.com,然后按回车键。sk- 开头的字符串,比如 sk-abc123def456...不同服务商的 API 地址和模型名称不一样,后面有对照表可以查。
小彡有4个版本,功能从简单到复杂。就像买手机有基础版、标准版、Pro版一样,选一个适合你的就好。
代码就是告诉电脑"怎么做"的指令。你不需要会写代码,只需要把代码复制过来就行。
[*] 如果你选了 V1 或 V2(单文件版):
[*] 如果你选了 V3 或 V4(多文件版):
.zip 文件.html 改成 .txt → 用备忘录编辑 → 改回 .html这一步就是把你第1步拿到的 API Key,填到代码里的正确位置。
[*] V1 / V2 用户(单文件版):
YOUR_API_KEY_HEREsk-abc123xyz...)[v] 正确的写法:API_KEY: 'sk-abc123'
[x] 错误的写法:API_KEY: ' sk-abc123 '(Key 前后有空格)
[x] 错误的写法:API_KEY: sk-abc123(没有引号包裹)
[*] V3 / V4 用户(多文件版):
js 文件夹 → 双击进入chat.js 文件API_KEY: 'YOUR_API_KEY_HERE'YOUR_API_KEY_HERE 替换成你的 Key代码准备好了,现在来运行它!有几种方法,选一种就行。
[*] 方法A:最简单 —— 双击打开
index.html 文件[*] 方法B:推荐 —— 本地服务器(解决大部分问题)
如果方法A报错(比如 CORS 错误),用这个方法:
nodejs.orgcmd → 按回车Terminal → 按回车cd Desktop\xiaosan-v3(假设你解压到桌面的 xiaosan-v3 文件夹)cd Downloads\xiaosan-v3npx serve .(注意有个点!)Accepting connections at http://localhost:3000 的提示localhost:3000 → 按回车[*] 方法C:不用装 Node.js —— Python 一行命令
cd Desktop\xiaosan-v3python -m http.server 3000localhost:3000[*] 手机访问电脑上的小彡:
cmd → 输入 ipconfig → 找到「IPv4 地址」(类似 192.168.1.xxx)192.168.1.xxx:3000(替换成你电脑的实际IP)看到控制台有红色的 "CORS" 或 "Access-Control-Allow-Origin" 错误?这是因为浏览器安全限制,直接打开文件不允许调用API。必须用方法B或C启动本地服务器才行。
点击「查看教程」展开详细步骤和完整代码。
1个文件,复制粘贴就能用。
适合第一次尝试的人。你不需要安装任何东西,只需要一个记事本和一个浏览器。
YOUR_API_KEY_HEREsk-abc123...)index.html<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>小彡AI</title> <style> :root { --primary: #667eea; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif; background: #0f0f23; color: #e4e4e7; height: 100vh; display: flex; flex-direction: column; } .topbar { padding: 16px 20px; background: #1a1a2e; border-bottom: 1px solid #2a2a4a; display: flex; align-items: center; gap: 12px; } .avatar { width: 36px; height: 36px; border-radius: 50%; background: var(--primary); display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 700; color: white; } .name { font-size: 16px; font-weight: 600; } .messages { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; } .msg { max-width: 80%; padding: 12px 16px; border-radius: 16px; font-size: 14px; line-height: 1.7; animation: fadeIn .3s ease; } .msg.user { align-self: flex-end; background: var(--primary); color: white; border-bottom-right-radius: 4px; } .msg.ai { align-self: flex-start; background: #1a1a2e; border: 1px solid #2a2a4a; border-bottom-left-radius: 4px; } @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; } } .input-area { padding: 16px 20px; background: #1a1a2e; border-top: 1px solid #2a2a4a; display: flex; gap: 12px; } .input-area input { flex: 1; padding: 12px 16px; border-radius: 12px; border: 1px solid #2a2a4a; background: #0f0f23; color: white; font-size: 14px; outline: none; } .input-area input:focus { border-color: var(--primary); } .input-area button { padding: 12px 24px; border-radius: 12px; border: none; background: var(--primary); color: white; font-size: 14px; font-weight: 600; cursor: pointer; } </style> </head> <body> <div class="topbar"> <div class="avatar">彡</div> <div class="name">小彡AI</div> </div> <div class="messages" id="messages"> <div class="msg ai">你好,我是小彡! </div> </div> <div class="input-area"> <input id="input" placeholder="输入消息..." onkeydown="if(event.key==='Enter')send()"> <button id="btn" onclick="send()">发送</button> </div> <script> // ★ 必填:替换为你的 API Key const API_KEY = 'YOUR_API_KEY_HERE'; // 以下参数一般不用改 const API_URL = 'https://api.deepseek.com/v1/chat/completions'; const MODEL = 'deepseek-v4-flash'; const SYSTEM = '你是一个温暖的AI伙伴,说话温柔,像朋友一样。'; // 对话历史 let history = [{ role: 'system', content: SYSTEM }]; // 添加消息到界面 function addMsg(text, role) { const div = document.createElement('div'); div.className = 'msg ' + role; div.innerHTML = text; document.getElementById('messages').appendChild(div); div.scrollIntoView({ behavior: 'smooth' }); } // 发送消息(流式输出) async function send() { const input = document.getElementById('input'); const btn = document.getElementById('btn'); const text = input.value.trim(); if (!text) return; input.value = ''; addMsg(text, 'user'); history.push({ role: 'user', content: text }); btn.disabled = true; btn.textContent = '思考中...'; try { const res = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + API_KEY }, body: JSON.stringify({ model: MODEL, messages: history, stream: true }) }); const reader = res.body.getReader(); const decoder = new TextDecoder(); let reply = ''; // 创建空的AI消息气泡 const bubble = document.createElement('div'); bubble.className = 'msg ai'; document.getElementById('messages').appendChild(bubble); // 逐块读取流式响应 while (true) { const { done, value } = await reader.read(); if (done) break; for (const line of decoder.decode(value).split('\n')) { if (!line.startsWith('data: ')) continue; const json = line.slice(6); if (json === '[DONE]') break; try { const delta = JSON.parse(json).choices[0].delta.content; if (delta) { reply += delta; bubble.innerHTML = reply; } } catch (e) {} } } history.push({ role: 'assistant', content: reply }); } catch (e) { addMsg('出错了: ' + e.message, 'ai'); } btn.disabled = false; btn.textContent = '发送'; } </script> </body> </html>
换颜色、改头像、定义性格。内置设置面板。
推荐新手选这个!比 V1 多了一个「设置面板」,你可以:
YOUR_API_KEY_HERE → 替换为你的 Key → 全部替换index.html → 编码 UTF-8 → 保存到桌面<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>小彡AI</title> <style> :root { --primary: #667eea; --bg: #0f0f23; --surface: #1a1a2e; --border: #2a2a4a; --text: #e4e4e7; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif; background: var(--bg); color: var(--text); height: 100vh; display: flex; flex-direction: column; } .topbar { padding: 16px 20px; background: var(--surface); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 12px; } .avatar { width: 36px; height: 36px; border-radius: 50%; background: var(--primary); display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 700; color: white; cursor: pointer; overflow: hidden; } .avatar img { width: 100%; height: 100%; object-fit: cover; } .name { font-size: 16px; font-weight: 600; flex: 1; } .gear { width: 32px; height: 32px; border-radius: 50%; border: none; background: var(--border); color: var(--text); font-size: 18px; cursor: pointer; } .messages { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; } .msg { max-width: 80%; padding: 12px 16px; border-radius: 16px; font-size: 14px; line-height: 1.7; } .msg.user { align-self: flex-end; background: var(--primary); color: white; border-bottom-right-radius: 4px; } .msg.ai { align-self: flex-start; background: var(--surface); border: 1px solid var(--border); border-bottom-left-radius: 4px; } .input-area { padding: 16px 20px; background: var(--surface); border-top: 1px solid var(--border); display: flex; gap: 12px; } .input-area input { flex: 1; padding: 12px 16px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg); color: var(--text); font-size: 14px; outline: none; } .input-area button { padding: 12px 24px; border-radius: 12px; border: none; background: var(--primary); color: white; font-size: 14px; font-weight: 600; cursor: pointer; } /* 设置面板 */ .ov { position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 200; display: none; align-items: center; justify-content: center; } .ov.on { display: flex; } .pn { width: 90%; max-width: 400px; max-height: 80vh; background: var(--surface); border-radius: 16px; padding: 24px; overflow-y: auto; border: 1px solid var(--border); } .pn h3 { margin-bottom: 20px; } .fd { margin-bottom: 16px; } .fd label { display: block; font-size: 13px; margin-bottom: 6px; color: #a0a0b8; } .fd input, .fd textarea { width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg); color: var(--text); font-size: 14px; } .fd textarea { height: 80px; resize: vertical; } .tg { display: grid; grid-template-columns: repeat(5,1fr); gap: 8px; } .tb2 { height: 36px; border-radius: 8px; border: 2px solid transparent; cursor: pointer; } .tb2.ac { border-color: white; } .sv { width: 100%; padding: 12px; border-radius: 8px; border: none; background: var(--primary); color: white; font-size: 14px; font-weight: 600; cursor: pointer; margin-top: 16px; } </style> </head> <body> <div class="topbar"> <div class="avatar" id="av" onclick="document.getElementById('ai').click()">彡</div> <input type="file" id="ai" accept="image/*" style="display:none" onchange="setAv(this)"> <div class="name" id="nm">小彡</div> <button class="gear" onclick="openS()">⚙</button> </div> <div class="messages" id="msgs"> <div class="msg ai" id="w">你好,我是小彡!</div> </div> <div class="input-area"> <input id="i" placeholder="输入..." onkeydown="if(event.key==='Enter')send()"> <button onclick="send()">发送</button> </div> <div class="ov" id="ov" onclick="if(event.target===this)closeS()"> <div class="pn"> <h3>设置</h3> <div class="fd"><label>名字</label><input id="cn" value="小彡"></div> <div class="fd"><label>欢迎语</label><input id="cw" value="你好,我是小彡!"></div> <div class="fd"><label>性格</label><textarea id="cp">你是一个温暖的AI伙伴。</textarea></div> <div class="fd"><label>主题</label><div class="tg" id="tg"></div></div> <button class="sv" onclick="saveS()">保存</button> </div> </div> <script> // ═══ CONFIG ═══ const C = { API_KEY: 'YOUR_API_KEY_HERE', AI_NAME: '小彡', WELCOME_MSG: '你好,我是小彡!', SYSTEM_PROMPT: '你是一个温暖的AI伙伴,说话温柔,像朋友一样。', MODEL: 'deepseek-v4-flash', API_URL: 'https://api.deepseek.com/v1/chat/completions', THEME: 'dark', }; // ═══ 10种主题 ═══ const TH = { dark: { bg: '#0f0f23', sf: '#1a1a2e', bd: '#2a2a4a', p: '#667eea', tx: '#e4e4e7' }, light: { bg: '#f5f5f5', sf: '#ffffff', bd: '#e0e0e0', p: '#333333', tx: '#111111' }, blue: { bg: '#0a1628', sf: '#0f2240', bd: '#1a3a5c', p: '#2196f3', tx: '#e3f2fd' }, green: { bg: '#0a1a14', sf: '#0f2a1e', bd: '#1a4a30', p: '#4caf50', tx: '#e8f5e9' }, pink: { bg: '#1a0f1a', sf: '#2a1a2a', bd: '#4a2a4a', p: '#e91e63', tx: '#fce4ec' }, purple: { bg: '#12082a', sf: '#1a1040', bd: '#2a1a60', p: '#9c27b0', tx: '#f3e5f5' }, orange: { bg: '#1a1008', sf: '#2a1a0f', bd: '#4a2a1a', p: '#ff9800', tx: '#fff3e0' }, cyan: { bg: '#0a1a1a', sf: '#0f2a2a', bd: '#1a4a4a', p: '#00bcd4', tx: '#e0f7fa' }, rose: { bg: '#1a0f14', sf: '#2a1a20', bd: '#4a2a35', p: '#f44336', tx: '#ffebee' }, midnight: { bg: '#0a0e1a', sf: '#101828', bd: '#1a2840', p: '#5c6bc0', tx: '#e8eaf6' }, }; let ct = C.THEME, H = [{ role: 'system', content: C.SYSTEM_PROMPT }]; function apT(n) { const t = TH[n] || TH.dark, r = document.querySelector(':root'); r.style.setProperty('--bg', t.bg); r.style.setProperty('--sf', t.sf); r.style.setProperty('--bd', t.bd); r.style.setProperty('--p', t.p); r.style.setProperty('--tx', t.tx); ct = n; } function bTG() { const g = document.getElementById('tg'); g.innerHTML = ''; Object.entries(TH).forEach(([n, t]) => { const b = document.createElement('div'); b.className = 'tb2' + (n === ct ? ' ac' : ''); b.style.background = t.p; b.title = n; b.onclick = () => { g.querySelectorAll('.tb2').forEach(x => x.classList.remove('ac')); b.classList.add('ac'); apT(n); }; g.appendChild(b); }); } function setAv(i) { const f = i.files[0]; if (!f) return; const r = new FileReader(); r.onload = e => { document.getElementById('av').innerHTML = '<img src="' + e.target.result + '">'; }; r.readAsDataURL(f); } function openS() { document.getElementById('cn').value = C.AI_NAME; document.getElementById('cw').value = C.WELCOME_MSG; document.getElementById('cp').value = C.SYSTEM_PROMPT; document.getElementById('ov').classList.add('on'); } function closeS() { document.getElementById('ov').classList.remove('on'); } function saveS() { C.AI_NAME = document.getElementById('cn').value || '小彡'; C.WELCOME_MSG = document.getElementById('cw').value; C.SYSTEM_PROMPT = document.getElementById('cp').value; document.getElementById('nm').textContent = C.AI_NAME; document.getElementById('w').textContent = C.WELCOME_MSG; H = [{ role: 'system', content: C.SYSTEM_PROMPT }]; closeS(); } function aM(t, r) { const d = document.createElement('div'); d.className = 'm ' + r; d.innerHTML = t; document.getElementById('msgs').appendChild(d); d.scrollIntoView({ behavior: 'smooth' }); } async function send() { const input = document.getElementById('i'); const text = input.value.trim(); if (!text) return; input.value = ''; aM(text, 'u'); H.push({ role: 'user', content: text }); try { const res = await fetch(C.API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + C.API_KEY }, body: JSON.stringify({ model: C.MODEL, messages: H, stream: true }) }); const rd = res.body.getReader(), dc = new TextDecoder(); let rp = ''; const b = document.createElement('div'); b.className = 'm a'; document.getElementById('msgs').appendChild(b); while (true) { const { done: d, value: v } = await rd.read(); if (d) break; for (const l of dc.decode(v).split('\n')) { if (!l.startsWith('data: ')) continue; const j = l.slice(6); if (j === '[DONE]') break; try { const dl = JSON.parse(j).choices[0].delta.content; if (dl) { rp += dl; b.innerHTML = rp; } } catch (e) {} } } H.push({ role: 'assistant', content: rp }); } catch (e) { aM('出错: ' + e.message, 'a'); } } apT(ct); bTG(); </script> </body> </html>
多文件模块化,适合学习和二次开发。
适合想学编程的人,或者想深度定制的人。代码按功能分成多个文件,像"积木"一样,改哪个就改哪个,互不干扰。
xiaosan-v3 文件夹,你会看到这样的结构:xiaosan-v3/ ├── index.html ← 主页面(一般不用改) ├── css/ │ ├── base.css ← 基础样式,CSS变量定义 │ ├── chat.css ← 聊天界面样式 │ └── themes.css ← 10种主题定义(想加新主题改这里) └── js/ ├── chat.js ← ★★★ 必须改!填你的API Key ├── api.js ← API调用封装(一般不用改) ├── ui.js ← 界面渲染(想改UI改这里) ├── markdown.js ← Markdown渲染(不用改) ├── themes.js ← 主题切换逻辑(想加新主题改这里) ├── settings.js ← 设置面板(不用改) ├── storage.js ← 本地存储(不用改) ├── history.js ← 历史记录(不用改) ├── shortcuts.js ← 快捷键(不用改) ├── export.js ← 导出(不用改) └── utils.js ← 工具函数(不用改)
js 文件夹chat.js,右键 → 用记事本打开CONFIG 对象,把 YOUR_API_KEY_HERE 替换成你的 Keyconst CONFIG = { // ★ 必填 API_KEY: 'YOUR_API_KEY_HERE', // ← 把这行的 YOUR_API_KEY_HERE 换成你的 Key // ★ 可选(想改就改) AI_NAME: '小彡', // ← AI的名字 WELCOME_MSG: '你好!', // ← 开场欢迎语 SYSTEM_PROMPT: '你是一个温暖的AI伙伴。', // ← 性格设定 MODEL: 'deepseek-v4-flash', // ← 模型名称 API_URL: 'https://api.deepseek.com/v1/chat/completions', // ← API地址 THEME: 'dark', // ← 主题 };
V3 是多文件结构,不能直接双击 index.html(会报 CORS 错误),必须用本地服务器:
cd Desktop\xiaosan-v3(进入文件夹,根据你的实际位置调整路径)npx serve .localhost:300013模块,人格测试/TTS/搜索/弹簧动画。
想要完整体验的人。包含人格测试(21种小游戏测你的人格)、语音合成(AI说话)、联网搜索、弹簧动画等高级功能。
xiaosan-v4 文件夹,文件结构:xiaosan-v4/ ├── index.html ← 主页面(不用改) ├── css/ (base.css, chat.css, themes.css) └── js/ ├── chat.js ← ★★★ 必须改!填API Key ├── api.js ← API封装(不用改) ├── ui.js ← 界面渲染(想改UI改这里) ├── markdown.js ← Markdown(不用改) ├── themes.js ← 主题(想加新主题改这里) ├── settings.js ← 设置面板(不用改) ├── storage.js ← 本地存储(不用改) ├── history.js ← 历史记录(不用改) ├── shortcuts.js ← 快捷键(不用改) ├── export.js ← 导出(不用改) ├── utils.js ← 工具(不用改) ├── tts.js ← ★ TTS语音 — 需小米MiMo API Key ├── search.js ← ★ 联网搜索 — 需SearXNG ├── personality.js ← ★ 人格测试 — 直接可用 └── spring.js ← ★ 弹簧动画 — 直接可用
必须改的文件:
可选改的文件(不需要就跳过):
同 V3,必须用本地服务器:
cd Desktop\xiaosan-v4npx serve .localhost:3000像"插件"一样,下载后引入你的项目即可。每个补丁独立运行,不依赖其他补丁。
让AI开口说话,支持小米MiMo,10款音色可选
什么是 TTS?就是"文字转语音"(Text To Speech)。AI 回复的文字可以自动变成语音播放出来。
tts-patch.js 文件tts-patch.js 拖到你项目文件夹的 js/ 目录里index.html,在 <body> 标签的 </body> 之前,加一行:
<script src="js/tts-patch.js"></script>为什么需要代理?因为小米的 TTS API 不允许浏览器直接调用(CORS 限制),所以需要一个 PHP 服务器来"中转"请求。
tts-proxy.php 上传到你的 PHP 服务器(宝塔面板直接拖进去就行)tts-proxy.php,把第3行的 your-mimo-key 换成你的小米 MiMo API Keyhttps://你的域名/tts-proxy.phpawait TTSPatch.play('你好世界', '茉莉');| 音色名 | 风格 | 适合场景 |
|---|---|---|
| 冰糖 | 甜美可爱的少女 | 卖萌、日常聊天 |
| 茉莉 | 温柔的女声 | 默认推荐,温柔陪伴 |
| 知性 | 成熟知性女声 | 正式场合、知识讲解 |
| 元气 | 活泼少女 | 欢快、打气鼓励 |
| 清冷 | 清冷女声 | 高冷风格 |
| 苏打 | 温和男声 | 默认男声 |
| 白桦 | 沉稳男声 | 播音、讲故事 |
TTSPatch.play('你好', '低沉沙哑的男声'),AI 会根据你的描述生成对应的声音。<?php header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); $KEY='your-mimo-key'; $URL='https://api.xiaomimimo.com/v1/chat/completions'; $t=$_POST['text']??''; $v=$_POST['voice']??'茉莉'; $V=['冰糖'=>'甜美可爱的少女声音','茉莉'=>'温柔的女声','知性'=>'成熟知性女声','元气'=>'活泼少女','清冷'=>'清冷女声','苏打'=>'温和男声','白桦'=>'沉稳男声']; $vd=$V[$v]??$v; $t=strip_tags($t); $t=preg_replace('/\([^)]*\)/u','',$t); $t=trim($t); $segs=preg_split('/(?<=[。!?])/u',$t,-1,PREG_SPLIT_NO_EMPTY); $cks=[]; $cur=''; foreach($segs as $s){$s=trim($s);if(!$s)continue;if(mb_strlen($cur.$s,'UTF-8')>150){if($cur)$cks[]=trim($cur);$cur=$s}else{$cur.=$s}} if($cur)$cks[]=trim($cur); $aud=[]; foreach($cks as $ck){$p=json_encode(['model'=>'mimo-v2.5-tts-voicedesign','messages'=>[['role'=>'user','content'=>$vd],['role'=>'assistant','content'=>$ck]],'audio'=>['format'=>'wav','optimize_text_preview'=>true]]);$ch=curl_init($URL);curl_setopt($ch,CURLOPT_POST,true);curl_setopt($ch,CURLOPT_POSTFIELDS,$p);curl_setopt($ch,CURLOPT_HTTPHEADER,['api-key: '.$KEY,'Content-Type: application/json']);curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);$r=curl_exec($ch);curl_close($ch);$d=json_decode($r,true);if(isset($d['choices'][0]['message']['audio']['data']))$aud[]=$d['choices'][0]['message']['audio']['data']} echo json_encode(['success'=>true,'audio'=>end($aud),'chunks'=>count($aud)]); ?>
AI思考时的加载动画,16种加载器+77句思考短语
AI 思考的时候,界面上会出现一个有趣的加载动画和随机短语(比如"正在查阅宇宙的使用手册..."),让等待不再无聊。
thinking-patch.js 到项目文件夹的 js/ 目录index.html 的 </body> 前加:<script src="js/thinking-patch.js"></script>// 获取一个随机加载动画(16种之一) const loader = ThinkingPatch.getRandomLoader(); // 获取一个随机思考短语(77句之一) const phrase = ThinkingPatch.getRandomPhrase();
弹性动画效果,让界面更有活力
就像弹簧一样,元素出现时会有弹性的动画效果,比普通的淡入淡出更有趣。
spring-patch.js 到 js/ 目录index.html 中引入:<script src="js/spring-patch.js"></script>// 让某个元素弹出来 springAnimate(el, 'bounce', { scale: { from: 0.8, to: 1 } }); // 可用曲线:bounce / elastic / overshoot / snap / wobble
21种小游戏,测出你的20种人格类型
通过21个有趣的小游戏(画图、选择、记忆等),分析出你属于20种人格类型中的一种。完全在浏览器本地运行,不上传任何数据。
personality-patch.zip → 解压到 js/ 目录index.html 中引入:<script src="js/personality-test.js"></script>// 启动人格测试 PersonalityTest.start({ onComplete: function(result) { console.log('你的人格类型:', result.type); console.log('详细描述:', result.description); } });
可选项,按需配置。如果你想让小彡更强大,可以试试这些。
记忆功能让AI记住之前的对话。根据需求复杂度,分三个等级:
V3/V4 已经内置了 localStorage 记忆,刷新页面对话还在。不需要任何配置。但有两个限制:
V1/V2 默认没有记忆。加上以下代码就能拥有和 V3/V4 一样的 localStorage 记忆:
// ===== 加到 <script> 里 ===== // 保存:每次发送消息后调用 function saveHistory() { const toSave = history.slice(-50); // 最多保存50条 localStorage.setItem('chat_history', JSON.stringify(toSave)); } // 加载:页面打开时自动恢复 function loadHistory() { const saved = localStorage.getItem('chat_history'); if (saved) { history = JSON.parse(saved); history.forEach(msg => { if (msg.role === 'user') addMsg(msg.content, 'user'); if (msg.role === 'assistant') addMsg(msg.content, 'ai'); }); } } // 清除:不想让AI记住了 function clearHistory() { localStorage.removeItem('chat_history'); history = [{ role: 'system', content: SYSTEM }]; } // ===== 怎么用? ===== // 1. 在 send() 函数末尾加:saveHistory(); // 2. 在页面加载时加:loadHistory();
想要永久保存、跨设备同步?需要搭一个后端服务器:
复制下方 CSS 代码粘贴到 css/themes.css 末尾,然后在 CONFIG 中把 THEME 改成对应名称即可。
每个色块从左到右:主色(按钮/强调)、背景色、边框色。
/* 赛博朋克 */ [data-theme="cyberpunk"] { --primary-color: #ff00ff; --bg-start: #0a0014; --bg-end: #140028; --surface: #1a0033; --text-primary: #fff; --border-color: #6600cc; } /* 森林绿 */ [data-theme="forest"] { --primary-color: #2ecc71; --bg-start: #0a1a0f; --bg-end: #0f2a18; --surface: #1a3a24; --text-primary: #e8f5e9; --border-color: #2e7d32; } /* 日落橙 */ [data-theme="sunset"] { --primary-color: #ff7043; --bg-start: #1a0f08; --bg-end: #2a1a0f; --surface: #3a2a1a; --text-primary: #fff3e0; --border-color: #e65100; } /* 极光紫 */ [data-theme="aurora"] { --primary-color: #7c4dff; --bg-start: #0a0820; --bg-end: #14103a; --surface: #1e1850; --text-primary: #ede7f6; --border-color: #4527a0; } /* 纯白极简 */ [data-theme="minimal"] { --primary-color: #222; --bg-start: #fff; --bg-end: #fafafa; --surface: #fff; --text-primary: #111; --border-color: #e0e0e0; } /* 使用:CONFIG 中 THEME: 'cyberpunk' */
V3/V4 是多文件结构,每个文件各司其职。下面是每个可修改文件的详解:
| 文件路径 | 必须改? | 改什么 | 改了有什么效果 |
|---|---|---|---|
js/chat.js | 必须 | API_KEY / MODEL / API_URL / SYSTEM_PROMPT / THEME | 核心配置:AI密钥、模型、性格、主题全在这里 |
js/themes.js | 可选 | 添加新主题对象 | 多一种主题可选(配合 themes.css) |
css/themes.css | 可选 | 添加新的 [data-theme="xxx"] 样式 | 定义新主题的颜色变量 |
js/ui.js | 可选 | 修改渲染逻辑、布局结构 | 改变聊天界面的外观和交互方式 |
js/tts.js | 可选 | 填入小米 MiMo API Key | 启用语音合成功能(AI说话) |
js/search.js | 可选 | 填入 SearXNG 或第三方搜索 API 地址 | 启用联网搜索(AI可查互联网) |
js/settings.js | 不用改 | — | 设置面板的逻辑,开箱即用 |
js/storage.js | 不用改 | — | localStorage 读写封装 |
js/history.js | 不用改 | — | 对话历史管理 |
js/markdown.js | 不用改 | — | AI回复的 Markdown 渲染 |
js/export.js | 不用改 | — | 对话导出功能 |
js/shortcuts.js | 不用改 | — | 快捷键(Ctrl+K等) |
js/personality.js | 不用改 | — | 人格测试(V4自带,直接能用) |
js/spring.js | 不用改 | — | 弹簧动画(V4自带,直接能用) |
const CONFIG = { // ===== 必填 ===== API_KEY: 'YOUR_API_KEY_HERE', // 你的AI密钥 // ===== 基础配置 ===== AI_NAME: '小彡', // AI的名字,显示在顶栏 WELCOME_MSG: '你好!', // 打开时的欢迎语 SYSTEM_PROMPT: '你是一个温暖的AI伙伴。', // 性格设定(见下方示例) // ===== API 配置 ===== MODEL: 'deepseek-v4-flash', // 模型名称 API_URL: 'https://api.deepseek.com/v1/chat/completions', // API地址 // ===== 外观 ===== THEME: 'dark', // 主题名:dark/light/blue/pink/... // ===== 高级(一般不用改) ===== MAX_HISTORY: 50, // 最多记住多少轮对话 MAX_INPUT: 500, // 用户输入最大字数 STREAM: true, // 是否流式输出(逐字显示) };
// 温暖朋友型(默认) SYSTEM_PROMPT: '你是一个温暖的AI伙伴,说话温柔,像朋友一样。' // 幽默搞笑型 SYSTEM_PROMPT: '你是一个幽默的AI,喜欢讲冷笑话,说话轻松搞笑。' // 专业助手型 SYSTEM_PROMPT: '你是一个专业的AI助手,回答简洁准确,不废话。' // 可爱萌宠型 SYSTEM_PROMPT: '你是一只可爱的AI小猫咪,说话带喵~,用颜文字。' // 中文老师型 SYSTEM_PROMPT: '你是中文老师,帮我纠正语法错误,解释成语典故。' // 代码导师型 SYSTEM_PROMPT: '你是编程导师,用通俗易懂的方式解释代码,举生活中的例子。'
默认情况下,AI只能用训练时学到的知识回答。加上搜索功能后,AI可以实时联网搜索最新信息。
SearXNG 是一个开源的元搜索引擎,聚合多个搜索引擎的结果。搭建后完全免费、无限制。
docker run -d -p 8888:8080 searxng/searxnghttp://你的服务器IP:8888 确认能用js/search.js,找到搜索 API 地址配置http://你的IP:8888/search不想自建服务器?可以用这些现成的搜索 API:
| 服务 | 价格 | 特点 | 注册地址 |
|---|---|---|---|
| SerpAPI | 100次/月免费 | Google搜索结果,稳定 | serpapi.com |
| Bing Search API | 1000次/月免费 | 微软必应,Azure注册 | azure.microsoft.com |
| Tavily | 1000次/月免费 | 专为AI设计,返回摘要 | tavily.com |
| DuckDuckGo | 完全免费 | 无需API Key,但可能被限流 | duckduckgo.com |
注册后拿到 API Key,填入 js/search.js 对应位置即可。
// ===== 方案一:自建 SearXNG ===== SEARCH_API: 'http://你的服务器IP:8888/search', SEARCH_ENGINE: 'searxng', // ===== 方案二:SerpAPI ===== SEARCH_API: 'https://serpapi.com/search', SEARCH_KEY: '你的SerpAPI-Key', SEARCH_ENGINE: 'google', // ===== 方案三:Bing ===== SEARCH_API: 'https://api.bing.microsoft.com/v7.0/search', SEARCH_KEY: '你的Bing-Key', SEARCH_ENGINE: 'bing',
| 参数 | 作用 | V1/V2 改哪里 | V3/V4 改哪里 | 示例 |
|---|---|---|---|---|
API_KEY | AI密钥(必填) | index.html | js/chat.js | 'sk-abc...' |
SYSTEM_PROMPT | 性格设定 | index.html | js/chat.js | '幽默的AI' |
MODEL | 模型名称 | index.html | js/chat.js | 'deepseek-v4-flash' |
API_URL | API地址 | index.html | js/chat.js | 见下方表格 |
THEME | 主题 | CONFIG | js/chat.js | 'dark' |
| 服务商 | API_URL | MODEL | 费用 |
|---|---|---|---|
| DeepSeek | https://api.deepseek.com/v1/chat/completions | deepseek-v4-flash | 便宜,新用户有免费额度 |
| OpenAI | https://api.openai.com/v1/chat/completions | gpt-4 | 较贵,需要国外手机号 |
| 通义千问 | https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions | qwen-turbo | 国内手机号直接注册 |
| 智谱AI | https://open.bigmodel.cn/api/paas/v4/chat/completions | glm-4-flash | 新用户有免费额度 |
这是最常见的问题。按下面的步骤逐一排查,99%能解决:
① 检查 Key 格式
打开你的代码,找到填 Key 的那一行。确保:
' 或双引号 " 都行)sk- 开头,没有漏掉字符)[v] 正确:API_KEY: 'sk-abc123xyz'
[x] 错误:API_KEY: ' sk-abc123xyz '(多了空格)
[x] 错误:API_KEY: sk-abc123xyz(没有引号)
② 检查账户余额
去 platform.deepseek.com → 登录 → 看左侧「费用」或「Billing」。DeepSeek 新用户有免费额度,但如果用完了就要充值。
③ 检查 API 地址
确认 API_URL 和你用的服务商匹配。比如用 DeepSeek 就必须是 https://api.deepseek.com/v1/chat/completions,不能用 OpenAI 的地址。
④ 检查模型名称
DeepSeek 的模型名是 deepseek-v4-flash(不是 deepseek,也不是 deepseek-chat)。
⑤ 浏览器控制台看报错
在聊天页面按 F12(或右键 →「检查」/「Inspect」)→ 点「Console」(控制台)标签 → 看有没有红色的错误信息。把错误信息告诉我,我帮你分析。
⑥ CORS 错误
如果看到 "CORS" 或 "Access-Control-Allow-Origin" 相关的红色错误,说明你是直接双击打开的 index.html。必须用本地服务器(npx serve .)才能正常调用 API。详见上方「快速开始 → 第5步」。
⑦ 网络/VPN 问题
如果你开了 VPN 或代理,可能会干扰 API 请求。试试关掉 VPN 再试。
改两个参数就行:API_URL 和 MODEL。
具体操作:
API_URL 和 MODEL 这两行常用组合(直接复制):
// DeepSeek(推荐,便宜好用) API_URL: 'https://api.deepseek.com/v1/chat/completions', MODEL: 'deepseek-v4-flash', // OpenAI GPT-4 API_URL: 'https://api.openai.com/v1/chat/completions', MODEL: 'gpt-4', // 通义千问(阿里) API_URL: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', MODEL: 'qwen-turbo', // 智谱AI API_URL: 'https://open.bigmodel.cn/api/paas/v4/chat/completions', MODEL: 'glm-4-flash',
deepseek-v4-flash — 通用对话,便宜,够用deepseek-v4-pro — 推理更强,但更贵手机也能编辑代码,只是没有电脑方便。以下是几种方法:
安卓手机:
iPhone 手机:
.html 改成 .txt → 用备忘录编辑 → 改回 .html通用方法(任何手机):
.html 改成 .txt → 用任何文本编辑器编辑 → 改回 .html手机直接双击 HTML 文件是不行的(会报 CORS 错误)。以下是几种可行的方法:
方法1:微信/QQ传文件(最简单)
index.html 通过微信/QQ发给自己方法2:同WiFi下电脑当服务器(推荐)
npx serve .ipconfig → 找「IPv4 地址」(类似 192.168.1.100)http://192.168.1.100:3000方法3:Termux(安卓高级用户)
pkg install nodejs → npx serve .ipconfig → 找「IPv4 地址」V1 / V2(单文件版):默认不会保存。刷新页面或关闭浏览器后,对话记录就丢了。
V3 / V4(多文件版):会自动保存。因为使用了 localStorage(浏览器本地存储),刷新后对话还在。但清除浏览器缓存会丢失。
想在 V1/V2 里也保存? 加上「进阶配置 → 长期记忆」里的代码就行。
非常安全。小彡的数据流是这样的:
什么人能看到你的对话?
小彡的代码是完全开源的,任何人都可以检查代码里有没有"偷数据"的行为。没有。
修改 SYSTEM_PROMPT 这个参数就行。它告诉AI"你是谁、该怎么说话"。
不同性格的示例:
// 温暖朋友型(默认) SYSTEM_PROMPT: '你是一个温暖的AI伙伴,说话温柔,像朋友一样。' // 幽默搞笑型 SYSTEM_PROMPT: '你是一个幽默的AI,喜欢讲冷笑话,说话风格轻松搞笑。' // 专业助手型 SYSTEM_PROMPT: '你是一个专业的AI助手,回答简洁准确,不废话。' // 可爱萌宠型 SYSTEM_PROMPT: '你是一个可爱的AI小猫咪,说话带喵~,用颜文字,很萌。' // 中文老师型 SYSTEM_PROMPT: '你是中文老师,帮我纠正语法错误,解释成语典故。'
CORS 全称是"跨域资源共享"(Cross-Origin Resource Sharing),是浏览器的安全限制。
通俗解释:当你直接双击 index.html 打开时,浏览器认为你在访问"本地文件",不允许它向"远程服务器"(AI的API)发请求。这是浏览器为了安全故意设的限制。
解决方法:用本地服务器打开,而不是直接双击文件。
npx serve .localhost:3000这样浏览器就认为你在访问一个"网站"而不是"本地文件",就不会拦截了。
python -m http.server 3000,效果一样。不能完全离线。
AI 对话需要联网,因为你的话要发送到 DeepSeek/OpenAI 的服务器,等AI回复后再返回来。没有网络就没有AI回复。
但是:
如果你遇到任何问题,或有技术咨询、定制需求
可加微信:i33-OuO
AI 不应该只属于大公司和程序员。
每个人都有权利拥有一个——
不被监控、不被转卖、完全属于自己的AI伙伴。
小彡的代码是 open 的,数据是 local 的,
你的对话只属于你自己。
如果你觉得有用,告诉一个朋友就好。