313 lines
21 KiB
HTML
313 lines
21 KiB
HTML
<!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>
|