Files

1205 lines
47 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, maximum-scale=5" />
<meta name="theme-color" content="#f5f5f7" id="themeColor" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="format-detection" content="telephone=no" />
<meta name="description" content="WeChat-AI 用户使用文档:登录、绑定机器人、批准好友、人设、表情包、用户对话 @用户名、命令一览与常见问题。" />
<meta property="og:title" content="使用文档 — WeChat-AI" />
<meta property="og:description" content="从登录到微信对话、@用户名用户对话、人设与表情包的完整使用说明。" />
<meta property="og:image" content="/og.jpg" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:url" content="/docs" />
<meta property="og:type" content="article" />
<meta property="og:site_name" content="WeChat-AI" />
<meta property="og:locale" content="zh_CN" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="使用文档 — WeChat-AI" />
<meta name="twitter:description" content="WeChat-AI 用户使用说明与命令一览。" />
<meta name="twitter:image" content="/og.jpg" />
<link rel="canonical" href="/docs" />
<!-- Robot favicon (inline SVG) -->
<link
rel="icon"
type="image/svg+xml"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0%25' stop-color='%235e5ce6'/%3E%3Cstop offset='100%25' stop-color='%23bf5af2'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='64' height='64' rx='14' fill='url(%23g)'/%3E%3Cg fill='none' stroke='%23fff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='14' y='22' width='36' height='28' rx='8'/%3E%3Ccircle cx='26' cy='36' r='2.4' fill='%23fff' stroke='none'/%3E%3Ccircle cx='38' cy='36' r='2.4' fill='%23fff' stroke='none'/%3E%3Cpath d='M32 14v8M24 50v4M40 50v4M14 34h-4M54 34h-4'/%3E%3C/g%3E%3C/svg%3E"
/>
<!-- Icon font: render-blocking cross-origin CSS that then chain-loads a woff2.
preconnect collapses the DNS+TLS round trips; media=print + onload takes it
off the critical path so first paint never waits on jsdelivr. -->
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css"
media="print" onload="this.media='all';this.onload=null" />
<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css" /></noscript>
<title>使用文档 — WeChat-AI</title>
<script>
(function () {
try {
var k = "wa_theme";
var t = localStorage.getItem(k);
if (t === "system" || (t !== "light" && t !== "dark")) {
if (localStorage.getItem(k) === "system") {
document.documentElement.setAttribute("data-theme-pref", "system");
}
t = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
document.documentElement.setAttribute("data-theme", t);
} catch (e) {
document.documentElement.setAttribute("data-theme", "light");
}
})();
</script>
<style>
:root {
--bg: #f5f5f7;
--bg-elevated: rgba(255, 255, 255, 0.72);
--surface: #ffffff;
--surface-2: #f5f5f7;
--text: #1d1d1f;
--text-secondary: #6e6e73;
--text-tertiary: #86868b;
--separator: rgba(0, 0, 0, 0.08);
--blue: #0071e3;
--blue-hover: #0077ed;
--blue-soft: rgba(0, 113, 227, 0.1);
--green: #34c759;
--green-soft: rgba(52, 199, 89, 0.12);
--orange: #ff9f0a;
--orange-soft: rgba(255, 159, 10, 0.12);
--red: #ff3b30;
--chip: rgba(0, 0, 0, 0.05);
--code-bg: rgba(0, 0, 0, 0.05);
--glow-a: rgba(0, 113, 227, 0.1);
--glow-b: rgba(175, 82, 222, 0.08);
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 24px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
--radius: 18px;
--radius-sm: 12px;
--radius-lg: 22px;
--radius-pill: 980px;
--font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
"Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
sans-serif;
--ease: cubic-bezier(0.25, 0.1, 0.25, 1);
--theme-icon-sun: none;
--theme-icon-moon: inline;
--safe-t: env(safe-area-inset-top, 0px);
--safe-b: env(safe-area-inset-bottom, 0px);
--safe-l: env(safe-area-inset-left, 0px);
--safe-r: env(safe-area-inset-right, 0px);
--nav-h: 56px;
--toc-w: 220px;
/* Fluid page gutter + content width: continuous phone → 4K, no jumps */
--gutter: clamp(16px, 4vw, 28px);
--content-max: 1080px;
/* ---- Liquid glass ----
Layered translucency: tinted base + blurred backdrop + specular rim
(bright top edge, dim bottom edge) that reads as glass thickness
rather than a flat frosted panel. */
--glass-bg: rgba(255, 255, 255, 0.6);
--glass-bg-strong: rgba(255, 255, 255, 0.76);
--glass-blur: 24px;
--glass-hi: rgba(255, 255, 255, 0.95);
--glass-lo: rgba(255, 255, 255, 0.3);
--glass-ring: rgba(0, 0, 0, 0.07);
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 2px 8px rgba(0, 0, 0, 0.05);
--sk-sheen: rgba(255, 255, 255, 0.6);
/* ---- 动态玻璃:滚动深度驱动 ---- */
--glass-rgb: 255, 255, 255;
--glass-a-min: 0.3;
--glass-a-max: 0.76;
--glass-blur-min: 4px;
--glass-blur-max: 24px;
--glass-progress: 0;
--glass-a: calc(var(--glass-a-min) + (var(--glass-a-max) - var(--glass-a-min)) * var(--glass-progress));
--glass-blur-live: calc(var(--glass-blur-min) + (var(--glass-blur-max) - var(--glass-blur-min)) * var(--glass-progress));
}
@media (min-width: 1600px) {
:root { --toc-w: 250px; --content-max: 1220px; }
}
html[data-theme="dark"] {
--bg: #000000;
--bg-elevated: rgba(28, 28, 30, 0.72);
--surface: #1c1c1e;
--surface-2: #2c2c2e;
--text: #f5f5f7;
--text-secondary: #a1a1a6;
--text-tertiary: #6e6e73;
--separator: rgba(255, 255, 255, 0.1);
--blue: #0a84ff;
--blue-hover: #409cff;
--blue-soft: rgba(10, 132, 255, 0.18);
--green: #30d158;
--green-soft: rgba(48, 209, 88, 0.16);
--orange: #ff9f0a;
--orange-soft: rgba(255, 159, 10, 0.18);
--red: #ff453a;
--chip: rgba(255, 255, 255, 0.08);
--code-bg: rgba(255, 255, 255, 0.08);
--glow-a: rgba(10, 132, 255, 0.14);
--glow-b: rgba(191, 90, 242, 0.12);
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
--shadow-md: 0 4px 24px rgba(0, 0, 0, 0.45);
--theme-icon-sun: inline;
--theme-icon-moon: none;
--glass-bg: rgba(30, 30, 32, 0.58);
--glass-bg-strong: rgba(32, 32, 34, 0.78);
--glass-hi: rgba(255, 255, 255, 0.16);
--glass-lo: rgba(255, 255, 255, 0.04);
--glass-ring: rgba(255, 255, 255, 0.1);
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.35);
--sk-sheen: rgba(255, 255, 255, 0.08);
--glass-rgb: 30, 30, 32;
--glass-a-min: 0.32;
--glass-a-max: 0.78;
}
* { box-sizing: border-box; }
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color-scheme: light;
scroll-behavior: smooth;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
scrollbar-width: thin;
scrollbar-color: var(--chip) transparent;
}
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
background: var(--chip);
border-radius: 99px;
border: 2px solid transparent;
background-clip: padding-box;
}
html[data-theme="dark"] { color-scheme: dark; }
body {
margin: 0;
min-height: 100vh;
min-height: 100dvh;
font-family: var(--font);
color: var(--text);
background:
radial-gradient(1200px 600px at 10% -10%, var(--glow-a), transparent 50%),
radial-gradient(900px 500px at 100% 0%, var(--glow-b), transparent 45%),
var(--bg);
line-height: 1.55;
overflow-x: hidden;
}
a { color: var(--blue); text-decoration: none; }
a:hover { color: var(--blue-hover); }
/* ---------- Liquid glass material ---------- */
.glass {
position: relative;
background: var(--glass-bg);
backdrop-filter: blur(var(--glass-blur)) saturate(180%);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(180%);
box-shadow:
inset 0 1px 0 0 var(--glass-hi),
inset 0 -1px 0 0 var(--glass-lo),
var(--glass-shadow);
}
.glass-strong { background: var(--glass-bg-strong); }
/* 吸顶层专用:底下有内容滚过时才逐渐加深,滚动到顶部时近乎透明。
与 iOS 导航栏一致;.glass 保持静态,供不随滚动变化的浮层使用。 */
.glass-dynamic {
background: rgba(var(--glass-rgb), var(--glass-a));
backdrop-filter: blur(var(--glass-blur-live)) saturate(180%);
-webkit-backdrop-filter: blur(var(--glass-blur-live)) saturate(180%);
box-shadow:
inset 0 1px 0 0 rgba(255, 255, 255, calc(0.95 * var(--glass-progress))),
0 1px 0 0 rgba(0, 0, 0, calc(0.08 * var(--glass-progress)));
}
html[data-theme="dark"] .glass-dynamic {
box-shadow:
inset 0 1px 0 0 rgba(255, 255, 255, calc(0.16 * var(--glass-progress))),
0 1px 0 0 rgba(255, 255, 255, calc(0.1 * var(--glass-progress)));
}
/* ---------- 边缘折射(液态玻璃厚度感) ---------- */
.wa-lens-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
/* 折射只在 Chromium 桌面开启,见页面底部的网关脚本。降低模糊让折射看得见——
已经糊成一团的内容没法再折射,两者此消彼长。 */
html.wa-lens .glass,
html.wa-lens .glass-strong {
backdrop-filter: blur(14px) saturate(180%) url(#waGlassLens);
-webkit-backdrop-filter: blur(14px) saturate(180%);
}
/* Specular rim traced around the shape, masked to a 1px ring so the fill
stays clear. Gives the edge-lit look that separates liquid glass from
plain frosted blur. */
.glass-rim::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(145deg, var(--glass-hi) 0%, transparent 38%, transparent 62%, var(--glass-lo) 100%);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
pointer-events: none;
z-index: 3;
}
.glass-rim > * { position: relative; z-index: 2; }
/* ---------- Staggered entrance ---------- */
@keyframes riseIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: none; }
}
.stagger:not(.in-view) > * { opacity: 0; }
.stagger.in-view > * { animation: riseIn 0.42s var(--ease) backwards; }
.stagger > *:nth-child(1) { animation-delay: 0.02s; }
.stagger > *:nth-child(2) { animation-delay: 0.06s; }
.stagger > *:nth-child(3) { animation-delay: 0.1s; }
.stagger > *:nth-child(4) { animation-delay: 0.14s; }
.stagger > *:nth-child(5) { animation-delay: 0.18s; }
.stagger > *:nth-child(6) { animation-delay: 0.22s; }
.stagger > *:nth-child(7) { animation-delay: 0.26s; }
.stagger > *:nth-child(8) { animation-delay: 0.3s; }
.stagger > *:nth-child(n + 9) { animation-delay: 0.34s; }
/* ---------- Button busy spinner ---------- */
.btn.is-busy {
pointer-events: none;
position: relative;
color: transparent !important;
}
.btn.is-busy > * { visibility: hidden; }
.btn.is-busy::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 15px;
height: 15px;
margin: -8px 0 0 -8px;
border-radius: 50%;
border: 2px solid var(--spin-track, rgba(0, 0, 0, 0.2));
border-top-color: var(--spin-head, var(--text));
animation: spin 0.7s linear infinite;
}
.btn-primary.is-busy, .btn-danger.is-busy { --spin-track: rgba(255, 255, 255, 0.35); --spin-head: #fff; }
@keyframes spin { to { transform: rotate(360deg); } }
button, .btn, .theme-toggle {
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
.btn:focus-visible,
.theme-toggle:focus-visible,
a:focus-visible {
outline: 2px solid var(--blue);
outline-offset: 2px;
box-shadow: 0 0 0 4px var(--blue-soft);
}
.wrap {
width: min(var(--content-max), 100%);
margin: 0 auto;
padding: 0 max(var(--gutter), var(--safe-r)) 0 max(var(--gutter), var(--safe-l));
}
.nav {
position: sticky;
top: 0;
z-index: 40;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-height: var(--nav-h);
padding: var(--safe-t) max(20px, var(--safe-r)) 0 max(20px, var(--safe-l));
/* 材质交给 .glass-dynamic:底色与模糊随滚动深度连续变化,
页面在顶部时几乎透明,正文滚到底下后才逐渐变成实心导航条。
这里只保留布局与下边框。 */
border-bottom: 1px solid var(--separator);
}
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--text);
font-weight: 650;
letter-spacing: -0.02em;
}
.brand:hover { color: var(--text); }
.brand-mark {
width: 28px;
height: 28px;
border-radius: 8px;
flex-shrink: 0;
display: inline-grid;
place-items: center;
background: linear-gradient(145deg, #5e5ce6 0%, #bf5af2 100%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.28),
0 2px 8px rgba(94, 92, 230, 0.28);
color: #fff;
font-size: 14px;
line-height: 1;
}
.brand-mark .bi {
font-size: 0.95em;
line-height: 1;
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.15));
}
.nav-actions {
display: flex;
align-items: center;
gap: 8px;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
border: none;
border-radius: var(--radius-pill);
padding: 9px 16px;
font: inherit;
font-size: 14px;
font-weight: 560;
cursor: pointer;
transition: transform .15s var(--ease), background .15s var(--ease), color .15s var(--ease);
text-decoration: none;
white-space: nowrap;
}
.btn:active { transform: scale(0.98); }
.btn-primary { background: var(--blue); color: #fff; }
.btn-primary:hover { background: var(--blue-hover); color: #fff; }
.btn-secondary {
background: var(--chip);
color: var(--text);
}
.btn-secondary:hover { background: var(--blue-soft); color: var(--blue); }
.btn-ghost {
background: transparent;
color: var(--text-secondary);
padding: 9px 12px;
}
.btn-ghost:hover { color: var(--text); background: var(--chip); }
.theme-toggle {
width: 40px;
height: 40px;
border: none;
border-radius: 50%;
background: var(--chip);
color: var(--text);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.theme-toggle .sun { display: var(--theme-icon-sun); }
.theme-toggle .moon { display: var(--theme-icon-moon); }
.docs-layout {
display: grid;
grid-template-columns: var(--toc-w) minmax(0, 1fr);
gap: 28px;
padding: 28px 0 64px;
align-items: start;
}
.toc {
position: sticky;
top: calc(var(--nav-h) + 16px + var(--safe-t));
max-height: calc(100dvh - var(--nav-h) - 32px - var(--safe-t));
overflow: auto;
padding: 14px;
border-radius: var(--radius);
/* Deliberately NOT glass: .docs-layout puts the TOC in its own grid column,
so the article scrolls *beside* it, never behind it. A backdrop-filter
here would blur the flat page background — GPU cost for a muddy grey
panel. Solid surface reads cleaner and keeps the text contrast. */
background: var(--surface);
border: 1px solid var(--separator);
box-shadow: var(--shadow-sm);
}
.toc h2 {
margin: 0 0 10px;
font-size: 12px;
font-weight: 650;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--text-tertiary);
}
.toc a {
display: block;
padding: 7px 10px;
border-radius: 8px;
color: var(--text-secondary);
font-size: 13.5px;
line-height: 1.35;
}
.toc a:hover,
.toc a.active {
background: var(--blue-soft);
color: var(--blue);
}
.toc a.sub { padding-left: 18px; font-size: 13px; }
.doc {
min-width: 0;
}
.doc-hero {
padding: 8px 0 20px;
border-bottom: 1px solid var(--separator);
margin-bottom: 28px;
}
.doc-hero .eyebrow {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: var(--radius-pill);
background: var(--blue-soft);
color: var(--blue);
font-size: 12px;
font-weight: 600;
margin-bottom: 12px;
}
.doc-hero h1 {
margin: 0 0 10px;
font-size: clamp(24px, 5vw, 36px);
letter-spacing: -0.03em;
line-height: 1.15;
}
.doc-hero p {
margin: 0;
color: var(--text-secondary);
font-size: clamp(14.5px, 1.8vw, 16px);
max-width: 52ch;
}
section.sec {
scroll-margin-top: calc(var(--nav-h) + 20px + var(--safe-t));
margin-bottom: 36px;
}
section.sec > h2 {
margin: 0 0 12px;
font-size: clamp(19px, 2.6vw, 22px);
letter-spacing: -0.02em;
display: flex;
align-items: center;
gap: 10px;
}
section.sec > h2 .num {
display: inline-flex;
width: 28px;
height: 28px;
border-radius: 8px;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 700;
background: var(--blue-soft);
color: var(--blue);
flex-shrink: 0;
}
section.sec > h3 {
margin: 20px 0 8px;
font-size: 16px;
letter-spacing: -0.01em;
}
section.sec p {
margin: 0 0 12px;
color: var(--text-secondary);
font-size: 15px;
}
section.sec ul, section.sec ol {
margin: 0 0 14px;
padding-left: 1.25em;
color: var(--text-secondary);
font-size: 15px;
}
section.sec li { margin: 6px 0; }
section.sec li strong { color: var(--text); font-weight: 600; }
.card {
background: var(--surface);
border: 1px solid var(--separator);
border-radius: var(--radius);
padding: 16px 18px;
box-shadow: var(--shadow-sm);
margin: 12px 0 16px;
}
.card.tip {
background: var(--green-soft);
border-color: transparent;
}
.card.warn {
background: var(--orange-soft);
border-color: transparent;
}
.card p:last-child { margin-bottom: 0; }
.card .label {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
color: var(--text-tertiary);
margin-bottom: 6px;
}
.card.tip .label { color: var(--green); }
.card.warn .label { color: var(--orange); }
.steps {
display: grid;
gap: 10px;
margin: 12px 0 16px;
}
.step {
display: grid;
grid-template-columns: 32px 1fr;
gap: 12px;
align-items: start;
padding: 14px 16px;
border-radius: var(--radius-sm);
background: var(--surface);
border: 1px solid var(--separator);
}
.step .n {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: var(--blue);
color: #fff;
font-size: 14px;
font-weight: 700;
}
.step h4 {
margin: 2px 0 4px;
font-size: 15px;
}
.step p {
margin: 0;
font-size: 14px;
color: var(--text-secondary);
}
table.cmd {
width: 100%;
border-collapse: collapse;
font-size: 14px;
margin: 10px 0 16px;
background: var(--surface);
border-radius: var(--radius-sm);
overflow: hidden;
border: 1px solid var(--separator);
}
table.cmd th,
table.cmd td {
text-align: left;
padding: 10px 12px;
border-bottom: 1px solid var(--separator);
vertical-align: top;
}
table.cmd th {
background: var(--surface-2);
color: var(--text-secondary);
font-size: 12px;
font-weight: 650;
letter-spacing: 0.03em;
text-transform: uppercase;
}
table.cmd tr:last-child td { border-bottom: none; }
table.cmd td:first-child { white-space: nowrap; width: 38%; }
code, kbd {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.9em;
background: var(--code-bg);
padding: 0.12em 0.4em;
border-radius: 6px;
color: var(--text);
}
kbd {
border: 1px solid var(--separator);
box-shadow: 0 1px 0 var(--separator);
}
.grid-2 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
gap: 12px;
margin: 12px 0 16px;
}
.mini {
padding: 14px 16px;
border-radius: var(--radius-sm);
background: var(--surface);
border: 1px solid var(--separator);
}
.mini h4 {
margin: 0 0 6px;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.mini p {
margin: 0;
font-size: 13.5px;
color: var(--text-secondary);
}
.mini i { color: var(--blue); }
footer {
border-top: 1px solid var(--separator);
padding: 24px 0 calc(32px + var(--safe-b));
color: var(--text-tertiary);
font-size: 13px;
}
footer .foot-inner {
display: flex;
flex-wrap: wrap;
gap: 12px 24px;
justify-content: space-between;
align-items: center;
}
footer a { color: var(--text-secondary); }
footer a:hover { color: var(--blue); }
@media (max-width: 860px) {
:root { --nav-h: 52px; }
.docs-layout {
grid-template-columns: 1fr;
gap: 16px;
padding-top: 18px;
padding-bottom: calc(40px + var(--safe-b));
}
.toc {
position: static;
max-height: none;
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 10px;
}
.toc h2 { width: 100%; margin-bottom: 4px; }
.toc a {
padding: 8px 12px;
background: var(--chip);
min-height: 36px;
display: inline-flex;
align-items: center;
}
.toc a.sub { padding-left: 10px; }
.nav {
padding-left: max(12px, var(--safe-l));
padding-right: max(12px, var(--safe-r));
gap: 8px;
}
.nav-actions { gap: 6px; }
.nav-actions .btn-ghost .btn-label { display: none; }
.nav-actions .btn-ghost {
padding: 0;
min-width: 36px;
min-height: 36px;
width: 36px;
height: 36px;
border-radius: 50%;
}
.theme-toggle { width: 36px; height: 36px; flex-shrink: 0; }
.brand span {
max-width: 42vw;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
table.cmd {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
}
@media (max-width: 380px) {
.brand span { max-width: 96px; }
.nav-actions .btn-primary { padding: 8px 12px; font-size: 13px; min-height: 36px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
html { scroll-behavior: auto; }
/* Entrance stagger must never gate visibility on JS for these users. */
.stagger:not(.in-view) > * { opacity: 1 !important; }
}
@media (prefers-contrast: more) {
:root {
--separator: rgba(0, 0, 0, 0.24);
--text-secondary: #515154;
--text-tertiary: #6e6e73;
}
html[data-theme="dark"] {
--separator: rgba(255, 255, 255, 0.3);
--text-secondary: #c7c7cc;
--text-tertiary: #98989d;
}
}
@media (prefers-reduced-transparency: reduce) {
:root { --bg-elevated: #f5f5f7; }
html[data-theme="dark"] { --bg-elevated: #1c1c1e; }
/* 动态玻璃降级为实心:不再透明,也不再随滚动改变模糊与底色。 */
.nav,
.glass-dynamic {
background: var(--bg-elevated) !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
/* 折射同样关闭:html.wa-lens 那两条把 backdrop-filter 又写了回来,必须覆盖。 */
.glass,
.glass-strong,
html.wa-lens .glass,
html.wa-lens .glass-strong {
background: var(--surface) !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
.glass-rim::before { display: none !important; }
}
</style>
<noscript>
<style>
/* The stagger reveal hides children until JS adds .in-view — without JS
that content would be permanently invisible. */
.stagger:not(.in-view) > * { opacity: 1 !important; animation: none !important; }
/* 无脚本时滞动进度永远为 0,吸顶栏会停在 0.3 底色 + 4px 模糊,
连分隔线都没有,正文会直接透过去。直接拉满,等同于改造前的静态玻璃。 */
:root { --glass-progress: 1; }
</style>
</noscript>
</head>
<body>
<svg class="wa-lens-defs" width="0" height="0" aria-hidden="true" focusable="false"><defs>
<!-- 边缘折射位移图:R 通道控制横向取样、G 通道控制纵向。中心为中性值 128 故不位移,
四边渐变让取样点向内偏移,形成玻璃厚度的透镜感。
color-interpolation-filters="sRGB" 必须保留:默认的 linearRGB 会把中性点 128
换算成 ~55,导致整个背景被均匀推歪。 -->
<filter id="waGlassLens" x="0%" y="0%" width="100%" height="100%" color-interpolation-filters="sRGB">
<feImage href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' preserveAspectRatio='none'><defs><linearGradient id='x' x1='0' x2='1'><stop offset='0' stop-color='rgb(255,0,0)'/><stop offset='0.25' stop-color='rgb(128,0,0)'/><stop offset='0.75' stop-color='rgb(128,0,0)'/><stop offset='1' stop-color='rgb(0,0,0)'/></linearGradient><linearGradient id='y' x1='0' y1='0' x2='0' y2='1'><stop offset='0' stop-color='rgb(0,255,0)'/><stop offset='0.3' stop-color='rgb(0,128,0)'/><stop offset='0.7' stop-color='rgb(0,128,0)'/><stop offset='1' stop-color='rgb(0,0,0)'/></linearGradient></defs><rect width='100' height='100' fill='url(%23x)'/><rect width='100' height='100' fill='url(%23y)' style='mix-blend-mode:plus-lighter'/></svg>" preserveAspectRatio="none" result="wa-map"/>
<feDisplacementMap in="SourceGraphic" in2="wa-map" scale="14" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</defs></svg>
<header class="nav glass-dynamic">
<a class="brand" href="/">
<span class="brand-mark" aria-hidden="true"><i class="bi bi-robot"></i></span>
<span>WeChat-AI</span>
</a>
<div class="nav-actions">
<button class="theme-toggle" id="themeToggle" type="button" title="切换主题" aria-label="切换主题">
<i class="bi bi-sun-fill sun" aria-hidden="true"></i>
<i class="bi bi-moon-fill moon" aria-hidden="true"></i>
</button>
<a class="btn btn-ghost" href="/"><span class="btn-label">首页</span></a>
<a class="btn btn-primary" href="/app">用户中心</a>
</div>
</header>
<div class="wrap">
<div class="docs-layout">
<aside class="toc" aria-label="目录">
<h2>目录</h2>
<a href="#intro">简介</a>
<a href="#quickstart">快速开始</a>
<a href="#bots">机器人</a>
<a href="#peers">批准好友</a>
<a href="#persona">人设</a>
<a href="#stickers">表情包</a>
<a href="#p2p">用户对话 @</a>
<a href="#p2p" class="sub">绑定身份</a>
<a href="#p2p-chat" class="sub">发起 / 同意</a>
<a href="#block">黑名单</a>
<a href="#commands">微信命令</a>
<a href="#proactive">主动联系</a>
<a href="#faq">常见问题</a>
</aside>
<article class="doc">
<header class="doc-hero">
<div class="eyebrow"><i class="bi bi-book" aria-hidden="true"></i> 使用文档</div>
<h1>WeChat-AI 用户手册</h1>
<p>
说明如何登录、绑定微信机器人、和好友角色扮演对话,以及通过
<code>@LINUX DO 用户名</code> 与其他用户私聊。面向普通用户,无需写代码。
</p>
</header>
<section class="sec" id="intro">
<h2><span class="num">1</span> 简介</h2>
<p>
WeChat-AI 是一个<strong>自托管微信角色扮演服务</strong>:你用
<strong>LINUX DO</strong> 账号登录网页,扫码绑定自己的微信 ClawBot,
批准好友后即可在微信里和 AI 角色聊天。系统还会支持已绑定用户之间的
<strong>用户对话</strong>(机器人中继,不经过 AI)。
</p>
<div class="grid-2 stagger">
<div class="mini">
<h4><i class="bi bi-window"></i> 网页端</h4>
<p>用户中心 <a href="/app">/app</a>:机器人、用户对话、人设、表情、绑定码、黑名单。</p>
</div>
<div class="mini">
<h4><i class="bi bi-chat-dots"></i> 微信端</h4>
<p>给机器人发文字即可聊天;可用 <code>/绑定</code><code>@用户名</code> 等命令。</p>
</div>
</div>
</section>
<section class="sec" id="quickstart">
<h2><span class="num">2</span> 快速开始</h2>
<div class="steps">
<div class="step">
<div class="n">1</div>
<div>
<h4>登录</h4>
<p>打开 <a href="/app">用户中心</a>,使用 <strong>LINUX DO</strong> 账号授权登录。</p>
</div>
</div>
<div class="step">
<div class="n">2</div>
<div>
<h4>扫码添加机器人</h4>
<p>在「我的机器人」点击<strong>扫码添加</strong>,用微信扫码完成 ClawBot 绑定。</p>
</div>
</div>
<div class="step">
<div class="n">3</div>
<div>
<h4>让好友私聊机器人</h4>
<p>好友在微信中找到该机器人并发一条消息;你在用户中心<strong>批准</strong>后即可对话。</p>
</div>
</div>
<div class="step">
<div class="n">4</div>
<div>
<h4>(可选)分配人设</h4>
<p>从人设广场添加角色,在用户列表中为人设分配给具体好友。</p>
</div>
</div>
</div>
<div class="card tip">
<div class="label">提示</div>
<p>默认仅<strong>已批准</strong>的微信用户可以和 AI 聊天。未批准时机器人会提示联系主人开通。</p>
</div>
</section>
<section class="sec" id="bots">
<h2><span class="num">3</span> 管理机器人</h2>
<ul>
<li><strong>扫码添加</strong>:创建新的微信机器人账号。</li>
<li><strong>重新扫码</strong>:token 失效时刷新凭证;会保留已批准用户、人设分配与记忆。</li>
<li><strong>改名 / 删除</strong>:在机器人卡片或列表中操作。</li>
</ul>
<div class="card warn">
<div class="label">注意</div>
<p>请遵守腾讯微信 / ClawBot 相关使用条款。个人 Bot 可能有限流与风控;建议控制开放范围(白名单批准)。</p>
</div>
</section>
<section class="sec" id="peers">
<h2><span class="num">4</span> 批准微信好友</h2>
<ol>
<li>对方给机器人发任意消息后,会出现在「微信用户」列表。</li>
<li>点击<strong>批准</strong>,可选同时分配人设。</li>
<li>对方即可开始角色扮演对话(AI 可能分多条气泡回复)。</li>
</ol>
<p>可在列表中查看记忆、清空记忆,或开启「允许主动」(见下文主动联系)。</p>
</section>
<section class="sec" id="persona">
<h2><span class="num">5</span> 人设</h2>
<ul>
<li><strong>人设广场</strong>:按热度浏览公开角色;可<strong>试聊</strong>(网页限量体验,不写入微信)、<strong>加入库</strong><strong>改编</strong>(复制为你的私有草稿,可自由改写)。</li>
<li><strong>我的人设</strong>:创建 / 编辑自己的角色提示词;可设为公开或私有;改编来的人设会标注「改编自」。</li>
<li><strong>分配</strong>:在微信用户行选择人设,仅对应该用户生效;分配次数会提升广场热度。</li>
</ul>
<p>微信内发送 <code>/角色</code> <strong>不会</strong>切换角色——角色只能由主人在后台分配。</p>
<p>试聊有每日与单会话条数上限,计入 Token 用量;管理员可通过环境变量关闭。</p>
</section>
<section class="sec" id="stickers">
<h2><span class="num">6</span> 表情包</h2>
<ul>
<li>在表情包广场浏览、加入自己的库;也可上传投稿(公开需管理员审核)。</li>
<li>对话中 AI 可能引用库中的 slug 以图片表情回复(需主人库中可用)。</li>
</ul>
</section>
<section class="sec" id="p2p">
<h2><span class="num">7</span> 用户对话(@LINUX DO 用户名)</h2>
<p>
两个都完成「微信身份绑定」的用户,可以在微信里通过机器人
<strong>互相发消息</strong>(中继,不调用 AI)。用户名即 LINUX DO 的
<code>username</code>
</p>
<h3>7.1 绑定身份</h3>
<div class="steps">
<div class="step">
<div class="n">1</div>
<div>
<h4>生成绑定码</h4>
<p>用户中心 → <strong>用户对话</strong> → 微信身份绑定 → 生成绑定码(约 10 分钟有效)。</p>
</div>
</div>
<div class="step">
<div class="n">2</div>
<div>
<h4>微信确认</h4>
<p><strong>任意已绑定的机器人</strong>发送:<code>/绑定 ABC123</code>(替换为你的码)。</p>
</div>
</div>
<div class="step">
<div class="n">3</div>
<div>
<h4>先聊一句</h4>
<p>绑定后请给机器人发过至少一条消息,系统才有推送凭证;否则别人 @ 你时会提示「不可达」。</p>
</div>
</div>
</div>
<div class="card tip">
<div class="label">查询身份</div>
<p>微信发送 <code>/我的身份</code> 可查看当前绑定的 LINUX DO 用户名与会话状态。</p>
</div>
<h3 id="p2p-chat">7.2 发起与聊天</h3>
<ol>
<li>整条消息只发:<code>@对方用户名</code>(例如 <code>@alice</code>)。</li>
<li>对方收到「对话请求」,回复 <code>/同意</code><code>/拒绝</code></li>
<li>同意后双方直接发文字;对方会看到类似 <code>[alice] 你好</code> 的前缀。</li>
<li>任一方发送 <code>/断开</code> 结束;长时间无消息(默认约 30 分钟)也会自动结束,并恢复与 AI 角色扮演。</li>
</ol>
<div class="card warn">
<div class="label">限制</div>
<ul style="margin-bottom:0">
<li>会话中仅支持<strong>文字</strong>;图片 / 语音会提示不支持。</li>
<li><code>hello @alice</code> 这类夹在句子里的写法<strong>不会</strong>发起请求。</li>
<li>每个账号同一时间通常只能有一个进行中的请求或会话。</li>
</ul>
</div>
</section>
<section class="sec" id="block">
<h2><span class="num">8</span> 黑名单</h2>
<p>不想被某人 @ 时可以拉黑(双向无法再建立用户对话)。</p>
<ul>
<li><strong>网页</strong>:用户中心 → 用户对话 → 黑名单 → 输入用户名拉黑 / 取消拉黑。</li>
<li><strong>微信</strong><code>/拉黑 用户名</code><code>/取消拉黑 用户名</code><code>/黑名单</code></li>
</ul>
<p>拉黑会结束与对方进行中的用户对话请求或会话。</p>
</section>
<section class="sec" id="commands">
<h2><span class="num">9</span> 微信命令一览</h2>
<table class="cmd">
<thead>
<tr><th>命令</th><th>说明</th></tr>
</thead>
<tbody>
<tr><td><code>/绑定 CODE</code></td><td>认领用户中心生成的绑定码</td></tr>
<tr><td><code>/解绑</code></td><td>解除微信与 LINUX DO 的绑定</td></tr>
<tr><td><code>/我的身份</code></td><td>查看绑定用户名与当前状态</td></tr>
<tr><td><code>@用户名</code></td><td>整条消息:向对方发起对话请求</td></tr>
<tr><td><code>/同意</code></td><td>同意入站对话请求</td></tr>
<tr><td><code>/拒绝</code></td><td>拒绝入站对话请求</td></tr>
<tr><td><code>/取消请求</code></td><td>取消自己发出的请求</td></tr>
<tr><td><code>/断开</code></td><td>结束当前用户对话,恢复 AI</td></tr>
<tr><td><code>/拉黑 用户名</code></td><td>拉黑(可选前缀 <code>@</code></td></tr>
<tr><td><code>/取消拉黑 用户名</code></td><td>移出黑名单</td></tr>
<tr><td><code>/黑名单</code></td><td>查看黑名单列表</td></tr>
<tr><td><code>/角色 …</code></td><td>不支持自助切换;由主人在后台分配</td></tr>
</tbody>
</table>
<p>未匹配上述命令、且不在用户会话中时,消息会交给 AI 角色扮演回复(需已批准)。</p>
</section>
<section class="sec" id="proactive">
<h2><span class="num">10</span> 主动联系(可选)</h2>
<p>若站点开启了主动联系,空闲一段时间后角色可能主动给用户发消息。</p>
<ol>
<li>机器人卡片中打开「主动找用户聊天」并保存参数。</li>
<li>对具体微信用户勾选「允许主动」。</li>
<li>对方须曾经聊过机器人(有会话凭证)。</li>
</ol>
<p>具体是否开启由站点管理员配置;个人用户仅能控制自己的机器人与好友开关。</p>
</section>
<section class="sec" id="faq">
<h2><span class="num">11</span> 常见问题</h2>
<h3>对方收不到我的 @ 请求?</h3>
<ul>
<li>双方是否都完成了 <code>/绑定</code></li>
<li>对方是否用绑定的微信给机器人发过消息(可达)?</li>
<li>是否在黑名单中?用户名是否拼写正确(不区分大小写)?</li>
<li>对方是否正忙(已有请求 / 会话)?</li>
</ul>
<h3>AI 不回复?</h3>
<ul>
<li>是否已在用户中心批准该微信用户?</li>
<li>是否在用户对话会话中?会话内不会走 AI,需先 <code>/断开</code></li>
<li>发得太快会触发限流提示。</li>
</ul>
<h3>能发图片 / 语音吗?</h3>
<ul>
<li>
<strong>语音</strong>:微信自带的语音转文字会一并送到机器人,所以带转写的语音就当文字聊。
</li>
<li>
<strong>图片</strong>:站点开启「图片理解」后,机器人能看图并据实描述;未开启时会直接告诉你看不了。
</li>
<li><strong>视频 / 文件</strong>:暂时看不了,机器人会说明并请你用文字描述。</li>
</ul>
<h3>绑定码无效?</h3>
<p>绑定码有时效且只能用一次。请在用户中心重新生成,再发 <code>/绑定 新码</code></p>
<h3>机器人突然不回?</h3>
<p>可能是微信会话过期。在用户中心对该机器人使用<strong>重新扫码</strong>刷新凭证(数据会保留)。</p>
<div class="card" style="margin-top:20px">
<div class="label">需要帮助</div>
<p style="margin:0">
进入 <a href="/app">用户中心</a> 管理机器人与绑定;站点首页见
<a href="/">/</a>。管理员后台为 <a href="/admin">/admin</a>(需管理员权限)。
</p>
</div>
</section>
</article>
</div>
</div>
<footer>
<div class="wrap foot-inner">
<span>WeChat-AI · 用户使用文档</span>
<span>
<a href="/">首页</a> ·
<a href="/docs">文档</a> ·
<a href="/app">用户中心</a>
</span>
</div>
</footer>
<script>
(function () {
var root = document.documentElement;
var btn = document.getElementById("themeToggle");
function applyTheme(t) {
root.setAttribute("data-theme", t);
var meta = document.getElementById("themeColor");
if (meta) meta.setAttribute("content", t === "dark" ? "#000000" : "#f5f5f7");
}
function current() {
return root.getAttribute("data-theme") === "dark" ? "dark" : "light";
}
if (btn) {
btn.addEventListener("click", function () {
var next = current() === "dark" ? "light" : "dark";
try { localStorage.setItem("wa_theme", next); } catch (e) {}
root.removeAttribute("data-theme-pref");
applyTheme(next);
});
}
applyTheme(current());
// TOC active section
var links = Array.prototype.slice.call(document.querySelectorAll(".toc a[href^='#']"));
var map = {};
links.forEach(function (a) {
var id = a.getAttribute("href").slice(1);
if (id) map[id] = a;
});
var sections = Object.keys(map)
.map(function (id) { return document.getElementById(id); })
.filter(Boolean);
function setActive() {
var y = window.scrollY + 100;
var cur = sections[0];
for (var i = 0; i < sections.length; i++) {
if (sections[i].offsetTop <= y) cur = sections[i];
}
links.forEach(function (a) { a.classList.remove("active"); });
if (cur && map[cur.id]) map[cur.id].classList.add("active");
var nav = document.querySelector(".nav");
if (nav) nav.classList.toggle("scrolled", window.scrollY > 4);
}
window.addEventListener("scroll", setActive, { passive: true });
setActive();
})();
// Staggered entrance: reveal .stagger groups when they scroll into view.
// Kept in its own IIFE + try/catch: .stagger children sit at opacity:0 until
// "in-view" lands, so a throw anywhere above (or in the IO constructor) must
// not be able to strand page content invisible.
(function () {
var staggers = Array.prototype.slice.call(document.querySelectorAll(".stagger"));
function revealAll() {
staggers.forEach(function (el) { el.classList.add("in-view"); });
}
try {
if (!("IntersectionObserver" in window)) { revealAll(); return; }
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (en) {
if (en.isIntersecting) {
en.target.classList.add("in-view");
io.unobserve(en.target);
}
});
},
{ rootMargin: "0px 0px -10% 0px", threshold: 0.05 }
);
staggers.forEach(function (el) { io.observe(el); });
} catch (e) {
revealAll();
}
})();
/* 只有 Chromium 会真正渲染 backdrop-filter 里的 SVG 滤镜引用;Safari / Firefox
语法能解析但渲染为空,会把整条 backdrop-filter 作废——必须运行时判定而不是 @supports。
navigator.userAgentData 目前仅 Chromium 实现,用作引擎判定。
再要求 pointer:fine:位移滤镜在滚动时每帧重算,中低端手机上代价太高。 */
(function () {
try {
var fine = matchMedia("(pointer: fine)").matches;
var chromium = !!navigator.userAgentData;
if (chromium && fine && CSS.supports("backdrop-filter", "url(#x)")) {
document.documentElement.classList.add("wa-lens");
}
} catch (e) {}
})();
/* 把滚动深度写成 0..1 的进度值,驱动吸顶层的模糊与底色。rAF 节流,passive 监听。 */
(function () {
var root = document.documentElement;
var ticking = false;
function apply() {
ticking = false;
var y = window.scrollY || root.scrollTop || 0;
root.style.setProperty("--glass-progress", Math.min(1, y / 80).toFixed(3));
}
addEventListener(
"scroll",
function () {
if (!ticking) {
ticking = true;
requestAnimationFrame(apply);
}
},
{ passive: true },
);
apply();
})();
</script>
</body>
</html>