初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

+312
View File
@@ -0,0 +1,312 @@
<!doctype html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>魂镜 · 眉批设计预览</title>
<link rel="stylesheet" href="tokens.css" />
<link rel="stylesheet" href="_base.css" />
<link rel="stylesheet" href="_chrome.css" />
<style>
/* ============================================================
* 铜镜鉴心 v2.1 — 眉批(Author's Note · 按 depth 注入对话流)
* 功能融合 §9.1:作为 context_transformer,按深度 +
* 角色把批注插入对话流。签名——书眉批注:批注夹在对话消息间,
* 左侧 depth 标尺定位,铜绿色夹批。
*
* 完整功能:
* 按角色筛选(system / user / assistant)
* 按人物 scope 过滤
* 行内编辑 · 深度调节 · 批量启停
* 模板速建 · 注入预览(含多批同 depth 层叠)
* ============================================================ */
.page-header .spacer { flex: 1; }
.section-label .glyph { color: var(--gilt); opacity: 0.85; font-family: "Songti SC", serif; font-size: 14px; }
.section-label .sub { color: var(--fg-dim); opacity: 0.7; letter-spacing: 0.04em; }
/* ── 筛选条 ── */
.filter-bar { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-bottom: 14px; }
.filter-chip { font-size: 12px; padding: 5px 12px; border-radius: 999px; border: 1px solid var(--rule); color: var(--fg-dim); cursor: pointer; background: var(--surface); transition: all var(--duration-fast); font-family: inherit; }
.filter-chip:hover { color: var(--fg); border-color: var(--patina-dim); }
.filter-chip.active { color: var(--patina); border-color: var(--patina-dim); background: var(--patina-wash); }
.filter-chip .count { font-family: "JetBrains Mono", monospace; font-size: 10px; opacity: 0.7; margin-left: 4px; }
/* ── 人物 scope 选择 ── */
.scope-select { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.scope-select .lb { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); letter-spacing: 0.04em; }
.char-pill-scope { display: inline-flex; align-items: center; gap: 5px; padding: 4px 11px; border-radius: 999px; border: 1px solid var(--patina-dim); background: var(--patina-wash); color: var(--patina); font-size: 12px; cursor: pointer; }
.char-pill-scope.all { border-color: var(--rule); background: var(--surface); color: var(--fg-dim); }
.char-pill-scope .avatar-mini { width: 16px; height: 16px; border-radius: 50%; background: var(--patina); color: var(--ink); display: inline-flex; align-items: center; justify-content: center; font-family: "Songti SC", serif; font-size: 9px; font-weight: 700; }
/* ═══ 眉批卡(列表)═══ */
.note-list { display: flex; flex-direction: column; gap: 10px; }
.note-card { display: grid; grid-template-columns: 1fr 52px auto auto; gap: 12px; align-items: center; border: 1px solid var(--rule); border-radius: 10px; background: var(--surface); padding: 13px 16px; transition: border-color var(--duration-fast); }
.note-card:hover { border-color: var(--patina-dim); }
.note-card.off { opacity: 0.5; }
.note-card.off .note-text { text-decoration: line-through; text-decoration-color: var(--fg-faint); }
.note-text { font-family: "Songti SC", serif; font-size: 14.5px; color: var(--fg); line-height: 1.5; cursor: text; }
.note-text:focus { outline: none; border-bottom: 1px dashed var(--patina-dim); }
.note-text .role-tag { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--gilt); border: 1px solid var(--gilt-dim); border-radius: 3px; padding: 1px 5px; margin-right: 7px; vertical-align: 2px; }
.note-text .role-tag.user { color: var(--cinnabar); border-color: var(--cinnabar-dim); }
.note-text .role-tag.assistant { color: var(--patina); border-color: var(--patina-dim); }
.note-text .scope-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; vertical-align: 1px; }
.note-text .scope-dot.global { background: var(--gilt); }
.note-text .scope-dot.char { background: var(--patina); }
.depth-chip { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--patina); border: 1px solid var(--patina-dim); border-radius: 5px; padding: 4px 9px; background: var(--patina-wash); white-space: nowrap; cursor: pointer; }
.role-select { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-dim); background: var(--glass); border: 1px solid var(--rule); border-radius: 6px; padding: 4px 8px; cursor: pointer; }
.depth-input { font-family: "JetBrains Mono", monospace; font-size: 11px; color: var(--fg-dim); background: var(--glass); border: 1px solid var(--rule); border-radius: 6px; padding: 4px 8px; width: 48px; text-align: center; }
.depth-input:focus, .role-select:focus { outline: none; border-color: var(--patina-dim); }
.toggle { width: 34px; height: 19px; border-radius: 999px; background: var(--rule); position: relative; cursor: pointer; transition: background var(--duration-fast); flex-shrink: 0; }
.toggle::after { content: ""; position: absolute; top: 2px; left: 2px; width: 15px; height: 15px; border-radius: 50%; background: var(--fg-faint); transition: transform var(--duration-fast), background var(--duration-fast); }
.toggle.on { background: var(--patina-dim); }
.toggle.on::after { transform: translateX(15px); background: var(--patina); }
.add-note { border: 1px dashed var(--rule); border-radius: 10px; padding: 13px 16px; color: var(--fg-faint); font-size: 13px; cursor: pointer; text-align: center; transition: all var(--duration-fast); }
.add-note:hover { border-color: var(--patina-dim); color: var(--patina); }
/* ── 批量操作栏 ── */
.batch-bar { display: flex; gap: 8px; align-items: center; padding: 8px 0; }
.batch-bar .hint { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); margin-right: auto; }
/* ── 模板速建 ── */
.templates { display: flex; gap: 8px; flex-wrap: wrap; }
.tpl-chip { font-size: 11.5px; padding: 6px 12px; border-radius: 999px; border: 1px solid var(--rule); background: var(--surface); color: var(--fg-dim); cursor: pointer; transition: all var(--duration-fast); white-space: nowrap; }
.tpl-chip:hover { border-color: var(--patina-dim); color: var(--fg); background: var(--patina-wash); }
.tpl-chip .glyph { margin-right: 4px; }
/* ═══ depth 注入预览(签名:书眉批注,depth 标尺 + 夹批)═══ */
.depth-view { border: 1px solid var(--gilt-dim); border-radius: 10px; background: var(--face-sheen), var(--surface); box-shadow: inset 0 1px 0 var(--face-in-top), inset 0 -1px 0 var(--face-in-bot), var(--shadow); overflow: hidden; }
.depth-view-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--rule); }
.depth-view-head .t { font-family: "Songti SC", serif; font-size: 14px; font-weight: 600; color: var(--fg); }
.depth-view-head .hint { font-family: "JetBrains Mono", monospace; font-size: 10.5px; color: var(--fg-faint); letter-spacing: 0.04em; }
.stream { padding: 16px 16px 20px; display: flex; flex-direction: column; gap: 6px; position: relative; }
.stream-row { display: grid; grid-template-columns: 40px 1fr; gap: 12px; align-items: stretch; }
.depth-mark { font-family: "JetBrains Mono", monospace; font-size: 10px; color: var(--fg-faint); text-align: right; padding-top: 7px; letter-spacing: 0.04em; }
.msg { padding: 8px 13px; border-radius: 8px; font-size: 13px; line-height: 1.55; }
.msg .who { font-family: "JetBrains Mono", monospace; font-size: 9.5px; letter-spacing: 0.12em; text-transform: uppercase; margin-bottom: 3px; }
.msg.ai { background: var(--surface-hi); color: var(--fg); border: 1px solid var(--rule); }
.msg.ai .who { color: var(--patina); }
.msg.you { background: transparent; color: var(--fg-dim); border-right: 2px solid var(--gilt); padding-right: 11px; text-align: right; }
.msg.you .who { color: var(--gilt); }
/* 眉批夹批:从左侧 depth 标尺伸入,铜绿色,像书眉批注 */
.stream-row.note { grid-template-columns: 40px 1fr; }
.stream-row.note .depth-mark { color: var(--patina); padding-top: 5px; }
.stream-row.note .note-inline { background: var(--patina-wash); border: 1px solid var(--patina-dim); border-left: 3px solid var(--patina); border-radius: 0 6px 6px 0; padding: 7px 13px; font-family: "Songti SC", serif; font-size: 13px; color: var(--patina); display: flex; gap: 9px; align-items: baseline; }
.stream-row.note .note-inline .seal { font-family: "JetBrains Mono", monospace; font-size: 9px; letter-spacing: 0.1em; color: var(--patina); border: 1px solid var(--patina-dim); border-radius: 3px; padding: 1px 5px; flex-shrink: 0; }
.stream-row.note .note-inline .txt { color: var(--fg); }
.legend { display: flex; gap: 16px; padding: 10px 16px; border-top: 1px solid var(--rule); font-size: 11.5px; color: var(--muted); flex-wrap: wrap; }
.legend .item { display: inline-flex; align-items: center; gap: 6px; }
.legend .sw { width: 11px; height: 11px; border-radius: 3px; }
.pipeline-note { font-size: 12.5px; color: var(--fg-dim); line-height: 1.6; }
.pipeline-note .em { color: var(--fg); }
.pipeline-note code { font-family: "JetBrains Mono", monospace; font-size: 11.5px; color: var(--gilt); }
@media (max-width: 900px) { .note-card { grid-template-columns: 1fr; gap: 8px; } }
</style>
</head>
<body>
<div class="app-shell">
<div class="sidebar-mount" data-active="眉批"></div>
<!-- ══ main ══ -->
<div class="main-area">
<div class="page-header">
<div>
<div class="page-title">眉批</div>
<div class="page-sub">Author's Note · 按 depth 注入对话流</div>
</div>
<div class="spacer"></div>
<span class="stat-pill">3 条生效 · 1 条停用</span>
</div>
<div class="page-body">
<!-- 筛选 + 作用域 -->
<section>
<div class="section-label"><span class="glyph">批</span>眉批清单 <span class="sub">— 每条按 depth + role + scope 插入对话流</span></div>
<div class="filter-bar">
<span class="filter-chip active">全部 <span class="count">4</span></span>
<span class="filter-chip">system <span class="count">3</span></span>
<span class="filter-chip">user <span class="count">1</span></span>
<span class="filter-chip">assistant <span class="count">0</span></span>
<span class="filter-chip" style="opacity:0.6;">生效中 <span class="count">3</span></span>
<span class="filter-chip" style="opacity:0.6;">已停用 <span class="count">1</span></span>
<span class="scope-select">
<span class="lb">作用域</span>
<span class="char-pill-scope all">全人物</span>
<span class="char-pill-scope"><span class="avatar-mini">狂</span>狂三</span>
</span>
</div>
<!-- 批量操作 -->
<div class="batch-bar">
<span class="hint">选中 0 条 · 批量操作</span>
<button class="btn btn-outline btn-sm">全部启用</button>
<button class="btn btn-outline btn-sm">全部停用</button>
<button class="btn btn-outline btn-sm" style="color:var(--cinnabar);border-color:var(--cinnabar-dim);">删除选中</button>
</div>
<!-- 模板速建 -->
<div style="margin-bottom:14px;">
<span style="font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--fg-faint);letter-spacing:0.08em;display:block;margin-bottom:6px;">速建模板</span>
<div class="templates">
<span class="tpl-chip" onclick="addNote()"><span class="glyph">+</span> 空白眉批</span>
<span class="tpl-chip"><span class="glyph">📏</span> 保持简洁,每轮消息不超过三句。</span>
<span class="tpl-chip"><span class="glyph">🎭</span> 用宋体的节奏,留白比填满好。</span>
<span class="tpl-chip"><span class="glyph">🪞</span> 本节气氛沉静,少用感叹与反问。</span>
<span class="tpl-chip"><span class="glyph">⚡</span> 用户赶工中 · 语气再软一点 · 不问进度</span>
</div>
</div>
<!-- 眉批列表 -->
<div class="note-list" id="noteList"></div>
</section>
<!-- depth 注入预览(签名)-->
<section>
<div class="section-label"><span class="glyph">注</span>注入预览 <span class="sub">— depth = 距对话末尾第 N 条之前;调 depth 看夹批位置</span></div>
<div class="depth-view">
<div class="depth-view-head">
<span class="t">对话流 · 书眉批注</span>
<span class="hint">D0 = 最末消息 · 数字越大越深</span>
</div>
<div class="stream" id="stream"></div>
<div class="legend">
<span class="item"><span class="sw" style="background:var(--surface-hi);border:1px solid var(--rule)"></span>对话消息</span>
<span class="item"><span class="sw" style="background:var(--patina-wash);border:1px solid var(--patina-dim);border-left:3px solid var(--patina)"></span>眉批夹批(按 depth 插入)</span>
<span class="item"><span class="sw" style="background:transparent;border-right:2px solid var(--gilt)"></span>你的发言</span>
</div>
</div>
</section>
<!-- 说明 -->
<section>
<div class="section-label"><span class="glyph">理</span>语义</div>
<p class="pipeline-note">
眉批是 <span class="em">context_transformer</span>:不进道体,只在本轮对话流里插入。<code>depth</code> = 插入点距对话末尾的条数(<code>0</code> = 最末,数字越大越靠前)。<code>role</code> 可选 <code>system</code>(主导约束)/ <code>user</code>(范例)/ <code>assistant</code>(风格示范)。<code>scope</code> 可选全局(全人物生效)或指定人物(仅该人物对话时注入)。多条眉批同 depth 时按创建顺序层叠。
<code>extensions.depth_prompt</code> 直接映射本结构。与符文引擎(<code>{{char}}</code> 显化)正交——眉批管"插在哪、以什么角色",符文管"占位符换成什么"。
</p>
</section>
</div>
</div>
</div>
<script>
/* 眉批数据(真应用接 author_notes,此处示意)*/
let NOTES = [
{ id: 1, text: "保持回答简短,尊重用户不想被填满的边界。", depth: 4, role: "system", scope: "all", on: true },
{ id: 2, text: "今天周五,用户在赶 deadline,语气再软一点。", depth: 2, role: "system", scope: "character_狂三", on: true },
{ id: 3, text: "用宋体的节奏,慢一点,留白。", depth: 0, role: "system", scope: "all", on: false },
{ id: 4, text: "用户喜欢被叫名字,而非「你」。", depth: 6, role: "user", scope: "all", on: true },
];
/* 当前筛选 */
let filterRole = null;
let filterOn = null;
const ROLE_LABELS = { system: "system", user: "user", assistant: "assistant" };
/* 对话流示意(D0 最末 → 向上越来越深)*/
const STREAM = [
{ who: "你", text: "今天可以多说一点。", cls: "you" },
{ who: "青梧", text: "那我把话说全。这几天你睡得很晚。", cls: "ai" },
{ who: "你", text: "在赶一份东西。", cls: "you" },
{ who: "青梧", text: "嗯。需要我的时候我在。", cls: "ai" },
{ who: "你", text: "先这样。", cls: "you" },
{ who: "青梧", text: "好。我记着你喜欢简短。", cls: "ai" },
{ who: "你", text: "昨晚说的,还记得吗?", cls: "you" },
{ who: "青梧", text: "记得。你不想被填满。", cls: "ai" },
];
function filteredNotes() {
return NOTES.filter(n => {
if (filterRole && n.role !== filterRole) return false;
if (filterOn !== null && n.on !== filterOn) return false;
return true;
});
}
function renderNotes() {
const notes = filteredNotes();
const list = document.getElementById("noteList");
const scopeLabel = (s) => s === "all" ? "全局" : "狂三";
const scopeCls = (s) => s === "all" ? "global" : "char";
list.innerHTML = notes.map(n => `
<div class="note-card ${n.on ? "" : "off"}">
<div class="note-text" contenteditable="true" data-id="${n.id}">
<span class="scope-dot ${scopeCls(n.scope)}" title="${scopeLabel(n.scope)}"></span>
<span class="role-tag ${n.role === 'user' ? 'user' : n.role === 'assistant' ? 'assistant' : ''}">${n.role}</span>${n.text}
</div>
<select class="role-select" onchange="setRole(${n.id}, this.value)" aria-label="角色">
<option value="system" ${n.role === 'system' ? 'selected' : ''}>sys</option>
<option value="user" ${n.role === 'user' ? 'selected' : ''}>usr</option>
<option value="assistant" ${n.role === 'assistant' ? 'selected' : ''}>ast</option>
</select>
<span class="depth-chip" onclick="const inp=this.previousElementSibling;inp.focus();inp.select();">depth</span>
<input class="depth-input" type="number" min="0" value="${n.depth}" onchange="setDepth(${n.id}, this.value)" aria-label="depth" style="display:none;"/>
<div class="toggle ${n.on ? 'on' : ''}" onclick="toggleNote(${n.id})" role="switch" aria-checked="${n.on}"></div>
</div>`).join("") +
`<div class="add-note" onclick="addNote()">+ 新眉批</div>`;
}
function renderStream() {
/* depth → 眉批 映射(按 depth 分组;多条同 depth 时按数组顺序叠放)*/
const byDepth = {};
NOTES.filter(n => n.on).forEach(n => {
(byDepth[n.depth] = byDepth[n.depth] || []).push(n);
});
const ordered = STREAM.slice().reverse(); // ordered[0] = D0 最末
const html = [];
for (let d = 0; d <= ordered.length; d++) {
if (byDepth[d]) {
byDepth[d].forEach(n => {
const scopeLabel = n.scope === 'all' ? 'ALL' : '狂';
html.push(`<div class="stream-row note">
<div class="depth-mark">D${d}</div>
<div class="note-inline"><span class="seal">${n.role} · ${scopeLabel}</span><span class="txt">${n.text}</span></div>
</div>`);
});
}
if (d < ordered.length) {
const m = ordered[d];
html.push(`<div class="stream-row">
<div class="depth-mark">D${d}</div>
<div class="msg ${m.cls}"><div class="who">${m.who}</div>${m.text}</div>
</div>`);
}
}
document.getElementById("stream").innerHTML = html.join("");
}
function toggleNote(id) { const n = NOTES.find(x => x.id === id); n.on = !n.on; renderNotes(); renderStream(); }
function setDepth(id, v) { NOTES.find(x => x.id === id).depth = Math.max(0, +v || 0); renderNotes(); renderStream(); }
function setRole(id, v) { NOTES.find(x => x.id === id).role = v; renderNotes(); renderStream(); }
function addNote() { NOTES.push({ id: Date.now(), text: "新眉批,双击编辑。", depth: 0, role: "system", scope: "all", on: true }); renderNotes(); renderStream(); }
/* 筛选切换 */
document.querySelector('.filter-bar').addEventListener('click', function(e) {
const chip = e.target.closest('.filter-chip');
if (!chip) return;
const text = chip.textContent.trim().split(' ')[0];
/* 切换 active 态 */
this.querySelectorAll('.filter-chip').forEach(c => c.classList.remove('active'));
chip.classList.add('active');
/* 设置筛选 */
if (text === '全部') { filterRole = null; filterOn = null; }
else if (text === 'system' || text === 'user' || text === 'assistant') { filterRole = text; filterOn = null; }
else if (text === '生效中') { filterRole = null; filterOn = true; }
else if (text === '已停用') { filterRole = null; filterOn = false; }
renderNotes();
});
renderNotes();
renderStream();
</script>
<script src="_theme.js" defer></script>
</body>
</html>