Files
wecom_it_smart_desk/docs/01-产品文档/05-用户端H5/原型-REQ-用户-006-智能推荐重构-v1.0.html
T

990 lines
37 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="zh-CN" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能推荐重构原型 v1.0 · REQ-用户-006</title>
<style>
/* ============================================================================
CSS 变量(沿用 H5 用户端原型 v2.0 风格)
============================================================================ */
:root {
--accent: #3b82f6;
--accent-soft: rgba(59, 130, 246, 0.12);
--bg-primary: #f5f6fa;
--bg-secondary: #ffffff;
--bg-tertiary: #eef0f5;
--text-primary: #1a1a2e;
--text-secondary: #555770;
--text-tertiary: #9ca3af;
--border-color: #e5e7eb;
--shadow: 0 1px 4px rgba(0,0,0,0.06);
--radius: 12px;
/* v1.0 新增:layer 边框颜色(PRD §4.5 UI 规范)*/
--color-layer-l1-border: #22c55e; /* L1 相关推荐:绿色 */
--color-layer-l2-border: #f97316; /* L2 运维提醒:橙色 */
--color-layer-l3-border: #94a3b8; /* L3 常用资源:灰色 */
--color-layer-a-border: #3b82f6; /* 来源 A Dify action:蓝色 */
--color-progress-border: #8b5cf6; /* 审批进度:紫色 */
}
[data-theme="dark"] {
--accent: #3b82f6;
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-tertiary: #162032;
--text-primary: #e2e8f0;
--text-secondary: #94a3b8;
--text-tertiary: #64748b;
--border-color: #334155;
--shadow: 0 1px 4px rgba(0,0,0,0.3);
--color-layer-l1-border: #4ade80;
--color-layer-l2-border: #fb923c;
--color-layer-l3-border: #64748b;
--color-layer-a-border: #60a5fa;
--color-progress-border: #a78bfa;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
padding: 20px;
}
/* ============================================================================
顶部导航
============================================================================ */
.topbar {
max-width: 1400px;
margin: 0 auto 20px;
background: var(--bg-secondary);
border-radius: var(--radius);
padding: 16px 24px;
box-shadow: var(--shadow);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.topbar h1 { font-size: 18px; color: var(--accent); }
.topbar .meta { color: var(--text-secondary); font-size: 13px; }
.topbar .actions button {
background: var(--accent-soft);
color: var(--accent);
border: none;
padding: 6px 14px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
margin-left: 8px;
}
/* ============================================================================
场景标签页
============================================================================ */
.scene-tabs {
max-width: 1400px;
margin: 0 auto 20px;
background: var(--bg-secondary);
border-radius: var(--radius);
padding: 12px 16px;
box-shadow: var(--shadow);
display: flex;
gap: 8px;
overflow-x: auto;
}
.scene-tab {
background: transparent;
border: 1px solid var(--border-color);
color: var(--text-secondary);
padding: 8px 14px;
border-radius: 8px;
cursor: pointer;
font-size: 13px;
white-space: nowrap;
transition: all 0.2s;
}
.scene-tab.active {
background: var(--accent);
color: white;
border-color: var(--accent);
}
/* ============================================================================
场景内容区
============================================================================ */
.scene {
max-width: 1400px;
margin: 0 auto;
background: var(--bg-secondary);
border-radius: var(--radius);
padding: 24px;
box-shadow: var(--shadow);
display: none;
}
.scene.active { display: block; }
.scene h2 {
font-size: 18px;
margin-bottom: 8px;
color: var(--text-primary);
}
.scene .subtitle {
color: var(--text-secondary);
font-size: 13px;
margin-bottom: 20px;
}
/* ============================================================================
对比布局(重构前 vs 重构后)
============================================================================ */
.compare {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 900px) { .compare { grid-template-columns: 1fr; } }
.compare-col h3 {
font-size: 14px;
margin-bottom: 12px;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 8px;
}
.compare-col.old h3::before {
content: "重构前";
background: #fee2e2;
color: #dc2626;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
}
.compare-col.new h3::before {
content: "重构后";
background: #dcfce7;
color: #16a34a;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
}
/* ============================================================================
H5 模拟容器
============================================================================ */
.h5-mock {
background: var(--bg-tertiary);
border-radius: 8px;
padding: 12px;
display: grid;
grid-template-columns: 1fr 280px;
gap: 12px;
min-height: 500px;
}
.h5-mock.single-col { grid-template-columns: 1fr; }
.h5-chat, .h5-right {
background: var(--bg-secondary);
border-radius: 8px;
padding: 12px;
overflow-y: auto;
max-height: 600px;
}
/* ============================================================================
气泡样式
============================================================================ */
.bubble {
max-width: 75%;
padding: 10px 14px;
border-radius: 10px;
margin-bottom: 8px;
font-size: 13px;
}
.bubble.user {
background: var(--accent);
color: white;
margin-left: auto;
border-bottom-right-radius: 2px;
}
.bubble.ai {
background: var(--bg-tertiary);
color: var(--text-primary);
border-bottom-left-radius: 2px;
}
.bubble.system {
background: transparent;
color: var(--text-tertiary);
font-size: 12px;
text-align: center;
margin: 8px auto;
}
/* ============================================================================
推荐卡片(核心:4 类卡片样式)
============================================================================ */
.rec-card {
background: var(--bg-secondary);
border-left: 4px solid;
border-radius: 8px;
padding: 12px;
margin-bottom: 10px;
position: relative;
box-shadow: 0 1px 2px rgba(0,0,0,0.04);
font-size: 13px;
}
.rec-card.layer-L1, .rec-card.source-keyword { border-left-color: var(--color-layer-l1-border); }
.rec-card.layer-L2, .rec-card.source-profile { border-left-color: var(--color-layer-l2-border); }
.rec-card.layer-L3, .rec-card.source-role { border-left-color: var(--color-layer-l3-border); }
.rec-card.layer-A, .rec-card.source-dify { border-left-color: var(--color-layer-a-border); }
.rec-card.layer-progress, .rec-card.source-approval { border-left-color: var(--color-progress-border); }
.rec-card .rec-title {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 4px;
display: flex;
align-items: center;
gap: 6px;
}
.rec-card .rec-icon { font-size: 16px; }
.rec-card .rec-desc {
color: var(--text-secondary);
font-size: 12px;
margin-bottom: 8px;
line-height: 1.5;
}
.rec-card .rec-items {
list-style: none;
display: flex;
flex-direction: column;
gap: 4px;
}
.rec-card .rec-items li {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 0;
font-size: 12px;
color: var(--text-secondary);
}
.rec-card .rec-items li::before {
content: "•";
color: var(--accent);
}
.rec-card .rec-items a {
color: var(--accent);
text-decoration: none;
}
.rec-card .rec-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed var(--border-color);
font-size: 11px;
color: var(--text-tertiary);
}
.rec-card .rec-source-tag {
background: var(--bg-tertiary);
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
}
.rec-card .rec-dismiss {
position: absolute;
top: 6px;
right: 8px;
background: transparent;
border: none;
color: var(--text-tertiary);
cursor: pointer;
font-size: 14px;
line-height: 1;
}
/* 操作步骤可展开样式 */
.rec-card.expandable .rec-steps {
display: none;
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed var(--border-color);
}
.rec-card.expandable.open .rec-steps { display: block; }
.rec-card .rec-toggle {
background: transparent;
border: 1px solid var(--border-color);
color: var(--accent);
font-size: 11px;
padding: 2px 8px;
border-radius: 4px;
cursor: pointer;
margin-top: 4px;
}
.rec-card .step-item {
padding: 6px 0;
border-bottom: 1px solid var(--border-color);
font-size: 12px;
}
.rec-card .step-item:last-child { border-bottom: none; }
.rec-card .step-num {
background: var(--accent);
color: white;
border-radius: 50%;
width: 18px;
height: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 10px;
margin-right: 6px;
}
/* 联系人/联系窗口卡片样式 */
.rec-card .contact-window-btn, .rec-card .contact-card-btn {
display: inline-block;
background: var(--accent-soft);
color: var(--accent);
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
text-decoration: none;
margin-top: 6px;
}
/* 审批进度卡片样式 */
.rec-card.layer-progress .progress-bar {
height: 4px;
background: var(--bg-tertiary);
border-radius: 2px;
margin-top: 6px;
overflow: hidden;
}
.rec-card.layer-progress .progress-fill {
height: 100%;
background: var(--color-progress-border);
border-radius: 2px;
transition: width 0.3s;
}
.rec-card.layer-progress .progress-status {
display: inline-block;
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
margin-top: 4px;
}
.rec-card.layer-progress .progress-status.pending {
background: rgba(251, 146, 60, 0.15);
color: #ea580c;
}
.rec-card.layer-progress .progress-status.approved {
background: rgba(34, 197, 94, 0.15);
color: #16a34a;
}
/* ============================================================================
旧的"重构前"样式(保留做对比)
============================================================================ */
.rec-section-title {
font-size: 12px;
color: var(--text-tertiary);
margin-bottom: 8px;
padding: 0 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.rec-card-old {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
font-size: 13px;
}
/* ============================================================================
注释说明区
============================================================================ */
.notes {
background: var(--bg-tertiary);
border-left: 3px solid var(--accent);
padding: 12px 16px;
border-radius: 6px;
margin-top: 16px;
font-size: 13px;
color: var(--text-secondary);
}
.notes strong { color: var(--text-primary); }
.notes ul { padding-left: 20px; margin-top: 6px; }
.notes li { margin-bottom: 4px; }
/* ============================================================================
冷启动空状态
============================================================================ */
.empty-state {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
min-height: 400px;
color: var(--text-tertiary);
font-size: 13px;
font-style: italic;
}
</style>
</head>
<body>
<!-- 顶部导航 -->
<div class="topbar">
<div>
<h1>智能推荐重构原型 v1.0</h1>
<div class="meta">REQ-用户-006 · 基于 PRD v1.0-Frozen + 技术方案 v1.0 · 2026-07-28</div>
</div>
<div class="actions">
<button onclick="toggleTheme()">🌓 切换主题</button>
</div>
</div>
<!-- 场景标签 -->
<div class="scene-tabs">
<button class="scene-tab active" onclick="showScene(0)">1. 重构前 vs 重构后</button>
<button class="scene-tab" onclick="showScene(1)">2. 冷启动空状态(决策 ① A</button>
<button class="scene-tab" onclick="showScene(2)">3. AI 回复瞬间 FIFO 插入</button>
<button class="scene-tab" onclick="showScene(3)">4. 4 类卡片样式</button>
<button class="scene-tab" onclick="showScene(4)">5. 跨会话持久化</button>
<button class="scene-tab" onclick="showScene(5)">6. 话题切换</button>
<button class="scene-tab" onclick="showScene(6)">7. 触发失败降级</button>
</div>
<!-- 场景 1:重构前 vs 重构后 -->
<div class="scene active" id="scene-0">
<h2>场景 1:重构前 vs 重构后 整体对比</h2>
<div class="subtitle">核心改动:移除"智能推荐"分类标题 + 边框颜色区分 layer + FIFO 插入</div>
<div class="compare">
<!-- 重构前 -->
<div class="compare-col old">
<h3>v2.3 现状(有标题 + 分组)</h3>
<div class="h5-mock">
<div class="h5-chat">
<div class="bubble user">VPN 连不上怎么办?</div>
<div class="bubble ai">请尝试以下步骤:1. 检查网络 2. 重启 aTrust</div>
</div>
<div class="h5-right">
<div class="rec-section-title">⚡ 相关推荐</div>
<div class="rec-card-old">
<div style="font-weight:600">VPN 相关信息</div>
<div style="font-size:12px;color:#555770">客户端下载、服务器地址...</div>
</div>
<div class="rec-section-title" style="margin-top:12px">🔔 运维提醒</div>
<div class="rec-card-old">
<div style="font-weight:600">火绒病毒库过期</div>
<div style="font-size:12px;color:#555770">病毒库已 7 天未更新</div>
</div>
<div class="rec-section-title" style="margin-top:12px">💼 常用资源</div>
<div class="rec-card-old">
<div style="font-weight:600">开发者资源</div>
<div style="font-size:12px;color:#555770">代码仓库、API 文档...</div>
</div>
</div>
</div>
<div class="notes">
<strong>问题:</strong>
<ul>
<li>三个分类标题占据空间,视觉杂乱</li>
<li>无 FIFO,新旧卡片位置固定</li>
<li>分组标题强化"分类"概念,用户误以为必须点击分类切换</li>
<li>卡片样式无 layer 区分,仅靠标题文字</li>
</ul>
</div>
</div>
<!-- 重构后 -->
<div class="compare-col new">
<h3>v1.0 重构后(无标题 + 边框颜色 + FIFO)</h3>
<div class="h5-mock">
<div class="h5-chat">
<div class="bubble user">VPN 连不上怎么办?</div>
<div class="bubble ai">请尝试以下步骤:1. 检查网络 2. 重启 aTrust<br><span style="font-size:11px;color:#9ca3af">📋💻</span></div>
</div>
<div class="h5-right">
<!-- v1.0 重构后:无任何分类标题 -->
<!-- v1.0 重构后:FIFO 顺序,最新在最上 -->
<div class="rec-card source-keyword">
<div class="rec-title"><span class="rec-icon">🔐</span>VPN 相关信息</div>
<div class="rec-desc">VPN 连接所需资源</div>
<ul class="rec-items">
<li>客户端下载(支持 Windows/macOS</li>
<li>服务器地址:vpn.servyou-it.com</li>
<li>申请审批 → <a>去申请</a></li>
</ul>
<div class="rec-meta">
<span class="rec-source-tag">L1 · 关键词命中</span>
<span>2 秒前</span>
</div>
</div>
<div class="rec-card source-profile">
<div class="rec-title"><span class="rec-icon">🦠</span>火绒病毒库过期</div>
<div class="rec-desc">病毒库已 7 天未更新,建议立即升级</div>
<div class="rec-meta">
<span class="rec-source-tag">L2 · 画像触发</span>
<span>2 秒前</span>
</div>
</div>
<div class="rec-card source-role">
<div class="rec-title"><span class="rec-icon">💻</span>开发者资源</div>
<div class="rec-desc">代码仓库、API 文档、测试环境申请</div>
<div class="rec-meta">
<span class="rec-source-tag">L3 · 角色匹配</span>
<span>2 秒前</span>
</div>
</div>
</div>
</div>
<div class="notes">
<strong>改进:</strong>
<ul>
<li>✅ 移除所有分类标题("⚡ 相关推荐"/"🔔 运维提醒"/"💼 常用资源"),视觉更清爽</li>
<li>✅ 边框颜色区分 layer:🟢 L1 / 🟠 L2 / ⚪ L3 / 🔵 A / 🟣 progress</li>
<li>✅ FIFO:新卡片插入最上方(最新在上)</li>
<li>✅ 来源标签在卡片底部可查(L1/L2/L3 + 来源类型)</li>
</ul>
</div>
</div>
</div>
</div>
<!-- 场景 2:冷启动空状态 -->
<div class="scene" id="scene-1">
<h2>场景 2:冷启动空状态(PRD §4.7.4 决策 ① A</h2>
<div class="subtitle">用户进入会话但未发言 / AI 还未回复 → 右侧栏完全空白(不强求兜底)</div>
<div class="h5-mock single-col">
<div class="h5-chat">
<div class="bubble user">VPN 连不上怎么办?</div>
<div class="bubble system">(消息已发送,等待 AI 回复...</div>
</div>
</div>
<div class="h5-mock">
<div class="h5-chat">
<div class="bubble user">VPN 连不上怎么办?</div>
<div class="bubble system">AI 正在思考...</div>
</div>
<div class="h5-right">
<!-- 冷启动空状态:完全空白 -->
<div class="empty-state">
<!-- 不显示任何卡片 -->
<!-- 不显示"暂无推荐"提示卡 -->
<!-- 不显示"试试问 XXX"引导 -->
<!-- 完全空白,等待 AI 回复到达 -->
</div>
</div>
</div>
<div class="notes">
<strong>PRD §4.7.4 决策 ① A 落地:</strong>
<ul>
<li>✅ 进会话 5s 内无任何消息:右侧栏空白</li>
<li>✅ 首条消息刚发出、AI 未回复:右侧栏空白</li>
<li>✅ AI 回复到达瞬间:按 §4.7.3 合并规则填充真实推荐</li>
<li>✅ 完全无数据:右侧栏空白(不显示兜底提示)</li>
<li>❌ 重构前(旧方案):进会话就显示"⚡ 相关推荐"占位</li>
<li>❌ 重构前(旧方案):无消息时显示"VPN/Outlook/打印机"等假数据</li>
</ul>
</div>
</div>
<!-- 场景 3:AI 回复瞬间 FIFO 插入 -->
<div class="scene" id="scene-2">
<h2>场景 3:AI 回复瞬间 FIFO 插入</h2>
<div class="subtitle">新生成的卡片插入到右侧栏最上方(最新优先)</div>
<div class="h5-mock">
<div class="h5-chat">
<div class="bubble user">VPN 连不上怎么办?</div>
<div class="bubble ai">
请尝试以下步骤:<br>
1. 检查网络连接<br>
2. 重启 aTrust 客户端<br>
3. 如仍无法连接,<strong>右侧已为您准备好重连步骤</strong>
<div style="font-size:11px;color:#9ca3af;margin-top:4px">📋💻</div>
</div>
<div class="bubble user">顺便帮我看下电脑补丁情况</div>
<div class="bubble ai">
您的电脑有 12 个安全补丁未安装,<strong>右侧已准备好补丁列表</strong>
<div style="font-size:11px;color:#9ca3af;margin-top:4px">💻⚠️</div>
</div>
</div>
<div class="h5-right">
<!-- FIFO:新卡片在最上方 -->
<!-- 最新:补丁缺失提醒(刚生成的 L1 推荐)-->
<div class="rec-card source-keyword">
<div class="rec-title"><span class="rec-icon">🔧</span>系统补丁缺失</div>
<div class="rec-desc">您的电脑有 12 个安全补丁未安装,可能存在安全风险</div>
<div class="rec-meta">
<span class="rec-source-tag">L1 · 关键词"补丁"</span>
<span>刚刚</span>
</div>
</div>
<!-- 之前:VPN 步骤 -->
<div class="rec-card source-dify expandable open">
<div class="rec-title"><span class="rec-icon">🔐</span>VPN 重连步骤</div>
<div class="rec-desc">完整操作步骤,点击展开</div>
<button class="rec-toggle" onclick="toggleExpand(this)">收起 ▲</button>
<div class="rec-steps">
<div class="step-item"><span class="step-num">1</span>打开 aTrust 客户端</div>
<div class="step-item"><span class="step-num">2</span>点击"重新连接"</div>
<div class="step-item"><span class="step-num">3</span>输入账号密码</div>
<div class="step-item"><span class="step-num">4</span>如失败,重启客户端</div>
</div>
<div class="rec-meta">
<span class="rec-source-tag">A · Dify 步骤卡</span>
<span>5 秒前</span>
</div>
</div>
</div>
</div>
<div class="notes">
<strong>FIFO 规则(PRD §4.7.3):</strong>
<ul>
<li>✅ 新卡片插入最上方("刚刚"在最上)</li>
<li>✅ 旧卡片往下排("5 秒前"在下)</li>
<li>✅ 上限 3 张:超出部分自动滚出(用户看不见)</li>
<li>✅ 来源 A(Dify 步骤卡)用蓝色边框 + 可展开/收起</li>
</ul>
</div>
</div>
<!-- 场景 44 类卡片样式 -->
<div class="scene" id="scene-3">
<h2>场景 44 类卡片样式</h2>
<div class="subtitle">基于 PRD §4.5 + §4.6 决策的完整卡片类型展示</div>
<!-- 4.1 操作步骤(A 来源,可展开) -->
<h3 style="margin-top:20px;margin-bottom:8px;color:var(--text-secondary);font-size:14px">① 操作步骤(来自 Dify action · 可展开)</h3>
<div class="rec-card source-dify expandable">
<div class="rec-title"><span class="rec-icon">🔐</span>VPN 重连操作步骤</div>
<div class="rec-desc">点击展开查看完整步骤</div>
<button class="rec-toggle" onclick="toggleExpand(this)">展开 ▼</button>
<div class="rec-steps">
<div class="step-item"><span class="step-num">1</span>打开 aTrust 客户端</div>
<div class="step-item"><span class="step-num">2</span>点击"重新连接"</div>
<div class="step-item"><span class="step-num">3</span>输入账号密码</div>
<div class="step-item"><span class="step-num">4</span>如失败,重启客户端</div>
</div>
<div class="rec-meta">
<span class="rec-source-tag">A · Dify 步骤卡</span>
<span>点击操作按钮 → aTrust 重连</span>
</div>
</div>
<!-- 4.2 联系窗口(来自 Dify action · 仅左侧栏) -->
<h3 style="margin-top:20px;margin-bottom:8px;color:var(--text-secondary);font-size:14px">② 联系窗口(仅左侧栏出现,右侧栏不显示)</h3>
<div style="background:var(--bg-tertiary);padding:12px;border-radius:8px;color:var(--text-secondary);font-size:13px">
位置:左侧气泡 / 右侧栏不出现<br>
关联:员工服务窗口链接(chat_id / openid<br>
示例气泡(左侧):<br>
<div class="rec-card source-dify" style="margin-top:8px;max-width:50%">
<div class="rec-title"><span class="rec-icon">💬</span>IT 服务窗口</div>
<div class="rec-desc">需要人工帮助?点击下方按钮跳转 IT 服务窗口</div>
<a class="contact-window-btn" href="javascript:void(0)">打开 IT 服务窗口</a>
<div class="rec-meta">
<span class="rec-source-tag">A · Dify action</span>
</div>
</div>
</div>
<!-- 4.3 联系人名片(来自 Dify action · 仅左侧栏) -->
<h3 style="margin-top:20px;margin-bottom:8px;color:var(--text-secondary);font-size:14px">③ 联系人名片(仅左侧栏出现,右侧栏不显示)</h3>
<div style="background:var(--bg-tertiary);padding:12px;border-radius:8px;color:var(--text-secondary);font-size:13px">
位置:左侧气泡 / 右侧栏不出现<br>
关联:员工名片(user_id)<br>
示例气泡(左侧):<br>
<div class="rec-card source-dify" style="margin-top:8px;max-width:50%">
<div class="rec-title"><span class="rec-icon">👤</span>联系:财务部 张三</div>
<div class="rec-desc">财务部经理 · 工号 10086</div>
<a class="contact-card-btn" href="javascript:void(0)">查看名片</a>
<div class="rec-meta">
<span class="rec-source-tag">A · Dify 路由名片</span>
</div>
</div>
</div>
<!-- 4.4 下载/链接(来自 Dify action · 仅左侧栏) -->
<h3 style="margin-top:20px;margin-bottom:8px;color:var(--text-secondary);font-size:14px">④ 下载/链接(仅左侧栏出现,右侧栏不显示)</h3>
<div style="background:var(--bg-tertiary);padding:12px;border-radius:8px;color:var(--text-secondary);font-size:13px">
位置:左侧气泡 / 右侧栏不出现<br>
关联:直接下载链接(exe/pdf/zip<br>
示例气泡(左侧):<br>
<div class="rec-card source-dify" style="margin-top:8px;max-width:50%">
<div class="rec-title"><span class="rec-icon">📦</span>VPN 客户端下载</div>
<div class="rec-desc">Windows 10/11 / macOS · 32.5 MB</div>
<a class="contact-card-btn" href="javascript:void(0)">立即下载</a>
<div class="rec-meta">
<span class="rec-source-tag">A · Dify 资源链接</span>
</div>
</div>
</div>
<!-- 4.5 常用资源 L3(来自角色匹配 · 仅左侧栏) -->
<h3 style="margin-top:20px;margin-bottom:8px;color:var(--text-secondary);font-size:14px">⑤ 常用资源 L3(来自角色匹配 · 仅左侧栏出现)</h3>
<div style="background:var(--bg-tertiary);padding:12px;border-radius:8px;color:var(--text-secondary);font-size:13px">
位置:左侧气泡("试试问:xxx"形式) / 右侧栏不出现<br>
触发:profile.position 匹配 role_assets<br>
示例气泡(左侧):<br>
<div class="rec-card source-role" style="margin-top:8px;max-width:50%">
<div class="rec-title"><span class="rec-icon">💻</span>开发者常用资源</div>
<div class="rec-desc">代码仓库 · API 文档 · 测试环境申请</div>
<ul class="rec-items">
<li>git.servyou-it.com</li>
<li>api-docs.servyou-it.com</li>
<li>测试环境申请 → <a>去申请</a></li>
</ul>
<div class="rec-meta">
<span class="rec-source-tag">L3 · 角色"开发"</span>
</div>
</div>
</div>
<!-- 4.6 审批进度(来自 T2 webhook · 右侧栏回流) -->
<h3 style="margin-top:20px;margin-bottom:8px;color:var(--text-secondary);font-size:14px">⑥ 审批进度回流(来自 T2 webhook · 右侧栏顶部插入)</h3>
<div class="rec-card layer-progress">
<div class="rec-title"><span class="rec-icon">📋</span>VPN 申请进度</div>
<div class="rec-desc">您发起的 VPN 权限申请</div>
<div class="progress-bar"><div class="progress-fill" style="width:60%"></div></div>
<div style="display:flex;justify-content:space-between;margin-top:4px">
<span class="progress-status approved">已审批</span>
<span style="font-size:11px;color:var(--text-tertiary)">60% · 等待 IT 开通</span>
</div>
<div class="rec-meta">
<span class="rec-source-tag">progress · T2 事件触发</span>
<span>2 分钟前更新</span>
</div>
</div>
</div>
<!-- 场景 5:跨会话持久化 -->
<div class="scene" id="scene-4">
<h2>场景 5:跨会话持久化(PRD §4.7.7 决策 ⑤ B</h2>
<div class="subtitle">L2/L3/progress 持久化到 localStorageL1/A 不持久</div>
<h3 style="margin-top:12px;margin-bottom:8px;color:var(--text-secondary);font-size:14px">会话 1(首次打开,3 张卡都已生成)</h3>
<div class="h5-mock">
<div class="h5-chat">
<div class="bubble user">帮我看看电脑情况</div>
<div class="bubble ai">已检测,您的电脑有 12 个补丁待安装,病毒库已过期 7 天</div>
</div>
<div class="h5-right">
<div class="rec-card source-profile">
<div class="rec-title"><span class="rec-icon">🦠</span>火绒病毒库过期</div>
<div class="rec-desc">病毒库已 7 天未更新</div>
<div class="rec-meta">
<span class="rec-source-tag">L2 · 持久化</span>
<span>✅ 已存 localStorage</span>
</div>
</div>
<div class="rec-card layer-progress">
<div class="rec-title"><span class="rec-icon">📋</span>VPN 申请进度</div>
<div class="rec-desc">已审批 · 等待 IT 开通</div>
<div class="progress-bar"><div class="progress-fill" style="width:60%"></div></div>
<div class="rec-meta">
<span class="rec-source-tag">progress · 持久化直到完结</span>
<span>✅ 已存 localStorage</span>
</div>
</div>
<div class="rec-card source-keyword">
<div class="rec-title"><span class="rec-icon">🔧</span>系统补丁缺失</div>
<div class="rec-desc">12 个安全补丁未安装</div>
<div class="rec-meta">
<span class="rec-source-tag">L1 · ❌ 不持久</span>
<span>会话结束即清</span>
</div>
</div>
</div>
</div>
<h3 style="margin-top:20px;margin-bottom:8px;color:var(--text-secondary);font-size:14px">会话 2(用户关闭浏览器后再次打开,localStorage 加载)</h3>
<div class="h5-mock">
<div class="h5-chat">
<div class="bubble user">(新会话,用户未发言)</div>
</div>
<div class="h5-right">
<!-- 跨会话持久化的 L2 + progress 仍在 -->
<!-- L1 已自动清空(不持久) -->
<div class="rec-card source-profile">
<div class="rec-title"><span class="rec-icon">🦠</span>火绒病毒库过期</div>
<div class="rec-desc">病毒库已 7 天未更新(来自 localStorage</div>
<div class="rec-meta">
<span class="rec-source-tag">L2 · 跨会话持久</span>
<span>30 天后过期</span>
</div>
</div>
<div class="rec-card layer-progress">
<div class="rec-title"><span class="rec-icon">📋</span>VPN 申请进度</div>
<div class="rec-desc">已审批 · 等待 IT 开通(来自 localStorage</div>
<div class="progress-bar"><div class="progress-fill" style="width:60%"></div></div>
<div class="rec-meta">
<span class="rec-source-tag">progress · 跨会话持久</span>
<span>完结后清除</span>
</div>
</div>
<!-- L1 不在(已被清空) -->
</div>
</div>
<div class="notes">
<strong>持久化规则(PRD §4.7.7):</strong>
<table style="margin-top:8px;width:100%;border-collapse:collapse">
<tr style="background:var(--bg-tertiary)">
<th style="padding:6px;text-align:left;font-size:12px">类型</th>
<th style="padding:6px;text-align:left;font-size:12px">持久化</th>
<th style="padding:6px;text-align:left;font-size:12px">过期规则</th>
</tr>
<tr><td style="padding:6px;font-size:12px">L1 关键词</td><td style="padding:6px;font-size:12px"></td><td style="padding:6px;font-size:12px">话题切换 / 会话结束即清</td></tr>
<tr><td style="padding:6px;font-size:12px">L2 画像</td><td style="padding:6px;font-size:12px">✅ 30 天</td><td style="padding:6px;font-size:12px">30 天后或画像更新时清</td></tr>
<tr><td style="padding:6px;font-size:12px">L3 角色</td><td style="padding:6px;font-size:12px"></td><td style="padding:6px;font-size:12px">角色变更时清(极少)</td></tr>
<tr><td style="padding:6px;font-size:12px">来源 A 步骤</td><td style="padding:6px;font-size:12px"></td><td style="padding:6px;font-size:12px">话题切换 / 会话结束即清</td></tr>
<tr><td style="padding:6px;font-size:12px">progress</td><td style="padding:6px;font-size:12px"></td><td style="padding:6px;font-size:12px">审批完结时清</td></tr>
</table>
</div>
</div>
<!-- 场景 6:话题切换 -->
<div class="scene" id="scene-5">
<h2>场景 6:话题切换(PRD §4.7.6 决策 ④ B</h2>
<div class="subtitle">用户切换话题后,L1 推荐清空;L2/L3/progress 保留</div>
<div class="h5-mock">
<div class="h5-chat">
<div class="bubble user">VPN 连不上</div>
<div class="bubble ai">请尝试重连 aTrust 客户端</div>
<div class="bubble user" style="background:#22c55e">顺便帮我预约一下会议室</div>
<!-- v1.0Jaccard 相似度 < 0.3,触发话题切换 -->
<div class="bubble system" style="margin-top:8px;color:#22c55e">话题已切换,旧的 L1 推荐已清空 ✅</div>
<div class="bubble ai">好的,您要预约哪个会议室?</div>
</div>
<div class="h5-right">
<!-- L1 已清空(VPN 卡消失)-->
<!-- L2/L3/progress 保留(与话题无关)-->
<div class="rec-card source-profile">
<div class="rec-title"><span class="rec-icon">🦠</span>火绒病毒库过期</div>
<div class="rec-desc">病毒库已 7 天未更新</div>
<div class="rec-meta">
<span class="rec-source-tag">L2 · 保留</span>
<span>画像无关</span>
</div>
</div>
<div class="rec-card layer-progress">
<div class="rec-title"><span class="rec-icon">📋</span>VPN 申请进度</div>
<div class="rec-desc">已审批 · 等待 IT 开通</div>
<div class="progress-bar"><div class="progress-fill" style="width:60%"></div></div>
<div class="rec-meta">
<span class="rec-source-tag">progress · 保留</span>
<span>跨会话持久</span>
</div>
</div>
</div>
</div>
<div class="notes">
<strong>话题切换检测(PRD §4.7.6):</strong>
<ul>
<li>✅ 关键词突变:新消息与最近 3 条消息的 Jaccard 相似度 &lt; 0.3 → 清空 L1</li>
<li>✅ 意图突变:Dify 返回的 intent_type 与上一条不同 → 清空对应 layer</li>
<li>✅ 会话分隔:用户点"结束会话"或新建会话 → 清空所有跨会话推荐</li>
<li>✅ L2/L3/progress 不清(与当前话题无关)</li>
</ul>
</div>
</div>
<!-- 场景 7:触发失败降级 -->
<div class="scene" id="scene-6">
<h2>场景 7:触发失败降级(PRD §4.7.5 决策 ③ B</h2>
<div class="subtitle">C 失败 → D 降级;全部失败 → 右侧栏空</div>
<h3 style="margin-top:12px;margin-bottom:8px;color:var(--text-secondary);font-size:14px">7.1 画像 API 故障(C 失败 → D 降级)</h3>
<div class="h5-mock">
<div class="h5-chat">
<div class="bubble user">我要申请 VPN</div>
<div class="bubble ai">好的,请填写 VPN 申请表单</div>
</div>
<div class="h5-right">
<!-- L1 关键词命中 -->
<div class="rec-card source-keyword">
<div class="rec-title"><span class="rec-icon">🔐</span>VPN 相关信息</div>
<div class="rec-desc">VPN 连接所需资源</div>
<div class="rec-meta">
<span class="rec-source-tag">L1 · 正常</span>
<span></span>
</div>
</div>
<!-- C 失败 → 降级到 D -->
<div class="rec-card source-role" style="opacity:0.8">
<div class="rec-title"><span class="rec-icon">⚠️</span>开发者资源(降级展示)</div>
<div class="rec-desc">画像 API 故障,降级使用 L3 角色通用资源</div>
<div class="rec-meta">
<span class="rec-source-tag">L3 · C→D 降级</span>
<span style="color:#f97316">画像不可用</span>
</div>
</div>
</div>
</div>
<h3 style="margin-top:20px;margin-bottom:8px;color:var(--text-secondary);font-size:14px">7.2 全部失败(右侧栏空)</h3>
<div class="h5-mock">
<div class="h5-chat">
<div class="bubble user">xxx(无关内容)</div>
<div class="bubble ai">您好,请问需要什么帮助?</div>
</div>
<div class="h5-right">
<div class="empty-state">
<!-- 全部失败:右侧栏空 -->
</div>
</div>
</div>
<div class="notes">
<strong>降级链路(PRD §4.7.5):</strong>
<ul>
<li>[A 失败] → [B + C + D]</li>
<li>[B 失败] → [C + D]</li>
<li>[C 失败] → [D] ★ 关键降级(保证有内容)</li>
<li>[D 失败] → []</li>
<li>[全部失败] → [] (按 §4.7.4 走空状态,与决策 ① A 保持一致)</li>
</ul>
</div>
</div>
<script>
// 场景切换
function showScene(idx) {
document.querySelectorAll('.scene').forEach(s => s.classList.remove('active'));
document.querySelectorAll('.scene-tab').forEach(t => t.classList.remove('active'));
document.getElementById('scene-' + idx).classList.add('active');
document.querySelectorAll('.scene-tab')[idx].classList.add('active');
}
// 主题切换
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute('data-theme');
html.setAttribute('data-theme', current === 'light' ? 'dark' : 'light');
}
// 操作步骤可展开
function toggleExpand(btn) {
const card = btn.closest('.rec-card');
card.classList.toggle('open');
btn.textContent = card.classList.contains('open') ? '收起 ▲' : '展开 ▼';
}
</script>
</body>
</html>