3129 lines
103 KiB
HTML
3129 lines
103 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>魂镜 · 对话页重设计预览 v6</title>
|
||
<link rel="stylesheet" href="tokens.css" />
|
||
<link rel="stylesheet" href="_base.css" />
|
||
<link rel="stylesheet" href="_chrome.css" />
|
||
<style>
|
||
/* ============================================================
|
||
* 铜镜鉴心 v2.1 — 与登录页 copper-mirror-login.html 同源
|
||
* ============================================================ */
|
||
|
||
.chat-region {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ============================================================
|
||
* 镜背轨 Rail (280px) — 也可折叠
|
||
* ============================================================ */
|
||
.rail-wrap {
|
||
position: relative;
|
||
display: flex;
|
||
}
|
||
.rail {
|
||
width: 280px;
|
||
min-width: 280px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
background: linear-gradient(
|
||
180deg,
|
||
color-mix(in srgb, var(--night-bronze) 88%, var(--patina) 4%),
|
||
var(--night-bronze)
|
||
);
|
||
border-right: 1px solid var(--rule);
|
||
transition: width 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
.rail.collapsed {
|
||
width: 0;
|
||
min-width: 0;
|
||
border: none;
|
||
overflow: hidden;
|
||
}
|
||
/* 镜背轨双视图:铭文往来(默认)/ 此刻 —— 同一面镜背的两种照见 */
|
||
.rail-tabs {
|
||
display: flex;
|
||
gap: 0;
|
||
border-bottom: 1px solid var(--rule);
|
||
flex-shrink: 0;
|
||
}
|
||
.rail-tab {
|
||
flex: 1;
|
||
padding: 12px 10px 10px;
|
||
background: none;
|
||
border: none;
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
letter-spacing: 0.08em;
|
||
color: var(--fg-faint);
|
||
cursor: pointer;
|
||
transition: color 0.15s;
|
||
position: relative;
|
||
font-weight: 500;
|
||
}
|
||
.rail-tab:hover {
|
||
color: var(--fg-dim);
|
||
}
|
||
.rail-tab.active {
|
||
color: var(--gilt);
|
||
}
|
||
.rail-tab.active::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 24%;
|
||
right: 24%;
|
||
bottom: -1px;
|
||
height: 1.5px;
|
||
background: var(--gilt);
|
||
}
|
||
.rail-view {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: none;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
.rail-view.active {
|
||
display: flex;
|
||
}
|
||
#view-now {
|
||
overflow-y: auto;
|
||
}
|
||
.status-card {
|
||
padding: 18px 18px 12px;
|
||
border-bottom: 1px solid var(--rule);
|
||
}
|
||
.status-card .character-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
.status-card .avatar-ring {
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
border: 1.5px solid var(--patina-dim);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--patina-wash);
|
||
}
|
||
.status-card .avatar-ring .inner {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
background: var(--patina);
|
||
color: var(--ink);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-family: "Songti SC", serif;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
}
|
||
.status-card .char-info {
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
.status-card .char-name {
|
||
font-family: "Songti SC", serif;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: var(--fg);
|
||
letter-spacing: 0.02em;
|
||
}
|
||
.status-card .char-meta {
|
||
font-size: 12px;
|
||
color: var(--fg-dim);
|
||
margin-top: 1px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.pulse-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: var(--patina);
|
||
flex-shrink: 0;
|
||
box-shadow: 0 0 0 0 var(--patina);
|
||
animation: pulse 2.6s cubic-bezier(0.16, 1, 0.3, 1) infinite;
|
||
}
|
||
@keyframes pulse {
|
||
0% {
|
||
box-shadow: 0 0 0 0 rgba(111, 163, 137, 0.5);
|
||
}
|
||
70% {
|
||
box-shadow: 0 0 0 6px transparent;
|
||
}
|
||
100% {
|
||
box-shadow: 0 0 0 0 transparent;
|
||
}
|
||
}
|
||
.status-card .stats {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
margin-top: 12px;
|
||
}
|
||
.status-card .stat {
|
||
background: var(--surface);
|
||
border: 1px solid var(--rule);
|
||
border-radius: 5px;
|
||
padding: 8px 10px;
|
||
}
|
||
.status-card .stat .k {
|
||
font-size: 12px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.1em;
|
||
color: var(--fg-faint);
|
||
}
|
||
.status-card .stat .v {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--fg);
|
||
margin-top: 2px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.status-card .stat .v small {
|
||
font-size: 12px;
|
||
font-weight: 400;
|
||
color: var(--fg-dim);
|
||
}
|
||
|
||
.hex-section {
|
||
padding: 16px 18px 12px;
|
||
border-bottom: 1px solid var(--rule);
|
||
}
|
||
|
||
.section-label .sub {
|
||
font-size: 12px;
|
||
letter-spacing: 0.04em;
|
||
color: var(--fg-dim);
|
||
opacity: 0.7;
|
||
}
|
||
.section-label .fold-toggle {
|
||
margin-left: auto;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
color: var(--fg-faint);
|
||
padding: 2px 9px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--rule);
|
||
background: transparent;
|
||
transition:
|
||
color 0.12s,
|
||
border-color 0.12s;
|
||
font-family: inherit;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.section-label .fold-toggle:hover {
|
||
color: var(--fg);
|
||
border-color: var(--gilt-dim);
|
||
}
|
||
.section-label .fold-toggle .arrow-icon {
|
||
display: inline-block;
|
||
transition: transform 0.25s;
|
||
font-size: 12px;
|
||
line-height: 1;
|
||
}
|
||
.section-label .fold-toggle .arrow-icon.open {
|
||
transform: rotate(180deg);
|
||
}
|
||
.section-header {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
letter-spacing: 0.1em;
|
||
color: var(--fg-faint);
|
||
margin-bottom: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.section-header .sub {
|
||
font-size: 12px;
|
||
letter-spacing: 0.04em;
|
||
color: var(--fg-dim);
|
||
opacity: 0.7;
|
||
}
|
||
.section-header .fold-toggle {
|
||
margin-left: auto;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
color: var(--fg-faint);
|
||
padding: 2px 9px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--rule);
|
||
background: transparent;
|
||
transition:
|
||
color 0.12s,
|
||
border-color 0.12s;
|
||
font-family: inherit;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.section-header .fold-toggle:hover {
|
||
color: var(--fg);
|
||
border-color: var(--gilt-dim);
|
||
}
|
||
.section-header .fold-toggle .arrow-icon {
|
||
display: inline-block;
|
||
transition: transform 0.25s;
|
||
font-size: 12px;
|
||
line-height: 1;
|
||
}
|
||
.section-header .fold-toggle .arrow-icon.open {
|
||
transform: rotate(180deg);
|
||
}
|
||
.section-heading {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
letter-spacing: 0.1em;
|
||
color: var(--fg-faint);
|
||
font-weight: 400;
|
||
margin: 0;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.hex-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 4px 0;
|
||
}
|
||
.hex-row.done .hex-idx,
|
||
.hex-row.done .hex-name {
|
||
color: var(--fg-dim);
|
||
}
|
||
.hex-row.active .hex-idx,
|
||
.hex-row.active .hex-name {
|
||
color: var(--gilt);
|
||
font-weight: 600;
|
||
}
|
||
.hex-row.pending .hex-idx,
|
||
.hex-row.pending .hex-name {
|
||
color: var(--fg-faint);
|
||
}
|
||
.hex-idx {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
width: 22px;
|
||
text-align: right;
|
||
flex-shrink: 0;
|
||
font-weight: 500;
|
||
}
|
||
.hex-name {
|
||
font-size: 13.5px;
|
||
flex: 1;
|
||
font-weight: 500;
|
||
}
|
||
/* 六爻可折叠:与息壤同构(max-height 平滑过渡) */
|
||
.hex-list {
|
||
transition:
|
||
max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
||
opacity 0.3s;
|
||
}
|
||
.hex-list.collapsed {
|
||
max-height: 0;
|
||
opacity: 0;
|
||
overflow: hidden;
|
||
pointer-events: none;
|
||
}
|
||
/* 爻象进度:容器为虚线(未达·阴爻象),fill 覆盖为实条(已达·阳爻象) */
|
||
.hex-bar {
|
||
height: 5px;
|
||
width: 64px;
|
||
overflow: hidden;
|
||
border-radius: 2px;
|
||
background: repeating-linear-gradient(
|
||
90deg,
|
||
var(--rule) 0 4px,
|
||
transparent 4px 8px
|
||
);
|
||
flex-shrink: 0;
|
||
}
|
||
.hex-bar-fill {
|
||
height: 100%;
|
||
border-radius: 2px;
|
||
transition: all 0.5s;
|
||
}
|
||
.hex-bar-fill.done {
|
||
background: linear-gradient(90deg, var(--patina-dim), var(--patina));
|
||
}
|
||
.hex-bar-fill.active {
|
||
background: linear-gradient(
|
||
90deg,
|
||
var(--gilt-dim),
|
||
var(--gilt),
|
||
var(--gilt-dim)
|
||
);
|
||
background-size: 200% 100%;
|
||
animation: hex-shimmer 2.4s linear infinite;
|
||
}
|
||
@keyframes hex-shimmer {
|
||
0% {
|
||
background-position: 200% 0;
|
||
}
|
||
100% {
|
||
background-position: -200% 0;
|
||
}
|
||
}
|
||
|
||
/* 息壤 — 密集列表(border-top 分隔,去卡片底色,提高信息密度);可折叠(平滑动画) */
|
||
.memo-section {
|
||
padding: 16px 18px 14px;
|
||
border-bottom: 1px solid var(--rule);
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
/* 收起时释放 flex:1,让标题(含展开按钮)回缩紧贴上方,避免下方留出大片空白 */
|
||
.memo-section.folded {
|
||
flex: 0 0 auto;
|
||
}
|
||
.memo-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0;
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
opacity: 1;
|
||
padding-right: 4px;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: var(--rule) transparent;
|
||
transition:
|
||
max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
||
opacity 0.3s,
|
||
padding 0.3s;
|
||
}
|
||
.memo-list.collapsed {
|
||
max-height: 0;
|
||
flex: 0;
|
||
opacity: 0;
|
||
padding-top: 0;
|
||
padding-bottom: 0;
|
||
margin-bottom: 0;
|
||
pointer-events: none;
|
||
overflow: hidden;
|
||
}
|
||
.memo-item {
|
||
padding: 9px 0;
|
||
border-top: 1px solid var(--rule);
|
||
font-size: 12.5px;
|
||
line-height: 1.55;
|
||
color: var(--fg-dim);
|
||
}
|
||
.memo-item:first-child {
|
||
border-top: none;
|
||
padding-top: 4px;
|
||
}
|
||
.memo-item .src {
|
||
display: block;
|
||
margin-top: 3px;
|
||
font-size: 12px;
|
||
color: var(--fg-faint);
|
||
}
|
||
.memo-item .src .conf {
|
||
color: var(--patina);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.session-section {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.session-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 14px 18px 8px;
|
||
flex-shrink: 0;
|
||
}
|
||
.session-list::-webkit-scrollbar {
|
||
width: 4px;
|
||
}
|
||
.session-list::-webkit-scrollbar-thumb {
|
||
background: var(--rule);
|
||
border-radius: 999px;
|
||
}
|
||
.session-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 7px 8px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
transition: background 0.1s;
|
||
}
|
||
.session-item:hover {
|
||
background: var(--surface);
|
||
}
|
||
.session-item.active {
|
||
background: var(--patina-wash);
|
||
}
|
||
.session-item .s-dot {
|
||
width: 5px;
|
||
height: 5px;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
}
|
||
.session-item .s-title {
|
||
font-size: 12.5px;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
color: var(--fg-dim);
|
||
font-weight: 500;
|
||
}
|
||
.session-item.active .s-title {
|
||
color: var(--patina);
|
||
}
|
||
.session-item .s-date {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
color: var(--fg-faint);
|
||
flex-shrink: 0;
|
||
}
|
||
.btn-new-chat {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
letter-spacing: 0.04em;
|
||
border: 1px solid var(--rule);
|
||
border-radius: 5px;
|
||
padding: 4px 10px;
|
||
color: var(--fg-faint);
|
||
background: transparent;
|
||
cursor: pointer;
|
||
transition:
|
||
color 0.15s,
|
||
border-color 0.15s;
|
||
flex-shrink: 0;
|
||
}
|
||
.btn-new-chat:hover {
|
||
color: var(--fg);
|
||
border-color: var(--gilt-dim);
|
||
}
|
||
.session-list-wrap {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.session-list {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
padding: 6px 14px 10px;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: var(--rule) transparent;
|
||
}
|
||
.session-more {
|
||
font-size: 12px;
|
||
color: var(--fg-faint);
|
||
padding: 6px 8px;
|
||
text-align: center;
|
||
border-top: 1px solid var(--rule);
|
||
cursor: pointer;
|
||
transition: color 0.15s;
|
||
flex-shrink: 0;
|
||
}
|
||
.session-more:hover {
|
||
color: var(--fg-dim);
|
||
}
|
||
/* 会话时间分组 */
|
||
.session-group {
|
||
margin-bottom: 4px;
|
||
}
|
||
.session-group-label {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
letter-spacing: 0.08em;
|
||
color: var(--fg-faint);
|
||
padding: 12px 8px 4px;
|
||
text-transform: uppercase;
|
||
}
|
||
.session-group:first-child .session-group-label {
|
||
padding-top: 2px;
|
||
}
|
||
/* 底部统计卡:margin-top:auto 钉在面板底部 */
|
||
.session-stats {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
padding: 12px 14px 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
.stat-mini {
|
||
background: var(--surface);
|
||
border: 1px solid var(--rule);
|
||
border-radius: 5px;
|
||
padding: 8px 10px;
|
||
}
|
||
.stat-mini .k {
|
||
font-size: 12px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.1em;
|
||
color: var(--fg-faint);
|
||
}
|
||
.stat-mini .v {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--fg);
|
||
margin-top: 3px;
|
||
font-family: "Songti SC", serif;
|
||
}
|
||
.stat-mini .v small {
|
||
font-size: 12px;
|
||
font-weight: 400;
|
||
color: var(--fg-dim);
|
||
margin-left: 2px;
|
||
}
|
||
|
||
/* ============================================================
|
||
* 镜面 Face — 对话主区域
|
||
* ============================================================ */
|
||
.face {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: linear-gradient(
|
||
180deg,
|
||
rgba(255, 255, 255, 0.035),
|
||
rgba(255, 255, 255, 0.006) 28%,
|
||
transparent
|
||
);
|
||
}
|
||
|
||
.chat-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 12px 18px;
|
||
border-bottom: 1px solid var(--rule);
|
||
background: var(--surface);
|
||
backdrop-filter: blur(8px);
|
||
flex-shrink: 0;
|
||
position: relative;
|
||
z-index: 20;
|
||
}
|
||
/* 镜背轨收起/展开(chat-header 内触发,桌面可见;替代原 rail 边缘的浮动圆钮) */
|
||
.rail-toggle-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--rule);
|
||
background: var(--surface);
|
||
color: var(--fg-dim);
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
transition:
|
||
color 0.15s,
|
||
border-color 0.15s,
|
||
background 0.15s;
|
||
}
|
||
.rail-toggle-btn:hover {
|
||
color: var(--fg);
|
||
border-color: var(--gilt-dim);
|
||
}
|
||
.rail-toggle-btn svg {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
.rail-toggle-btn.active {
|
||
color: var(--patina);
|
||
border-color: var(--patina-dim);
|
||
background: var(--patina-wash);
|
||
}
|
||
|
||
.chat-header .spacer {
|
||
flex: 1;
|
||
min-width: 8px;
|
||
}
|
||
/* 移动端镜背轨抽屉触发(仅 ≤768px 可见) */
|
||
.menu-btn {
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--rule);
|
||
background: var(--surface);
|
||
color: var(--fg-dim);
|
||
cursor: pointer;
|
||
flex-shrink: 0;
|
||
}
|
||
.menu-btn:hover {
|
||
color: var(--fg);
|
||
border-color: var(--gilt-dim);
|
||
}
|
||
.menu-btn svg {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
.model-switcher {
|
||
position: relative;
|
||
}
|
||
.model-switcher-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
border: 1px solid var(--rule);
|
||
border-radius: 999px;
|
||
padding: 5px 12px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--fg-dim);
|
||
background: var(--surface);
|
||
cursor: pointer;
|
||
transition:
|
||
border-color 0.15s,
|
||
color 0.15s;
|
||
white-space: nowrap;
|
||
max-width: 180px;
|
||
font-family: inherit;
|
||
}
|
||
.model-switcher-btn:hover {
|
||
border-color: var(--gilt-dim);
|
||
color: var(--fg);
|
||
}
|
||
.model-switcher-btn .ico {
|
||
width: 12px;
|
||
height: 12px;
|
||
opacity: 0.6;
|
||
flex-shrink: 0;
|
||
}
|
||
.model-switcher-btn .model-label {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
}
|
||
.model-switcher-btn .arrow {
|
||
transition: transform 0.2s;
|
||
width: 12px;
|
||
height: 12px;
|
||
flex-shrink: 0;
|
||
}
|
||
.model-switcher-btn .arrow.open {
|
||
transform: rotate(180deg);
|
||
}
|
||
.model-dropdown {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 100%;
|
||
z-index: 50;
|
||
margin-top: 6px;
|
||
width: 220px;
|
||
background: color-mix(in srgb, var(--night-bronze) 92%, transparent);
|
||
backdrop-filter: blur(24px) saturate(160%);
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 12px;
|
||
padding: 4px;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||
}
|
||
.model-opt {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
padding: 8px 10px;
|
||
border-radius: 8px;
|
||
border: none;
|
||
background: none;
|
||
font-size: 12px;
|
||
color: var(--fg-dim);
|
||
cursor: pointer;
|
||
text-align: left;
|
||
font-family: "JetBrains Mono", monospace;
|
||
transition:
|
||
background 0.1s,
|
||
color 0.1s;
|
||
}
|
||
.model-opt:hover {
|
||
background: var(--overlay-hover);
|
||
color: var(--fg);
|
||
}
|
||
.model-opt.active {
|
||
background: var(--patina-wash);
|
||
color: var(--patina);
|
||
}
|
||
.model-opt .status-tag {
|
||
margin-left: auto;
|
||
font-size: 12px;
|
||
padding: 1px 7px;
|
||
border-radius: 999px;
|
||
background: var(--surface);
|
||
color: var(--fg-faint);
|
||
font-family: inherit;
|
||
}
|
||
.char-switcher {
|
||
position: relative;
|
||
}
|
||
.char-switcher-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
border: 1px solid var(--rule);
|
||
border-radius: 999px;
|
||
padding: 4px 12px 4px 4px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--fg-dim);
|
||
background: var(--surface);
|
||
cursor: pointer;
|
||
transition:
|
||
border-color 0.15s,
|
||
color 0.15s;
|
||
white-space: nowrap;
|
||
font-family: inherit;
|
||
}
|
||
.char-switcher-btn:hover {
|
||
border-color: var(--gilt-dim);
|
||
color: var(--fg);
|
||
}
|
||
.char-switcher-btn .arrow {
|
||
transition: transform 0.2s;
|
||
width: 12px;
|
||
height: 12px;
|
||
flex-shrink: 0;
|
||
}
|
||
.char-switcher-btn .arrow.open {
|
||
transform: rotate(180deg);
|
||
}
|
||
.char-dropdown {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 100%;
|
||
z-index: 50;
|
||
margin-top: 6px;
|
||
width: 200px;
|
||
background: color-mix(in srgb, var(--night-bronze) 92%, transparent);
|
||
backdrop-filter: blur(24px) saturate(160%);
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 12px;
|
||
padding: 4px;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||
}
|
||
.char-opt {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
padding: 8px 10px;
|
||
border-radius: 8px;
|
||
border: none;
|
||
background: none;
|
||
font-size: 13px;
|
||
color: var(--fg-dim);
|
||
cursor: pointer;
|
||
text-align: left;
|
||
font-family: inherit;
|
||
transition:
|
||
background 0.1s,
|
||
color 0.1s;
|
||
}
|
||
.char-opt:hover {
|
||
background: var(--overlay-hover);
|
||
color: var(--fg);
|
||
}
|
||
.char-opt.active {
|
||
background: var(--patina-wash);
|
||
color: var(--patina);
|
||
}
|
||
.char-opt .status-tag {
|
||
margin-left: auto;
|
||
font-size: 12px;
|
||
padding: 1px 7px;
|
||
border-radius: 999px;
|
||
background: var(--surface);
|
||
color: var(--fg-faint);
|
||
}
|
||
.avatar-mini {
|
||
width: 22px;
|
||
height: 22px;
|
||
border-radius: 50%;
|
||
background: var(--patina-wash);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
font-family: "Songti SC", serif;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--patina);
|
||
}
|
||
.avatar-mini.active-char {
|
||
background: var(--patina);
|
||
color: var(--ink);
|
||
}
|
||
.run-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
font-size: 12px;
|
||
padding: 3px 10px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--patina-dim);
|
||
background: var(--patina-wash);
|
||
color: var(--patina);
|
||
white-space: nowrap;
|
||
text-decoration: none;
|
||
}
|
||
/* ── 消息流 ── */
|
||
.turns {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 28px 32px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24px;
|
||
}
|
||
@media (max-width: 900px) {
|
||
.turns {
|
||
padding: 20px 18px;
|
||
}
|
||
}
|
||
|
||
.turn.ai .who {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
letter-spacing: 0.1em;
|
||
color: var(--patina);
|
||
margin-bottom: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.turn.ai .who .stream-cursor {
|
||
display: inline-block;
|
||
width: 7px;
|
||
height: 13px;
|
||
background: var(--patina);
|
||
animation: blink 1s step-end infinite;
|
||
vertical-align: text-bottom;
|
||
}
|
||
@keyframes blink {
|
||
0%,
|
||
100% {
|
||
opacity: 1;
|
||
}
|
||
50% {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* AI 气泡 — 抛光铜镜面(与 copper-mirror-login.html 的 mirror-card 同源)
|
||
* 亮/暗主题均使用与登录卡一致的 face-sheen 渐变 + 鎏金描边
|
||
* + 顶光内阴影 + 扫描光泽动画
|
||
*/
|
||
.turn.ai .bubble {
|
||
position: relative;
|
||
overflow: hidden;
|
||
padding: 18px 22px;
|
||
font-family: "Songti SC", serif;
|
||
font-size: 16px;
|
||
line-height: 1.8;
|
||
/* 与登录页 mirror-card 完全一致的镜面质感 */
|
||
background: var(--face-sheen), var(--surface);
|
||
border: 1px solid var(--gilt-dim);
|
||
border-radius: 6px;
|
||
box-shadow:
|
||
inset 0 1px 0 var(--face-in-top),
|
||
inset 0 -1px 0 var(--face-in-bot),
|
||
var(--shadow);
|
||
}
|
||
/* 亮色下镜面浮起:暖纸上陈列的铜镜,需更明显的落影才不糊进背景 */
|
||
[data-theme="light"] .turn.ai .bubble {
|
||
box-shadow:
|
||
inset 0 1px 0 var(--face-in-top),
|
||
inset 0 -1px 0 var(--face-in-bot),
|
||
0 1px 2px rgba(60, 50, 30, 0.06),
|
||
0 4px 14px -6px rgba(60, 50, 30, 0.13);
|
||
}
|
||
/* 扫描光泽 — 静镜(已完成):光泽凝固于一处,仅留极弱静态反射 */
|
||
.turn.ai .bubble::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
background: var(--sweep-gradient);
|
||
background-size: 200% 200%;
|
||
background-position: 35% 35%;
|
||
opacity: 0.42;
|
||
}
|
||
/* 活镜(正在生成):光源流转,漫反射滑移 — 此态才让签名光泽"活"过来 */
|
||
.turn.ai.is-streaming .bubble::before {
|
||
background-position: 200% 200%;
|
||
opacity: 1;
|
||
animation: mirror-sweep 9s ease-in-out infinite;
|
||
}
|
||
@keyframes mirror-sweep {
|
||
0% {
|
||
background-position: 200% 200%;
|
||
}
|
||
40% {
|
||
background-position: 50% 50%;
|
||
}
|
||
60% {
|
||
background-position: 50% 50%;
|
||
}
|
||
100% {
|
||
background-position: -100% -100%;
|
||
}
|
||
}
|
||
/* 铜面颗粒(feTurbulence)— 与登录页 mirror-card 同源的金属肌理,杀死平坦矢量感 */
|
||
.turn.ai .bubble::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='7'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
|
||
opacity: 0.018;
|
||
mix-blend-mode: overlay;
|
||
}
|
||
.turn.ai.is-streaming .bubble::after {
|
||
opacity: 0.035;
|
||
}
|
||
[data-theme="light"] .turn.ai .bubble::after {
|
||
opacity: 0.028;
|
||
mix-blend-mode: multiply;
|
||
}
|
||
[data-theme="light"] .turn.ai.is-streaming .bubble::after {
|
||
opacity: 0.05;
|
||
}
|
||
.turn.ai .bubble .md-body,
|
||
.turn.ai .bubble .actions,
|
||
.turn.ai .bubble .fold-card,
|
||
.turn.ai .bubble .vermilion,
|
||
.turn.ai .bubble .version-panel {
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
.turn.ai .bubble .md-body {
|
||
font-size: 15px;
|
||
line-height: 1.75;
|
||
}
|
||
.turn.ai .bubble .md-body p {
|
||
margin: 0.4em 0;
|
||
}
|
||
/* 暗色下宋体笔画偏细,加重字重 + 轻锐化提升可读性(亮色宋体无此问题) */
|
||
[data-theme="dark"] .turn.ai .bubble .md-body {
|
||
font-weight: 600;
|
||
letter-spacing: 0.012em;
|
||
text-shadow: 0 0.5px 0 rgba(0, 0, 0, 0.28);
|
||
}
|
||
|
||
/* ── AI 气泡操作栏(hover/focus 显隐,降低阅读噪音) ── */
|
||
.turn.ai .bubble .actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
margin-top: 10px;
|
||
padding-top: 6px;
|
||
border-top: 1px solid var(--rule);
|
||
position: relative;
|
||
z-index: 1;
|
||
opacity: 0;
|
||
transition: opacity 0.18s;
|
||
}
|
||
.turn.ai:hover .bubble .actions,
|
||
.turn.ai .bubble:focus-within .actions {
|
||
opacity: 1;
|
||
}
|
||
/* 流式中的消息常显操作栏(避免 hover 不到) */
|
||
.turn.ai.is-streaming .bubble .actions {
|
||
opacity: 1;
|
||
}
|
||
.action-btn {
|
||
font-family: inherit;
|
||
font-size: 12px;
|
||
color: var(--fg-faint);
|
||
border: none;
|
||
background: none;
|
||
cursor: pointer;
|
||
padding: 3px 8px;
|
||
border-radius: 4px;
|
||
transition:
|
||
color 0.15s,
|
||
background 0.15s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.action-btn:hover {
|
||
color: var(--fg);
|
||
background: var(--overlay-hover);
|
||
}
|
||
.action-btn.active-regenerate {
|
||
color: var(--patina);
|
||
background: var(--patina-wash);
|
||
}
|
||
|
||
/* ── 版本浏览面板(重答展开) ── */
|
||
.version-panel {
|
||
margin-top: 8px;
|
||
border-top: 1px solid var(--rule);
|
||
padding-top: 8px;
|
||
display: none;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
.version-panel.open {
|
||
display: block;
|
||
animation: slide-down 0.28s cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
@keyframes slide-down {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-6px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
.version-panel .vp-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
letter-spacing: 0.04em;
|
||
color: var(--fg-dim);
|
||
}
|
||
.version-panel .vp-header .vp-icon {
|
||
color: var(--gilt);
|
||
opacity: 0.7;
|
||
}
|
||
.version-panel .vp-nav {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-left: auto;
|
||
}
|
||
.version-panel .vp-nav-btn {
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 50%;
|
||
border: 1px solid var(--rule);
|
||
background: transparent;
|
||
color: var(--fg-faint);
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
transition:
|
||
color 0.12s,
|
||
background 0.12s,
|
||
border-color 0.12s;
|
||
}
|
||
.version-panel .vp-nav-btn:hover {
|
||
color: var(--fg);
|
||
background: var(--overlay-hover);
|
||
border-color: var(--gilt-dim);
|
||
}
|
||
.version-panel .vp-nav-btn:disabled {
|
||
opacity: 0.3;
|
||
cursor: default;
|
||
pointer-events: none;
|
||
}
|
||
.version-panel .vp-counter {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
color: var(--fg-faint);
|
||
min-width: 48px;
|
||
text-align: center;
|
||
}
|
||
.version-panel .vp-counter .cur {
|
||
color: var(--gilt);
|
||
font-weight: 600;
|
||
}
|
||
.version-panel .vp-body {
|
||
margin-top: 8px;
|
||
background: var(--patina-wash);
|
||
border: 1px solid var(--patina-dim);
|
||
border-radius: 4px;
|
||
padding: 10px 14px;
|
||
font-size: 13px;
|
||
line-height: 1.65;
|
||
color: var(--fg-dim);
|
||
min-height: 40px;
|
||
}
|
||
.version-panel .vp-body p {
|
||
margin: 0.3em 0;
|
||
}
|
||
.version-panel .vp-body .vp-empty {
|
||
color: var(--fg-faint);
|
||
font-style: italic;
|
||
}
|
||
.version-panel .vp-actions {
|
||
margin-top: 6px;
|
||
display: flex;
|
||
gap: 8px;
|
||
justify-content: flex-end;
|
||
}
|
||
.version-panel .vp-use-btn {
|
||
font-family: inherit;
|
||
font-size: 12px;
|
||
padding: 4px 12px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--patina-dim);
|
||
background: var(--patina-wash);
|
||
color: var(--patina);
|
||
cursor: pointer;
|
||
transition: background 0.12s;
|
||
}
|
||
.version-panel .vp-use-btn:hover {
|
||
background: var(--patina-dim);
|
||
color: var(--ink);
|
||
}
|
||
.version-panel .vp-close-btn {
|
||
font-family: inherit;
|
||
font-size: 12px;
|
||
padding: 4px 10px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--rule);
|
||
background: transparent;
|
||
color: var(--fg-faint);
|
||
cursor: pointer;
|
||
transition:
|
||
color 0.12s,
|
||
background 0.12s;
|
||
}
|
||
.version-panel .vp-close-btn:hover {
|
||
color: var(--fg);
|
||
background: var(--overlay-hover);
|
||
}
|
||
|
||
.turn.you {
|
||
text-align: right;
|
||
}
|
||
.turn.you .who {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
letter-spacing: 0.1em;
|
||
color: var(--gilt);
|
||
margin-bottom: 8px;
|
||
}
|
||
/* 镜背:与 AI 的「镜面」器物对偶 —— 哑光底、浅纹边、鎏金脊(镜缘),无光泽流动、无颗粒 */
|
||
.turn.you .bubble {
|
||
display: inline-block;
|
||
text-align: right;
|
||
font-size: 15px;
|
||
color: var(--fg);
|
||
line-height: 1.7;
|
||
padding: 11px 16px 11px 18px;
|
||
max-width: 75%;
|
||
background: var(--surface);
|
||
border: 1px solid var(--rule);
|
||
border-right: 2px solid var(--gilt-dim);
|
||
border-radius: 4px;
|
||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.025);
|
||
}
|
||
|
||
.fold-card {
|
||
margin-bottom: 12px;
|
||
overflow: hidden;
|
||
border-radius: 6px;
|
||
border: 1px solid var(--glass-border);
|
||
background: rgba(255, 255, 255, 0.015);
|
||
}
|
||
.fold-card-hd {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
cursor: pointer;
|
||
transition: background 0.12s;
|
||
user-select: none;
|
||
}
|
||
.fold-card-hd:hover {
|
||
background: var(--overlay-hover);
|
||
}
|
||
.fold-card-hd .icon {
|
||
width: 14px;
|
||
height: 14px;
|
||
flex-shrink: 0;
|
||
color: var(--gilt);
|
||
}
|
||
.fold-card-hd .label {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--fg-dim);
|
||
flex: 1;
|
||
}
|
||
.fold-card-hd .arrow {
|
||
width: 10px;
|
||
height: 10px;
|
||
color: var(--fg-faint);
|
||
transition: transform 0.2s;
|
||
flex-shrink: 0;
|
||
}
|
||
.fold-card-hd .arrow.open {
|
||
transform: rotate(180deg);
|
||
}
|
||
.fold-card-body {
|
||
border-top: 1px solid var(--glass-border);
|
||
padding: 10px 12px;
|
||
display: none;
|
||
}
|
||
.fold-card-body.open {
|
||
display: block;
|
||
}
|
||
.fold-card-body p {
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
color: var(--fg-dim);
|
||
white-space: pre-wrap;
|
||
margin: 0;
|
||
}
|
||
|
||
.badge {
|
||
font-size: 12px;
|
||
padding: 1px 8px;
|
||
border-radius: 999px;
|
||
font-weight: 500;
|
||
letter-spacing: 0.02em;
|
||
font-family: inherit;
|
||
}
|
||
.badge-warning {
|
||
background: var(--gilt-faint);
|
||
color: var(--gilt);
|
||
}
|
||
.badge-success {
|
||
background: var(--patina-dim);
|
||
color: var(--patina);
|
||
}
|
||
.badge-danger {
|
||
background: var(--cinnabar-dim);
|
||
color: var(--cinnabar);
|
||
}
|
||
|
||
.vermilion {
|
||
margin-top: 12px;
|
||
border: 1px solid var(--cinnabar-dim);
|
||
border-left: 3px solid var(--cinnabar);
|
||
border-radius: 5px;
|
||
padding: 15px 17px;
|
||
background:
|
||
radial-gradient(
|
||
120% 100% at 0% 0%,
|
||
rgba(184, 57, 46, 0.12),
|
||
transparent 60%
|
||
),
|
||
var(--surface);
|
||
}
|
||
/* 朱砂浓度即风险等级:L1 浅描 / L2 朱块(默认)/ L3 满朱晕染 —— 等级一眼可读,文字降为辅助 */
|
||
.vermilion.risk-l1 {
|
||
background: var(--surface);
|
||
}
|
||
.vermilion.risk-l3 {
|
||
background:
|
||
radial-gradient(
|
||
120% 100% at 0% 0%,
|
||
rgba(184, 57, 46, 0.22),
|
||
transparent 55%
|
||
),
|
||
rgba(184, 57, 46, 0.1);
|
||
border-color: var(--cinnabar);
|
||
box-shadow: 0 0 28px -8px rgba(184, 57, 46, 0.35);
|
||
}
|
||
.vermilion .vh {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.vermilion .seal-txt {
|
||
font-family: "Songti SC", serif;
|
||
color: var(--cinnabar);
|
||
font-weight: 700;
|
||
font-size: 14px;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
.vermilion .risk {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 12px;
|
||
letter-spacing: 0.08em;
|
||
color: var(--cinnabar);
|
||
margin-left: auto;
|
||
opacity: 0.7;
|
||
}
|
||
.vermilion .vb {
|
||
font-size: 13.5px;
|
||
color: var(--fg);
|
||
line-height: 1.6;
|
||
}
|
||
.vermilion .va {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 12px;
|
||
}
|
||
.vbtn {
|
||
font-family: inherit;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
padding: 7px 15px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
border: 1px solid var(--rule);
|
||
background: transparent;
|
||
color: var(--fg);
|
||
transition:
|
||
background 0.12s,
|
||
border-color 0.12s;
|
||
}
|
||
.vbtn:hover {
|
||
background: var(--surface-hi);
|
||
}
|
||
.vbtn.cinnabar {
|
||
border-color: var(--cinnabar);
|
||
color: var(--cinnabar);
|
||
background: rgba(184, 57, 46, 0.08);
|
||
}
|
||
.vbtn.cinnabar:hover {
|
||
background: rgba(184, 57, 46, 0.16);
|
||
}
|
||
|
||
.composer {
|
||
border-top: 1px solid var(--rule);
|
||
padding: 14px 24px 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
.composer .input-row {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 12px;
|
||
}
|
||
.composer textarea {
|
||
flex: 1;
|
||
font-family: inherit;
|
||
font-size: 14px;
|
||
color: var(--fg);
|
||
background: transparent;
|
||
border: none;
|
||
outline: none;
|
||
padding: 8px 0;
|
||
resize: none;
|
||
min-height: 2.2rem;
|
||
max-height: 10rem;
|
||
field-sizing: content;
|
||
}
|
||
.composer textarea::placeholder {
|
||
color: var(--fg-faint);
|
||
}
|
||
.composer .send-btn {
|
||
font-family: "Songti SC", serif;
|
||
font-size: 14px;
|
||
letter-spacing: 0.08em;
|
||
color: var(--patina);
|
||
background: none;
|
||
border: 1px solid var(--patina-dim);
|
||
padding: 7px 20px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
transition:
|
||
color 0.15s,
|
||
border-color 0.15s;
|
||
}
|
||
.composer .send-btn:hover {
|
||
color: var(--patina-bright);
|
||
border-color: var(--patina);
|
||
}
|
||
.composer .send-btn:disabled {
|
||
color: var(--fg-faint);
|
||
border-color: var(--rule);
|
||
cursor: not-allowed;
|
||
}
|
||
.composer .stop-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
padding: 7px 15px;
|
||
border: 1px solid var(--cinnabar-dim);
|
||
background: rgba(184, 57, 46, 0.08);
|
||
color: var(--cinnabar);
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
transition: background 0.12s;
|
||
}
|
||
.composer .stop-btn:hover {
|
||
background: rgba(184, 57, 46, 0.16);
|
||
}
|
||
.composer .controls {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 6px;
|
||
margin-top: 10px;
|
||
}
|
||
.ctrl-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
padding: 4px 10px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--rule);
|
||
background: transparent;
|
||
color: var(--fg-faint);
|
||
cursor: pointer;
|
||
transition:
|
||
color 0.15s,
|
||
border-color 0.15s,
|
||
background 0.15s;
|
||
font-family: inherit;
|
||
}
|
||
.ctrl-pill:hover {
|
||
color: var(--fg-dim);
|
||
}
|
||
.ctrl-pill.active {
|
||
border-color: var(--gilt-dim);
|
||
background: var(--gilt-faint);
|
||
color: var(--gilt);
|
||
}
|
||
.ctrl-pill.active-patina {
|
||
border-color: var(--patina-dim);
|
||
background: var(--patina-wash);
|
||
color: var(--patina);
|
||
}
|
||
.ctrl-pill .ico {
|
||
width: 10px;
|
||
height: 10px;
|
||
flex-shrink: 0;
|
||
}
|
||
.ctrl-pill.active-gilt {
|
||
border-color: var(--gilt-dim);
|
||
background: var(--gilt-faint);
|
||
color: var(--gilt);
|
||
}
|
||
.effort-group {
|
||
display: none;
|
||
align-items: center;
|
||
gap: 2px;
|
||
border: 1px solid var(--gilt-dim);
|
||
border-radius: 999px;
|
||
padding: 2px;
|
||
}
|
||
.effort-group.show {
|
||
display: inline-flex;
|
||
}
|
||
.effort-opt {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
padding: 2px 10px;
|
||
border-radius: 999px;
|
||
cursor: pointer;
|
||
color: var(--fg-faint);
|
||
background: transparent;
|
||
border: none;
|
||
transition:
|
||
background 0.12s,
|
||
color 0.12s;
|
||
font-family: "JetBrains Mono", monospace;
|
||
}
|
||
.effort-opt:hover {
|
||
color: var(--fg-dim);
|
||
}
|
||
.effort-opt.active {
|
||
background: var(--gilt-faint);
|
||
color: var(--gilt);
|
||
}
|
||
|
||
.drawer-overlay {
|
||
display: none;
|
||
}
|
||
/* 移动端:侧栏隐藏,镜背轨改为抽屉(汉堡触发),触屏操作栏常显 */
|
||
@media (max-width: 768px) {
|
||
.menu-btn {
|
||
display: inline-flex;
|
||
}
|
||
.rail-wrap {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
height: 100%;
|
||
width: 300px;
|
||
max-width: 85vw;
|
||
z-index: 60;
|
||
transform: translateX(-100%);
|
||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
.rail-wrap.drawer-open {
|
||
transform: translateX(0);
|
||
box-shadow: 8px 0 36px -10px rgba(0, 0, 0, 0.55);
|
||
}
|
||
.rail-wrap.drawer-open .rail {
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
.rail-toggle-btn {
|
||
display: none;
|
||
}
|
||
.drawer-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 55;
|
||
background: rgba(0, 0, 0, 0.45);
|
||
backdrop-filter: blur(2px);
|
||
display: none;
|
||
}
|
||
.drawer-overlay.open {
|
||
display: block;
|
||
}
|
||
|
||
.chat-header {
|
||
padding: 10px 12px;
|
||
gap: 6px;
|
||
}
|
||
.turns {
|
||
padding: 16px;
|
||
gap: 18px;
|
||
}
|
||
.composer {
|
||
padding: 12px 16px;
|
||
}
|
||
.turn.ai .bubble .actions {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="app-shell">
|
||
<div
|
||
class="drawer-overlay"
|
||
id="drawerOverlay"
|
||
onclick="toggleRailDrawer(false)"
|
||
></div>
|
||
|
||
<!-- ════════════════════════════════════ -->
|
||
<!-- 侧栏 Sidebar (click 展开) — 灵台/法身/内景等 -->
|
||
<!-- ════════════════════════════════════ -->
|
||
<div class="sidebar-mount" data-active="对话"></div>
|
||
|
||
<!-- ════════════════════════════════════ -->
|
||
<!-- 主内容区 -->
|
||
<!-- ════════════════════════════════════ -->
|
||
<div class="main-area" id="mainArea">
|
||
<div class="chat-region">
|
||
<!-- ========================== -->
|
||
<!-- 镜背轨 (280px) -->
|
||
<!-- ========================== -->
|
||
<aside class="rail-wrap" id="railWrap">
|
||
<div class="rail" id="railInner">
|
||
<div class="rail-tabs">
|
||
<button
|
||
class="rail-tab active"
|
||
type="button"
|
||
data-view="sessions"
|
||
onclick="toggleRailView('sessions')"
|
||
>
|
||
铭文往来
|
||
</button>
|
||
<button
|
||
class="rail-tab"
|
||
type="button"
|
||
data-view="now"
|
||
onclick="toggleRailView('now')"
|
||
>
|
||
此刻
|
||
</button>
|
||
</div>
|
||
<div class="rail-view active" id="view-sessions">
|
||
<div class="session-header">
|
||
<h3 class="section-heading">铭文 · 往来</h3>
|
||
<button class="btn-new-chat" type="button">+ 新启</button>
|
||
</div>
|
||
<div class="session-list-wrap">
|
||
<div class="session-list">
|
||
<div class="session-group">
|
||
<div class="session-group-label">今日</div>
|
||
<div class="session-item active">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--patina)"
|
||
></span
|
||
><span class="s-title" style="color: var(--patina)"
|
||
>关于架构调整的讨论</span
|
||
><span class="s-date">07-11</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">青梧性格微调</span
|
||
><span class="s-date">07-11</span>
|
||
</div>
|
||
</div>
|
||
<div class="session-group">
|
||
<div class="session-group-label">本周</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">叩心枢记忆上下文</span
|
||
><span class="s-date">07-10</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">青梧的性格设定</span
|
||
><span class="s-date">07-09</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--cinnabar)"
|
||
></span
|
||
><span class="s-title">权限问题排查</span
|
||
><span class="s-date">07-08</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">记忆系统设计讨论</span
|
||
><span class="s-date">07-07</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">情绪状态机接入</span
|
||
><span class="s-date">07-06</span>
|
||
</div>
|
||
</div>
|
||
<div class="session-group">
|
||
<div class="session-group-label">更早</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">九枢权限模型评审</span
|
||
><span class="s-date">07-05</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">前端组件库迁移方案</span
|
||
><span class="s-date">07-04</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">博闻 MCP 联调</span
|
||
><span class="s-date">07-03</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">人物模板 birthday 字段</span
|
||
><span class="s-date">07-01</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">叩心枢长期记忆</span
|
||
><span class="s-date">06-29</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">文档 mermaid 风格</span
|
||
><span class="s-date">06-28</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">一炁订阅基础设施</span
|
||
><span class="s-date">06-26</span>
|
||
</div>
|
||
<div class="session-item">
|
||
<span
|
||
class="s-dot"
|
||
style="background: var(--fg-faint)"
|
||
></span
|
||
><span class="s-title">辰正 SDD 进度</span
|
||
><span class="s-date">06-25</span>
|
||
</div>
|
||
</div>
|
||
<div class="session-more" title="浏览全部归档">
|
||
浏览全部归档 →
|
||
</div>
|
||
</div>
|
||
<div class="session-stats">
|
||
<div class="stat-mini">
|
||
<div class="k">本月铭文</div>
|
||
<div class="v">42<small>通</small></div>
|
||
</div>
|
||
<div class="stat-mini">
|
||
<div class="k">活跃人物</div>
|
||
<div class="v">3<small>位</small></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="rail-view" id="view-now">
|
||
<div class="status-card">
|
||
<div class="character-row">
|
||
<div class="avatar-ring"><div class="inner">梧</div></div>
|
||
<div class="char-info">
|
||
<div class="char-name">青梧</div>
|
||
<div class="char-meta">
|
||
<span class="pulse-dot"></span>认识你第 41 日 · 沉静
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="stats">
|
||
<div class="stat">
|
||
<div class="k">情绪</div>
|
||
<div class="v">沉静 <small>平稳</small></div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="k">内驱</div>
|
||
<div class="v">探索 <small>你的偏好</small></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="hex-section" id="hexSection">
|
||
<div class="section-header">
|
||
六爻 <span class="sub">本次运行</span>
|
||
<button
|
||
class="fold-toggle"
|
||
type="button"
|
||
onclick="toggleHexagram()"
|
||
id="hexToggle"
|
||
>
|
||
<span class="arrow-icon">▼</span>
|
||
收起
|
||
</button>
|
||
</div>
|
||
<div class="hex-list" id="hexList">
|
||
<div class="hex-row done">
|
||
<span class="hex-idx">初</span
|
||
><span class="hex-name">起意</span>
|
||
<div class="hex-bar">
|
||
<div
|
||
class="hex-bar-fill done"
|
||
style="width: 100%"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
<div class="hex-row done">
|
||
<span class="hex-idx">二</span
|
||
><span class="hex-name">察势</span>
|
||
<div class="hex-bar">
|
||
<div
|
||
class="hex-bar-fill done"
|
||
style="width: 100%"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
<div class="hex-row active">
|
||
<span class="hex-idx">三</span
|
||
><span class="hex-name">定策</span>
|
||
<div class="hex-bar">
|
||
<div
|
||
class="hex-bar-fill active"
|
||
style="width: 100%"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
<div class="hex-row pending">
|
||
<span class="hex-idx">四</span
|
||
><span class="hex-name">行事</span>
|
||
<div class="hex-bar">
|
||
<div class="hex-bar-fill" style="width: 0%"></div>
|
||
</div>
|
||
</div>
|
||
<div class="hex-row pending">
|
||
<span class="hex-idx">五</span
|
||
><span class="hex-name">归真</span>
|
||
<div class="hex-bar">
|
||
<div class="hex-bar-fill" style="width: 0%"></div>
|
||
</div>
|
||
</div>
|
||
<div class="hex-row pending">
|
||
<span class="hex-idx">上</span
|
||
><span class="hex-name">入藏</span>
|
||
<div class="hex-bar">
|
||
<div class="hex-bar-fill" style="width: 0%"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 息壤(可折叠) -->
|
||
<div class="memo-section">
|
||
<div class="section-header">
|
||
息壤 <span class="sub">浮现</span>
|
||
<button
|
||
class="fold-toggle"
|
||
type="button"
|
||
onclick="toggleXirang()"
|
||
id="xirangToggle"
|
||
>
|
||
<span class="arrow-icon" id="xirangArrow">▼</span>
|
||
收起
|
||
</button>
|
||
</div>
|
||
<div class="memo-list" id="xirangList">
|
||
<div class="memo-item">
|
||
上周三你提到「deadline 在周五」
|
||
<span class="src"
|
||
>置信 <span class="conf">0.88</span> ·
|
||
已用于本次回应</span
|
||
>
|
||
</div>
|
||
<div class="memo-item">
|
||
你偏好简短回复,不喜欢被填满
|
||
<span class="src"
|
||
>置信 <span class="conf">0.82</span> · 长期·稳定</span
|
||
>
|
||
</div>
|
||
<div class="memo-item">
|
||
你这几天在赶一份文档,睡得很晚
|
||
<span class="src"
|
||
>置信 <span class="conf">0.75</span> ·
|
||
来自上下文推断</span
|
||
>
|
||
</div>
|
||
<div class="memo-item">
|
||
你提过喜欢宋代山水画
|
||
<span class="src"
|
||
>置信 <span class="conf">0.69</span> ·
|
||
来自过往对话</span
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- /view-now -->
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ========================== -->
|
||
<!-- 镜面 Face -->
|
||
<!-- ========================== -->
|
||
<div class="face">
|
||
<div class="chat-header">
|
||
<button
|
||
class="menu-btn"
|
||
type="button"
|
||
onclick="toggleRailDrawer()"
|
||
title="会话与状态"
|
||
aria-label="会话与状态"
|
||
>
|
||
<svg
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.3"
|
||
stroke-linecap="round"
|
||
>
|
||
<path d="M2 4h12M2 8h12M2 12h12" />
|
||
</svg>
|
||
</button>
|
||
<button
|
||
class="rail-toggle-btn active"
|
||
type="button"
|
||
onclick="toggleRail()"
|
||
title="展开/收起镜背轨"
|
||
aria-label="展开/收起镜背轨"
|
||
>
|
||
<svg
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.3"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<rect x="2" y="2" width="12" height="12" rx="1.5" />
|
||
<path d="M6 2v12" />
|
||
</svg>
|
||
</button>
|
||
<div class="char-switcher">
|
||
<button
|
||
class="char-switcher-btn"
|
||
type="button"
|
||
onclick="toggleCharDropdown(event)"
|
||
>
|
||
<span class="avatar-mini active-char" id="charAvatar"
|
||
>梧</span
|
||
>
|
||
<span id="charLabel">青梧</span>
|
||
<svg
|
||
class="arrow"
|
||
viewBox="0 0 12 12"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linecap="round"
|
||
>
|
||
<path d="M3 4.5l3 3 3-3" />
|
||
</svg>
|
||
</button>
|
||
<div
|
||
class="char-dropdown"
|
||
id="charDropdown"
|
||
style="display: none"
|
||
>
|
||
<button
|
||
class="char-opt active"
|
||
type="button"
|
||
data-char="qingwu"
|
||
data-name="青梧"
|
||
data-initial="梧"
|
||
>
|
||
<span class="avatar-mini active-char">梧</span>青梧
|
||
<span class="status-tag">当前</span>
|
||
</button>
|
||
<button
|
||
class="char-opt"
|
||
type="button"
|
||
data-char="yunlan"
|
||
data-name="云岚"
|
||
data-initial="岚"
|
||
>
|
||
<span class="avatar-mini">岚</span>云岚
|
||
<span class="status-tag">在线</span>
|
||
</button>
|
||
<button
|
||
class="char-opt"
|
||
type="button"
|
||
data-char="moyu"
|
||
data-name="墨羽"
|
||
data-initial="墨"
|
||
>
|
||
<span class="avatar-mini">墨</span>墨羽
|
||
<span class="status-tag">在线</span>
|
||
</button>
|
||
<button
|
||
class="char-opt"
|
||
type="button"
|
||
data-char="lingyin"
|
||
data-name="泠音"
|
||
data-initial="泠"
|
||
>
|
||
<span class="avatar-mini">泠</span>泠音
|
||
<span class="status-tag">离线</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<span class="spacer"></span>
|
||
<a href="#" class="run-badge">
|
||
<span
|
||
class="pulse-dot"
|
||
style="
|
||
width: 5px;
|
||
height: 5px;
|
||
animation: pulse 2.6s infinite;
|
||
"
|
||
></span>
|
||
定策中
|
||
</a>
|
||
<div class="model-switcher">
|
||
<button
|
||
class="model-switcher-btn"
|
||
type="button"
|
||
onclick="toggleModelDropdown(event)"
|
||
title="切换模型(透传 model)"
|
||
>
|
||
<svg
|
||
class="ico"
|
||
width="12"
|
||
height="12"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.2"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<rect x="2" y="3" width="12" height="10" rx="1" />
|
||
<path d="M5 7h6M5 10h4" />
|
||
</svg>
|
||
<span class="model-label" id="modelLabel"
|
||
>claude-sonnet-5</span
|
||
>
|
||
<svg
|
||
class="arrow"
|
||
viewBox="0 0 12 12"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linecap="round"
|
||
>
|
||
<path d="M3 4.5l3 3 3-3" />
|
||
</svg>
|
||
</button>
|
||
<div
|
||
class="model-dropdown"
|
||
id="modelDropdown"
|
||
style="display: none"
|
||
>
|
||
<button
|
||
class="model-opt active"
|
||
type="button"
|
||
data-model="claude-sonnet-5"
|
||
>
|
||
claude-sonnet-5 <span class="status-tag">当前</span>
|
||
</button>
|
||
<button
|
||
class="model-opt"
|
||
type="button"
|
||
data-model="claude-opus-4-8"
|
||
>
|
||
claude-opus-4-8
|
||
</button>
|
||
<button
|
||
class="model-opt"
|
||
type="button"
|
||
data-model="claude-haiku-4-5"
|
||
>
|
||
claude-haiku-4-5
|
||
</button>
|
||
<button class="model-opt" type="button" data-model="glm-4.6">
|
||
glm-4.6
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="turns">
|
||
<!-- AI 消息 1 — 普通(带版本历史模拟) -->
|
||
<div
|
||
class="turn ai"
|
||
data-versions='[
|
||
{"text":"你昨晚说,喜欢简短的回答。我记下了。也在想,这可能不只是风格,是你不想被填满的边界。今天还这样吗?"},
|
||
{"text":"你昨晚提过喜欢简洁。我理解那是你的边界感。今天状态如何?需要我继续保持简短吗?"},
|
||
{"text":"昨晚你提到偏好简短,我记下了。如果今天需要展开说,告诉我。"}
|
||
]'
|
||
>
|
||
<div class="who">青梧</div>
|
||
<div class="bubble">
|
||
<div class="md-body md-visible" data-vi="0">
|
||
<p>
|
||
你昨晚说,喜欢简短的回答。我记下了。也在想,这可能不只是风格,是你不想被填满的边界。今天还这样吗?
|
||
</p>
|
||
</div>
|
||
<!-- 版本浏览面板(点击重答展开) -->
|
||
<div class="version-panel" id="vp1">
|
||
<div class="vp-header">
|
||
<span class="vp-icon">⟡</span> 版本历史
|
||
<div class="vp-nav">
|
||
<button
|
||
class="vp-nav-btn"
|
||
type="button"
|
||
id="vpPrev1"
|
||
onclick="navVersion(1, -1)"
|
||
title="上一版"
|
||
>
|
||
◀
|
||
</button>
|
||
<span class="vp-counter"
|
||
><span class="cur" id="vc1">1</span>/<span id="vt1"
|
||
>3</span
|
||
></span
|
||
>
|
||
<button
|
||
class="vp-nav-btn"
|
||
type="button"
|
||
id="vpNext1"
|
||
onclick="navVersion(1, 1)"
|
||
title="下一版"
|
||
>
|
||
▶
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="vp-body" id="vpBody1">
|
||
<p>
|
||
你昨晚说,喜欢简短的回答。我记下了。也在想,这可能不只是风格,是你不想被填满的边界。今天还这样吗?
|
||
</p>
|
||
</div>
|
||
<div class="vp-actions">
|
||
<button
|
||
class="vp-close-btn"
|
||
type="button"
|
||
onclick="closeVersionPanel(1)"
|
||
>
|
||
关闭
|
||
</button>
|
||
<button
|
||
class="vp-use-btn"
|
||
type="button"
|
||
onclick="useVersion(1)"
|
||
>
|
||
使用此版
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="action-btn">
|
||
<svg
|
||
width="11"
|
||
height="11"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.2"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<path d="M2 5.5v5h2.5l4 3V2.5l-4 3H2Z" />
|
||
<path d="M11 6.5a2.5 2.5 0 0 1 0 3" stroke-width="1" />
|
||
<path
|
||
d="M12.5 5a5 5 0 0 1 0 6"
|
||
stroke-width="1"
|
||
opacity=".4"
|
||
/>
|
||
</svg>
|
||
朗读
|
||
</button>
|
||
<button
|
||
class="action-btn"
|
||
onclick="toggleVersionPanel(1, this)"
|
||
>
|
||
<svg
|
||
width="11"
|
||
height="11"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.3"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<path d="M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2v3.5H10" />
|
||
</svg>
|
||
重答
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 用户消息 -->
|
||
<div class="turn you">
|
||
<div class="who">你</div>
|
||
<div class="bubble">今天可以多说一点。</div>
|
||
</div>
|
||
|
||
<!-- AI 消息 2 — 带思考块 + 版本浏览(模拟 2 个版本) -->
|
||
<div
|
||
class="turn ai"
|
||
data-versions='[
|
||
{"text":"那我把话说全。这几天你一直在赶一份东西,睡得很晚。我没问,因为你不像想被问。如果今晚你想停一停,我在。"},
|
||
{"text":"好,那我不再克制。看得出你这几天在赶工,睡得晚。我不刻意追问,但如果今晚你需要停下来,我在这里陪你。"}
|
||
]'
|
||
>
|
||
<div class="who">青梧</div>
|
||
<div class="bubble">
|
||
<div class="fold-card">
|
||
<div class="fold-card-hd" onclick="toggleFold(this)">
|
||
<span class="icon">
|
||
<svg
|
||
width="13"
|
||
height="13"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.2"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<path
|
||
d="M8 1.5A4 4 0 0 0 5.2 8.4c.3.4.5.9.5 1.4v.2a1 1 0 0 0 1 1h2.6a1 1 0 0 0 1-1v-.2c0-.5.2-1 .5-1.4A4 4 0 0 0 8 1.5Z"
|
||
/>
|
||
<path d="M6.5 13h3M7 14.5h2" />
|
||
</svg>
|
||
</span>
|
||
<span class="label">思考过程</span>
|
||
<svg
|
||
class="arrow"
|
||
viewBox="0 0 12 12"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linecap="round"
|
||
>
|
||
<path d="M3 4.5l3 3 3-3" />
|
||
</svg>
|
||
</div>
|
||
<div class="fold-card-body">
|
||
<p>
|
||
用户今天说了"可以多说一点",与之前偏好简短不同。可能状态放松了。可以观察是否真的接受更长回应,还是客气。先回应感受而非信息。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="md-body md-visible" data-vi="0">
|
||
<p>
|
||
那我把话说全。这几天你一直在赶一份东西,睡得很晚。我没问,因为你不像想被问。如果今晚你想停一停,我在。
|
||
</p>
|
||
</div>
|
||
<div class="version-panel" id="vp2">
|
||
<div class="vp-header">
|
||
<span class="vp-icon">⟡</span> 版本历史
|
||
<div class="vp-nav">
|
||
<button
|
||
class="vp-nav-btn"
|
||
type="button"
|
||
id="vpPrev2"
|
||
onclick="navVersion(2, -1)"
|
||
title="上一版"
|
||
>
|
||
◀
|
||
</button>
|
||
<span class="vp-counter"
|
||
><span class="cur" id="vc2">1</span>/<span id="vt2"
|
||
>2</span
|
||
></span
|
||
>
|
||
<button
|
||
class="vp-nav-btn"
|
||
type="button"
|
||
id="vpNext2"
|
||
onclick="navVersion(2, 1)"
|
||
title="下一版"
|
||
>
|
||
▶
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="vp-body" id="vpBody2">
|
||
<p>
|
||
那我把话说全。这几天你一直在赶一份东西,睡得很晚。我没问,因为你不像想被问。如果今晚你想停一停,我在。
|
||
</p>
|
||
</div>
|
||
<div class="vp-actions">
|
||
<button
|
||
class="vp-close-btn"
|
||
type="button"
|
||
onclick="closeVersionPanel(2)"
|
||
>
|
||
关闭
|
||
</button>
|
||
<button
|
||
class="vp-use-btn"
|
||
type="button"
|
||
onclick="useVersion(2)"
|
||
>
|
||
使用此版
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="action-btn">🔊 朗读</button>
|
||
<button
|
||
class="action-btn"
|
||
onclick="toggleVersionPanel(2, this)"
|
||
>
|
||
<svg
|
||
width="11"
|
||
height="11"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.3"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<path d="M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2v3.5H10" />
|
||
</svg>
|
||
重答
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- AI 消息 3 — 带工具确认 + 朱漆卡(无多版本) -->
|
||
<div class="turn ai">
|
||
<div class="who">青梧</div>
|
||
<div class="bubble">
|
||
<div class="fold-card">
|
||
<div class="fold-card-hd" onclick="toggleFold(this)">
|
||
<span class="icon">
|
||
<svg
|
||
width="13"
|
||
height="13"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.2"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<path
|
||
d="M11.5 2.5a2.5 2.5 0 0 0-3.4 2.9L3 10.5a1.4 1.4 0 0 0 2 2l5.1-5.1a2.5 2.5 0 0 0 2.9-3.4l-1.7 1.7-1.4-.2-.2-1.4 1.8-1.6Z"
|
||
/>
|
||
</svg>
|
||
</span>
|
||
<span class="label">read_calendar</span>
|
||
<span class="badge badge-warning">待确认</span>
|
||
<svg
|
||
class="arrow"
|
||
viewBox="0 0 12 12"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
stroke-linecap="round"
|
||
>
|
||
<path d="M3 4.5l3 3 3-3" />
|
||
</svg>
|
||
</div>
|
||
<div class="fold-card-body open">
|
||
<p>参数: {"date_range": "2026-07-10", "scope": "day"}</p>
|
||
</div>
|
||
</div>
|
||
<div class="md-body">
|
||
<p>
|
||
我想确认一下你的日程,判断你是否在赶工,以及今晚是否该提醒你休息。
|
||
</p>
|
||
</div>
|
||
<div class="vermilion risk-l2">
|
||
<div class="vh">
|
||
<span class="seal-txt">朱批待定</span
|
||
><span class="risk">RISK L2</span>
|
||
</div>
|
||
<div class="vb">
|
||
青梧请求使用
|
||
<b>读取日历</b
|
||
>,以判断你是否正在赶工、今晚是否该提醒你休息。
|
||
</div>
|
||
<div class="va">
|
||
<button class="vbtn cinnabar" type="button">
|
||
准予 仅本次</button
|
||
><button class="vbtn" type="button">驳回</button>
|
||
</div>
|
||
</div>
|
||
<div class="version-panel" id="vp3">
|
||
<div class="vp-header">
|
||
<span class="vp-icon">⟡</span> 版本历史
|
||
<div class="vp-nav">
|
||
<button
|
||
class="vp-nav-btn"
|
||
type="button"
|
||
id="vpPrev3"
|
||
onclick="navVersion(3, -1)"
|
||
title="上一版"
|
||
>
|
||
◀
|
||
</button>
|
||
<span class="vp-counter"
|
||
><span class="cur" id="vc3">1</span>/<span id="vt3"
|
||
>2</span
|
||
></span
|
||
>
|
||
<button
|
||
class="vp-nav-btn"
|
||
type="button"
|
||
id="vpNext3"
|
||
onclick="navVersion(3, 1)"
|
||
title="下一版"
|
||
>
|
||
▶
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="vp-body" id="vpBody3">
|
||
<p>
|
||
我想确认一下你的日程,判断你是否在赶工,以及今晚是否该提醒你休息。
|
||
</p>
|
||
</div>
|
||
<div class="vp-actions">
|
||
<button
|
||
class="vp-close-btn"
|
||
type="button"
|
||
onclick="closeVersionPanel(3)"
|
||
>
|
||
关闭
|
||
</button>
|
||
<button
|
||
class="vp-use-btn"
|
||
type="button"
|
||
onclick="useVersion(3)"
|
||
>
|
||
使用此版
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="action-btn">🔊 朗读</button>
|
||
<button
|
||
class="action-btn"
|
||
onclick="toggleVersionPanel(3, this)"
|
||
>
|
||
<svg
|
||
width="11"
|
||
height="11"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.3"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<path d="M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2v3.5H10" />
|
||
</svg>
|
||
重答
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- AI 消息 4 — 流式(活镜:光泽流动) -->
|
||
<div class="turn ai is-streaming">
|
||
<div
|
||
class="who"
|
||
style="animation: fadePulse 2s ease-in-out infinite"
|
||
>
|
||
青梧 <span class="stream-cursor"></span>
|
||
</div>
|
||
<div class="bubble" style="border-color: var(--patina-dim)">
|
||
<div class="md-body">
|
||
<p>好,那让我看看你的日历……</p>
|
||
<p>
|
||
你从下午两点到五点有一个会议,之后没有安排。今晚应该可以<em>早一点休息</em>。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<style>
|
||
@keyframes fadePulse {
|
||
0%,
|
||
100% {
|
||
opacity: 1;
|
||
}
|
||
50% {
|
||
opacity: 0.7;
|
||
}
|
||
}
|
||
</style>
|
||
</div>
|
||
|
||
<!-- 铭文输入 -->
|
||
<div class="composer">
|
||
<div class="input-row">
|
||
<textarea
|
||
rows="1"
|
||
placeholder="对青梧说点什么……(Enter 发送,Shift+Enter 换行)"
|
||
></textarea>
|
||
<button class="send-btn" type="button">呈镜</button>
|
||
</div>
|
||
<div class="controls">
|
||
<button
|
||
class="ctrl-pill"
|
||
id="reasoningBtn"
|
||
type="button"
|
||
onclick="toggleReasoning(this)"
|
||
title="深虑:开关透传 reasoning,开启后选择强度(透传 reasoning_effort)"
|
||
>
|
||
<span class="ico">
|
||
<svg
|
||
width="12"
|
||
height="12"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.2"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<path
|
||
d="M8 1.5A4 4 0 0 0 5.2 8.4c.3.4.5.9.5 1.4v.2a1 1 0 0 0 1 1h2.6a1 1 0 0 0 1-1v-.2c0-.5.2-1 .5-1.4A4 4 0 0 0 8 1.5Z"
|
||
/>
|
||
<path d="M6.5 13h3M7 14.5h2" />
|
||
</svg>
|
||
</span>
|
||
深虑
|
||
</button>
|
||
<div class="effort-group" id="effortGroup">
|
||
<button class="effort-opt" type="button" data-effort="low">
|
||
low
|
||
</button>
|
||
<button
|
||
class="effort-opt active"
|
||
type="button"
|
||
data-effort="medium"
|
||
>
|
||
medium
|
||
</button>
|
||
<button class="effort-opt" type="button" data-effort="high">
|
||
high
|
||
</button>
|
||
</div>
|
||
<button
|
||
class="ctrl-pill"
|
||
id="bowenBtn"
|
||
type="button"
|
||
onclick="toggleBowen(this)"
|
||
title="博闻:联网检索(透传 web_search)"
|
||
>
|
||
<span class="ico">
|
||
<svg
|
||
width="12"
|
||
height="12"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.2"
|
||
stroke-linecap="round"
|
||
>
|
||
<circle cx="7" cy="7" r="4.5" />
|
||
<path d="M10.5 10.5l3 3" />
|
||
</svg>
|
||
</span>
|
||
博闻
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- /face -->
|
||
</div>
|
||
<!-- /chat-region -->
|
||
</div>
|
||
<!-- /main-area -->
|
||
</div>
|
||
<!-- /app-shell -->
|
||
|
||
<script>
|
||
// ─── 折叠卡片 ───
|
||
function toggleFold(h) {
|
||
const b = h.nextElementSibling;
|
||
if (!b) return;
|
||
b.classList.toggle("open");
|
||
h.querySelector(".arrow")?.classList.toggle("open");
|
||
}
|
||
|
||
// ─── 镜背轨折叠(人物/六爻/息壤/铭文等) ───
|
||
let railCollapsed = false;
|
||
window.toggleRail = function () {
|
||
railCollapsed = !railCollapsed;
|
||
const wrap = document.getElementById("railWrap");
|
||
const inner = document.getElementById("railInner");
|
||
inner.classList.toggle("collapsed");
|
||
wrap.classList.toggle("collapsed");
|
||
const toggle = document.querySelector(".rail-toggle-btn");
|
||
if (toggle) toggle.classList.toggle("active", !railCollapsed);
|
||
};
|
||
|
||
// ─── 息壤折叠(平滑动画 + 旋转箭头) ───
|
||
let xirangOpen = true;
|
||
window.toggleXirang = function () {
|
||
xirangOpen = !xirangOpen;
|
||
const list = document.getElementById("xirangList");
|
||
const section = list.closest(".memo-section");
|
||
const tog = document.getElementById("xirangToggle");
|
||
const arr = document.getElementById("xirangArrow");
|
||
list.classList.toggle("collapsed");
|
||
// 同步释放 memo-section 的 flex:1,否则标题下方会留出大片空白
|
||
if (section) section.classList.toggle("folded", !xirangOpen);
|
||
arr.classList.toggle("open");
|
||
const count = list.querySelectorAll(".memo-item").length;
|
||
tog.innerHTML = xirangOpen
|
||
? '<span class="arrow-icon" id="xirangArrow">▼</span> 收起'
|
||
: '<span class="arrow-icon" id="xirangArrow">▼</span> 展开(' +
|
||
count +
|
||
")";
|
||
// re-bind arrow ref
|
||
document
|
||
.getElementById("xirangArrow")
|
||
.classList.toggle("open", !xirangOpen);
|
||
};
|
||
|
||
// 息壤默认全显(flex 撑满 + 内部滚动),「展开全部」入口已移除
|
||
|
||
// ─── 侧栏 click toggle(固定展开,挤压主区而非浮层覆盖) ───
|
||
let sidebarExpanded = false;
|
||
window.toggleSidebar = function () {
|
||
sidebarExpanded = !sidebarExpanded;
|
||
document
|
||
.getElementById("sidebarWrap")
|
||
.classList.toggle("expanded", sidebarExpanded);
|
||
};
|
||
|
||
// ─── logo 彩蛋「镜启」:1.2s 内连点五次弹随机谶语(单击仍切侧栏) ───
|
||
const eggVerses = [
|
||
"镜中有镜,心外无心。",
|
||
"汝来看此镜时,此镜亦照汝。",
|
||
"一念起,万镜生。",
|
||
"镜非鉴物,乃鉴心也。",
|
||
"幻由心生,镜随心转。",
|
||
"九枢归一,一归于何处?",
|
||
"铜镜本无字,照尽往来人。",
|
||
"且向镜中看,那个是谁?",
|
||
];
|
||
let logoClickSeq = 0;
|
||
let logoClickTimer = null;
|
||
window.onLogoClick = function () {
|
||
const logoEl = document.querySelector(".logo");
|
||
logoClickSeq += 1;
|
||
clearTimeout(logoClickTimer);
|
||
if (logoClickSeq >= 5) {
|
||
logoClickSeq = 0;
|
||
const v = eggVerses[Math.floor(Math.random() * eggVerses.length)];
|
||
if (logoEl) {
|
||
logoEl.classList.add("egg-pulse");
|
||
setTimeout(() => logoEl.classList.remove("egg-pulse"), 1400);
|
||
}
|
||
showToast("✦ 镜启 · " + v);
|
||
} else {
|
||
// 轻微「敲门」反馈;不折叠侧栏(折叠由顶部专属按钮负责)
|
||
if (logoEl) {
|
||
logoEl.classList.add("tick");
|
||
setTimeout(() => logoEl.classList.remove("tick"), 300);
|
||
}
|
||
logoClickTimer = setTimeout(() => {
|
||
logoClickSeq = 0;
|
||
}, 1200);
|
||
}
|
||
};
|
||
|
||
// ─── 六爻折叠(与息壤同构) ───
|
||
let hexOpen = true;
|
||
window.toggleHexagram = function () {
|
||
hexOpen = !hexOpen;
|
||
const list = document.getElementById("hexList");
|
||
const tog = document.getElementById("hexToggle");
|
||
list.classList.toggle("collapsed", !hexOpen);
|
||
tog.innerHTML = hexOpen
|
||
? '<span class="arrow-icon">▼</span> 收起'
|
||
: '<span class="arrow-icon">▼</span> 展开';
|
||
tog.querySelector(".arrow-icon").classList.toggle("open", !hexOpen);
|
||
};
|
||
|
||
// ─── 移动端镜背轨抽屉(汉堡触发 + 遮罩) ───
|
||
window.toggleRailDrawer = function (force) {
|
||
const wrap = document.getElementById("railWrap");
|
||
const overlay = document.getElementById("drawerOverlay");
|
||
if (!wrap || !overlay) return;
|
||
const open =
|
||
typeof force === "boolean"
|
||
? force
|
||
: !wrap.classList.contains("drawer-open");
|
||
wrap.classList.toggle("drawer-open", open);
|
||
overlay.classList.toggle("open", open);
|
||
};
|
||
|
||
// ─── 版本浏览面板 ───
|
||
// 存储各个 turn 的版本数据
|
||
document
|
||
.querySelectorAll(".turn.ai[data-versions]")
|
||
.forEach((turn, idx) => {
|
||
const num = idx + 1; // 1-based
|
||
try {
|
||
const versions = JSON.parse(turn.getAttribute("data-versions"));
|
||
const vp = turn.querySelector(".version-panel");
|
||
if (!vp) return;
|
||
// 挂载版本数据
|
||
vp._versions = versions;
|
||
vp._currentIdx = 0;
|
||
vp._turnIdx = num;
|
||
} catch (e) {}
|
||
});
|
||
|
||
// 打开/关闭版本面板
|
||
window.toggleVersionPanel = function (num, btn) {
|
||
const turn = document.querySelectorAll(".turn.ai")[num - 1];
|
||
if (!turn) return;
|
||
const vp = turn.querySelector(".version-panel");
|
||
if (!vp) return;
|
||
const isOpen = vp.classList.contains("open");
|
||
if (isOpen) {
|
||
closeVersionPanel(num);
|
||
if (btn) btn.classList.remove("active-regenerate");
|
||
} else {
|
||
vp.classList.add("open");
|
||
if (btn) btn.classList.add("active-regenerate");
|
||
// 同步显示
|
||
const versions = vp._versions;
|
||
if (versions) {
|
||
const idx = vp._currentIdx || 0;
|
||
updateVersionDisplay(num, idx);
|
||
}
|
||
}
|
||
};
|
||
|
||
window.closeVersionPanel = function (num) {
|
||
const turn = document.querySelectorAll(".turn.ai")[num - 1];
|
||
if (!turn) return;
|
||
const vp = turn.querySelector(".version-panel");
|
||
if (vp) {
|
||
vp.classList.remove("open");
|
||
// 重置为默认版本(最新/第一个)
|
||
if (vp._versions) {
|
||
vp._currentIdx = 0;
|
||
updateVersionDisplay(num, 0);
|
||
}
|
||
}
|
||
// 取消按钮高亮
|
||
turn
|
||
.querySelectorAll(".action-btn")
|
||
.forEach((b) => b.classList.remove("active-regenerate"));
|
||
};
|
||
|
||
// 版本切换:仅保留左右箭头 + 计数器(序号选项卡已移除)
|
||
|
||
// 版本导航
|
||
window.navVersion = function (num, dir) {
|
||
const turn = document.querySelectorAll(".turn.ai")[num - 1];
|
||
if (!turn) return;
|
||
const vp = turn.querySelector(".version-panel");
|
||
if (!vp || !vp._versions) return;
|
||
const versions = vp._versions;
|
||
let idx = vp._currentIdx || 0;
|
||
idx = Math.max(0, Math.min(versions.length - 1, idx + dir));
|
||
vp._currentIdx = idx;
|
||
updateVersionDisplay(num, idx);
|
||
};
|
||
|
||
// 更新版本显示
|
||
function updateVersionDisplay(num, idx) {
|
||
const turn = document.querySelectorAll(".turn.ai")[num - 1];
|
||
if (!turn) return;
|
||
const vp = turn.querySelector(".version-panel");
|
||
if (!vp || !vp._versions) return;
|
||
const versions = vp._versions;
|
||
const total = versions.length;
|
||
// 更新计数
|
||
const curEl = document.getElementById("vc" + num);
|
||
const totalEl = document.getElementById("vt" + num);
|
||
if (curEl) curEl.textContent = idx + 1;
|
||
if (totalEl) totalEl.textContent = total;
|
||
// 更新内容
|
||
const bodyEl = document.getElementById("vpBody" + num);
|
||
if (bodyEl) {
|
||
bodyEl.innerHTML = versions[idx].text
|
||
.split("\n")
|
||
.map((p) => "<p>" + p + "</p>")
|
||
.join("");
|
||
}
|
||
// 更新按钮状态
|
||
const prevBtn = document.getElementById("vpPrev" + num);
|
||
const nextBtn = document.getElementById("vpNext" + num);
|
||
if (prevBtn) prevBtn.disabled = idx <= 0;
|
||
if (nextBtn) nextBtn.disabled = idx >= total - 1;
|
||
// 同步主消息内容(如果有 md-visible)
|
||
const mainBody = turn.querySelector(".md-body.md-visible");
|
||
if (mainBody) {
|
||
// 如果当前面板打开,主内容同步显示当前版本
|
||
if (vp.classList.contains("open")) {
|
||
mainBody.innerHTML = versions[idx].text
|
||
.split("\n")
|
||
.map((p) => "<p>" + p + "</p>")
|
||
.join("");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 使用此版本
|
||
window.useVersion = function (num) {
|
||
const turn = document.querySelectorAll(".turn.ai")[num - 1];
|
||
if (!turn) return;
|
||
const vp = turn.querySelector(".version-panel");
|
||
if (!vp || !vp._versions) return;
|
||
const idx = vp._currentIdx || 0;
|
||
const version = vp._versions[idx];
|
||
// 更新主内容
|
||
const mainBody = turn.querySelector(".md-body");
|
||
if (mainBody) {
|
||
mainBody.innerHTML = version.text
|
||
.split("\n")
|
||
.map((p) => "<p>" + p + "</p>")
|
||
.join("");
|
||
}
|
||
// 关闭面板
|
||
closeVersionPanel(num);
|
||
// 这个版本变成第一个(当前版本)
|
||
vp._versions = [version, ...vp._versions.filter((_, i) => i !== idx)];
|
||
vp._currentIdx = 0;
|
||
// 提示用户
|
||
const who = turn.querySelector(".who");
|
||
if (who) {
|
||
const note = document.createElement("span");
|
||
note.textContent = " ✓ 已采用";
|
||
note.style.cssText =
|
||
"font-size:12px;color:var(--patina);margin-left:6px;animation:fadeOut 2s forwards";
|
||
note.id = "useNote" + num;
|
||
const old = document.getElementById("useNote" + num);
|
||
if (old) old.remove();
|
||
who.appendChild(note);
|
||
setTimeout(() => {
|
||
if (note.parentNode) note.remove();
|
||
}, 2500);
|
||
}
|
||
};
|
||
|
||
// 添加 fadeOut 动画
|
||
const fadeStyle = document.createElement("style");
|
||
fadeStyle.textContent =
|
||
"@keyframes fadeOut{0%,70%{opacity:1}100%{opacity:0}}";
|
||
document.head.appendChild(fadeStyle);
|
||
|
||
// 为消息3(无 data-versions)添加模拟版本数据
|
||
(function () {
|
||
const turn3 = document.querySelectorAll(".turn.ai")[2]; // 第三个 AI 消息
|
||
if (turn3 && !turn3.hasAttribute("data-versions")) {
|
||
// 给第三个消息初始化的版本面板数据
|
||
const vp = turn3.querySelector(".version-panel");
|
||
if (vp) {
|
||
vp._versions = [
|
||
{
|
||
text: "我想确认一下你的日程,判断你是否在赶工,以及今晚是否该提醒你休息。",
|
||
},
|
||
{ text: "让我看看你的日程安排,确认你今晚是否需要提醒休息。" },
|
||
];
|
||
vp._currentIdx = 0;
|
||
vp._turnIdx = 3;
|
||
const total = document.getElementById("vt3");
|
||
if (total) total.textContent = "2";
|
||
}
|
||
}
|
||
})();
|
||
|
||
// ─── 镜背轨视图切换(铭文往来 / 此刻) ───
|
||
window.toggleRailView = function (view) {
|
||
document
|
||
.querySelectorAll(".rail-tab")
|
||
.forEach((t) =>
|
||
t.classList.toggle("active", t.dataset.view === view),
|
||
);
|
||
document
|
||
.querySelectorAll(".rail-view")
|
||
.forEach((v) =>
|
||
v.classList.toggle("active", v.id === "view-" + view),
|
||
);
|
||
};
|
||
|
||
// ─── 人物下拉(选中值透传后端 character_id) ───
|
||
let charOpen = false;
|
||
let currentChar = { id: "qingwu", name: "青梧", initial: "梧" };
|
||
window.toggleCharDropdown = function (e) {
|
||
e.stopPropagation();
|
||
const dd = document.getElementById("charDropdown");
|
||
charOpen = !charOpen;
|
||
dd.style.display = charOpen ? "block" : "none";
|
||
e.currentTarget
|
||
.querySelector(".arrow")
|
||
?.classList.toggle("open", charOpen);
|
||
};
|
||
window.selectChar = function (opt) {
|
||
currentChar = {
|
||
id: opt.dataset.char,
|
||
name: opt.dataset.name,
|
||
initial: opt.dataset.initial,
|
||
};
|
||
document.getElementById("charLabel").textContent = currentChar.name;
|
||
document.getElementById("charAvatar").textContent = currentChar.initial;
|
||
document.querySelectorAll(".char-opt").forEach((o) => {
|
||
const active = o === opt;
|
||
o.classList.toggle("active", active);
|
||
const av = o.querySelector(".avatar-mini");
|
||
if (av) av.classList.toggle("active-char", active);
|
||
let tag = o.querySelector(".status-tag");
|
||
if (active) {
|
||
if (!tag) {
|
||
tag = document.createElement("span");
|
||
tag.className = "status-tag";
|
||
tag.textContent = "当前";
|
||
o.appendChild(tag);
|
||
} else tag.textContent = "当前";
|
||
} else if (tag && tag.textContent === "当前") tag.remove();
|
||
});
|
||
document.getElementById("charDropdown").style.display = "none";
|
||
document
|
||
.querySelector(".char-switcher-btn .arrow")
|
||
?.classList.remove("open");
|
||
charOpen = false;
|
||
};
|
||
document
|
||
.querySelectorAll(".char-opt")
|
||
.forEach((o) =>
|
||
o.addEventListener("click", () => window.selectChar(o)),
|
||
);
|
||
document.addEventListener("mousedown", function (e) {
|
||
if (charOpen && !e.target.closest(".char-switcher")) {
|
||
document.getElementById("charDropdown").style.display = "none";
|
||
document
|
||
.querySelector(".char-switcher-btn .arrow")
|
||
?.classList.remove("open");
|
||
charOpen = false;
|
||
}
|
||
});
|
||
|
||
// ─── 模型下拉(选中值透传后端 model) ───
|
||
let modelOpen = false;
|
||
let currentModel = "claude-sonnet-5";
|
||
window.toggleModelDropdown = function (e) {
|
||
e.stopPropagation();
|
||
const dd = document.getElementById("modelDropdown");
|
||
modelOpen = !modelOpen;
|
||
dd.style.display = modelOpen ? "block" : "none";
|
||
e.currentTarget
|
||
.querySelector(".arrow")
|
||
?.classList.toggle("open", modelOpen);
|
||
};
|
||
document.querySelectorAll(".model-opt").forEach((o) => {
|
||
o.addEventListener("click", () => {
|
||
currentModel = o.dataset.model;
|
||
document.getElementById("modelLabel").textContent = currentModel;
|
||
document.querySelectorAll(".model-opt").forEach((p) => {
|
||
const active = p === o;
|
||
p.classList.toggle("active", active);
|
||
let tag = p.querySelector(".status-tag");
|
||
if (active) {
|
||
if (!tag) {
|
||
tag = document.createElement("span");
|
||
tag.className = "status-tag";
|
||
tag.textContent = "当前";
|
||
p.appendChild(tag);
|
||
} else tag.textContent = "当前";
|
||
} else if (tag && tag.textContent === "当前") tag.remove();
|
||
});
|
||
document.getElementById("modelDropdown").style.display = "none";
|
||
document
|
||
.querySelector(".model-switcher-btn .arrow")
|
||
?.classList.remove("open");
|
||
modelOpen = false;
|
||
});
|
||
});
|
||
document.addEventListener("mousedown", function (e) {
|
||
if (modelOpen && !e.target.closest(".model-switcher")) {
|
||
document.getElementById("modelDropdown").style.display = "none";
|
||
document
|
||
.querySelector(".model-switcher-btn .arrow")
|
||
?.classList.remove("open");
|
||
modelOpen = false;
|
||
}
|
||
});
|
||
|
||
// ─── 深虑开关 + 档位(开关透传 reasoning: boolean,档位透传 reasoning_effort: low/medium/high) ───
|
||
let reasoningOn = false;
|
||
let currentEffort = "medium";
|
||
window.toggleReasoning = function (btn) {
|
||
reasoningOn = !reasoningOn;
|
||
btn.classList.toggle("active-gilt", reasoningOn);
|
||
document
|
||
.getElementById("effortGroup")
|
||
.classList.toggle("show", reasoningOn);
|
||
};
|
||
document.getElementById("effortGroup").addEventListener("click", (e) => {
|
||
const btn = e.target.closest(".effort-opt");
|
||
if (!btn) return;
|
||
document
|
||
.querySelectorAll("#effortGroup .effort-opt")
|
||
.forEach((b) => b.classList.toggle("active", b === btn));
|
||
currentEffort = btn.dataset.effort;
|
||
});
|
||
|
||
// ─── 博闻开关(透传后端 web_search: boolean,即联网检索) ───
|
||
let bowenEnabled = false;
|
||
window.toggleBowen = function (btn) {
|
||
bowenEnabled = !bowenEnabled;
|
||
btn.classList.toggle("active-patina", bowenEnabled);
|
||
};
|
||
|
||
// ─── 组装发送载荷(character_id / model / reasoning / reasoning_effort / web_search 透传后端) ───
|
||
function getSendPayload() {
|
||
const ta = document.querySelector(".composer textarea");
|
||
return {
|
||
message: ta ? ta.value.trim() : "",
|
||
character_id: currentChar.id, // 当前人物 — 后端按此路由人格/记忆
|
||
model: currentModel, // 模型 — 后端转发至模型网关
|
||
reasoning: reasoningOn, // 深虑开关 — 是否启用深虑(extended thinking)
|
||
reasoning_effort: reasoningOn ? currentEffort : null, // 深虑强度(仅 reasoning=true 时有效)
|
||
web_search: bowenEnabled, // 博闻:是否联网检索
|
||
};
|
||
}
|
||
function showToast(msg) {
|
||
let t = document.getElementById("sendToast");
|
||
if (!t) {
|
||
t = document.createElement("div");
|
||
t.id = "sendToast";
|
||
t.style.cssText =
|
||
'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--glass-strong);backdrop-filter:blur(24px) saturate(160%);border:1px solid var(--glass-border);border-radius:10px;padding:12px 16px;font-family:"JetBrains Mono",monospace;font-size:12px;line-height:1.6;color:var(--fg);max-width:92vw;z-index:100;box-shadow:0 8px 32px rgba(0,0,0,.4);white-space:pre-wrap;text-align:left;opacity:0;transition:opacity .25s;';
|
||
document.body.appendChild(t);
|
||
}
|
||
t.textContent = msg;
|
||
requestAnimationFrame(() => {
|
||
t.style.opacity = "1";
|
||
});
|
||
clearTimeout(t._timer);
|
||
t._timer = setTimeout(() => {
|
||
t.style.opacity = "0";
|
||
}, 4000);
|
||
}
|
||
document
|
||
.querySelector(".composer .send-btn")
|
||
.addEventListener("click", () => {
|
||
const payload = getSendPayload();
|
||
if (!payload.message) {
|
||
showToast("⚠ 镜面未铭文(请先输入内容)");
|
||
return;
|
||
}
|
||
console.log("→ 发送载荷(透传后端 /api/v1/chat):", payload);
|
||
showToast(
|
||
"→ 已组装发送载荷(见控制台):\n" +
|
||
JSON.stringify(payload, null, 2),
|
||
);
|
||
});
|
||
|
||
/* 主题切换由全局 _theme.js 接管(#theme-toggle 委托 + localStorage 持久化) */
|
||
</script>
|
||
<script src="_theme.js" defer></script>
|
||
|
||
</body>
|
||
</html>
|