@
chore: delete old layout/common/tabs components before redesign @
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
<h2>A 方案 · 浅色背景版</h2>
|
||||
<p class="subtitle">Magenta 主色 + 全浅色背景 — 对比两种浅色处理方式</p>
|
||||
|
||||
<style>
|
||||
.slide-preview {
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 16/9;
|
||||
position: relative;
|
||||
font-family: 'Calibri', 'Segoe UI', sans-serif;
|
||||
border: 1px solid #ddd;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.tag-row { display:flex; gap:6px; margin-top:8px; flex-wrap:wrap; }
|
||||
.tag { padding:3px 8px; border-radius:4px; font-size:11px; font-weight:600; letter-spacing:0.5px; }
|
||||
.mini-card { border-radius:4px; padding:6px 10px; margin-top:4px; font-size:11px; }
|
||||
</style>
|
||||
|
||||
<div class="split">
|
||||
|
||||
<!-- Option A1: White/Light Gray (Sherlock style) -->
|
||||
<div>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">A1 · 纯白 + 灰色背景(Sherlock 风格)</div>
|
||||
<div class="mockup-body" style="background:#f5f5f5; padding:12px;">
|
||||
|
||||
<!-- Title slide -->
|
||||
<div class="slide-preview" style="background:#fff;">
|
||||
<div style="position:absolute;top:0;left:0;right:0;height:4px;background:linear-gradient(90deg,#e20074,#be0060);"></div>
|
||||
<div style="padding:18px 22px 14px;">
|
||||
<div style="font-size:7px;color:#e20074;letter-spacing:2px;font-weight:700;text-transform:uppercase;margin-bottom:8px;">INTERNAL · AI 合规项目组 · 2026.05</div>
|
||||
<div style="font-size:17px;font-weight:900;color:#1a1a2e;line-height:1.2;margin-bottom:6px;">AI + 合规智能中枢<br><span style="color:#e20074;">阶段性进展汇报</span></div>
|
||||
<div style="font-size:8px;color:#666;margin-bottom:12px;">基于 Agent 协同的多模块法规合规智能平台</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<div style="background:#fff0f7;border:1px solid rgba(226,0,116,0.3);border-radius:4px;padding:3px 10px;font-size:8px;color:#e20074;font-weight:700;">5 功能模块</div>
|
||||
<div style="background:#f0fdf8;border:1px solid rgba(0,137,106,0.3);border-radius:4px;padding:3px 10px;font-size:8px;color:#00896a;font-weight:700;">17+ API</div>
|
||||
<div style="background:#fff8f0;border:1px solid rgba(204,98,0,0.3);border-radius:4px;padding:3px 10px;font-size:8px;color:#cc6200;font-weight:700;">6+ 法规源</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position:absolute;bottom:0;left:0;right:0;height:2px;background:#f0f0f0;"></div>
|
||||
<div style="position:absolute;bottom:6px;right:14px;font-size:7px;color:#bbb;">T-Systems · Internal · Confidential</div>
|
||||
</div>
|
||||
|
||||
<!-- Content slide -->
|
||||
<div class="slide-preview" style="background:#fafafa;">
|
||||
<div style="position:absolute;top:0;left:0;right:0;height:3px;background:#e20074;"></div>
|
||||
<div style="padding:14px 18px;">
|
||||
<div style="font-size:7px;color:#e20074;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:3px;">阶段成果</div>
|
||||
<div style="font-size:11px;font-weight:800;color:#1a1a2e;margin-bottom:8px;">已完成的工作</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:5px;">
|
||||
<div style="background:#fff;border:1px solid #e8e8f0;border-left:3px solid #e20074;border-radius:3px;padding:5px 8px;">
|
||||
<div style="font-size:8px;font-weight:700;color:#1a1a2e;margin-bottom:1px;">📡 法规感知</div>
|
||||
<div style="font-size:7px;color:#666;">六大法规源实时事件流</div>
|
||||
</div>
|
||||
<div style="background:#fff;border:1px solid #e8e8f0;border-left:3px solid #00896a;border-radius:3px;padding:5px 8px;">
|
||||
<div style="font-size:8px;font-weight:700;color:#1a1a2e;margin-bottom:1px;">💬 Agent 对话</div>
|
||||
<div style="font-size:7px;color:#666;">核心链路全通 ✓</div>
|
||||
</div>
|
||||
<div style="background:#fff;border:1px solid #e8e8f0;border-left:3px solid #2a68c8;border-radius:3px;padding:5px 8px;">
|
||||
<div style="font-size:8px;font-weight:700;color:#1a1a2e;margin-bottom:1px;">📚 知识库</div>
|
||||
<div style="font-size:7px;color:#666;">5步 Pipeline 可视化</div>
|
||||
</div>
|
||||
<div style="background:#fff;border:1px solid #e8e8f0;border-left:3px solid #cc6200;border-radius:3px;padding:5px 8px;">
|
||||
<div style="font-size:8px;font-weight:700;color:#1a1a2e;margin-bottom:1px;">🔍 合规分析</div>
|
||||
<div style="font-size:7px;color:#666;">风险评分仪表盘</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:13px;color:var(--text-secondary);margin-top:8px;">✦ 纯白底 + 极浅灰区分层次。顶部洋红横线代替渐变块。内容卡片左侧彩色边线区分模块。<br>与 Sherlock PPTX 的白底风格一致。</p>
|
||||
</div>
|
||||
|
||||
<!-- Option A2: Light Beige/Blue (Bosch light style) -->
|
||||
<div>
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">A2 · 浅蓝灰 + 白卡片(Bosch 浅色风格)</div>
|
||||
<div class="mockup-body" style="background:#e8edf5; padding:12px;">
|
||||
|
||||
<!-- Title slide -->
|
||||
<div class="slide-preview" style="background:#f0f4fb;">
|
||||
<div style="position:absolute;left:0;top:0;bottom:0;width:5px;background:linear-gradient(to bottom,#e20074,#be0060);"></div>
|
||||
<div style="padding:18px 22px 14px 28px;">
|
||||
<div style="font-size:7px;color:#e20074;letter-spacing:2px;font-weight:700;text-transform:uppercase;margin-bottom:8px;">■ INTERNAL · AI 合规项目组 · 2026.05</div>
|
||||
<div style="font-size:17px;font-weight:900;color:#1a1a2e;line-height:1.2;margin-bottom:5px;">AI + 合规智能中枢</div>
|
||||
<div style="font-size:13px;color:#e20074;font-weight:700;margin-bottom:10px;">阶段性进展汇报</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<div style="background:rgba(226,0,116,0.1);border:1px solid rgba(226,0,116,0.25);border-radius:20px;padding:2px 10px;font-size:8px;color:#e20074;font-weight:700;">5 功能模块</div>
|
||||
<div style="background:rgba(0,137,106,0.1);border:1px solid rgba(0,137,106,0.25);border-radius:20px;padding:2px 10px;font-size:8px;color:#00896a;font-weight:700;">17+ API</div>
|
||||
<div style="background:rgba(42,104,200,0.1);border:1px solid rgba(42,104,200,0.25);border-radius:20px;padding:2px 10px;font-size:8px;color:#2a68c8;font-weight:700;">6+ 法规源</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position:absolute;bottom:6px;right:14px;font-size:7px;color:#aab;font-style:italic;">T-Systems · Internal · Confidential</div>
|
||||
</div>
|
||||
|
||||
<!-- Content slide -->
|
||||
<div class="slide-preview" style="background:#edf1f8;">
|
||||
<div style="position:absolute;left:0;top:0;bottom:0;width:4px;background:#e20074;"></div>
|
||||
<div style="padding:12px 16px 12px 20px;">
|
||||
<div style="font-size:7px;color:#e20074;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:2px;">阶段成果</div>
|
||||
<div style="font-size:11px;font-weight:800;color:#1a1a2e;margin-bottom:8px;">已完成的工作</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:5px;">
|
||||
<div style="background:#fff;border-radius:4px;padding:6px 8px;box-shadow:0 1px 4px rgba(0,0,0,0.06);">
|
||||
<div style="font-size:8px;font-weight:700;color:#e20074;margin-bottom:2px;">📡 法规感知</div>
|
||||
<div style="font-size:7px;color:#555;">六大法规源实时事件流</div>
|
||||
</div>
|
||||
<div style="background:#fff;border-radius:4px;padding:6px 8px;box-shadow:0 1px 4px rgba(0,0,0,0.06);">
|
||||
<div style="font-size:8px;font-weight:700;color:#00896a;margin-bottom:2px;">💬 Agent 对话</div>
|
||||
<div style="font-size:7px;color:#555;">核心链路全通 ✓</div>
|
||||
</div>
|
||||
<div style="background:#fff;border-radius:4px;padding:6px 8px;box-shadow:0 1px 4px rgba(0,0,0,0.06);">
|
||||
<div style="font-size:8px;font-weight:700;color:#2a68c8;margin-bottom:2px;">📚 知识库</div>
|
||||
<div style="font-size:7px;color:#555;">5步 Pipeline 可视化</div>
|
||||
</div>
|
||||
<div style="background:#fff;border-radius:4px;padding:6px 8px;box-shadow:0 1px 4px rgba(0,0,0,0.06);">
|
||||
<div style="font-size:8px;font-weight:700;color:#cc6200;margin-bottom:2px;">🔍 合规分析</div>
|
||||
<div style="font-size:7px;color:#555;">风险评分仪表盘</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:13px;color:var(--text-secondary);margin-top:8px;">✦ 浅蓝灰底色 + 白色卡片浮起。左侧竖向洋红线作为品牌标识。圆角胶囊标签替代方形标签。<br>柔和、现代感更强,参考 Bosch 浅色内页风格。</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p class="subtitle" style="margin-top:16px;">两种方案均使用 Magenta #E20074 主色 + 浅色全背景。请选择你更喜欢的处理方式,或告诉我想要调整的细节。</p>
|
||||
198
.superpowers/brainstorm/1652-1779893150/content/color-style.html
Normal file
198
.superpowers/brainstorm/1652-1779893150/content/color-style.html
Normal file
@@ -0,0 +1,198 @@
|
||||
<h2>PPT 整体视觉风格</h2>
|
||||
<p class="subtitle">三种风格方案 — 基于 Sherlock PPTX 和 Bosch PDF 的参考,结合项目品牌色</p>
|
||||
|
||||
<style>
|
||||
.slide-preview {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
aspect-ratio: 16/9;
|
||||
position: relative;
|
||||
font-family: 'Calibri', 'Segoe UI', sans-serif;
|
||||
}
|
||||
.slide-title-bar {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 20px 28px;
|
||||
}
|
||||
.slide-content-bar {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.tag-row {
|
||||
display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap;
|
||||
}
|
||||
.tag {
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.mini-card {
|
||||
border-radius: 4px;
|
||||
padding: 6px 10px;
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="cards">
|
||||
|
||||
<!-- Option A: T-Systems Magenta Dark -->
|
||||
<div class="card" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="background:#0f0f1a; padding:16px;">
|
||||
|
||||
<!-- Title slide preview -->
|
||||
<div class="slide-preview" style="background:linear-gradient(135deg,#1a0030 0%,#0f0f1a 60%); border:1px solid #e2007430; margin-bottom:8px;">
|
||||
<div class="slide-title-bar">
|
||||
<div style="width:3px;height:36px;background:#e20074;display:inline-block;margin-right:12px;border-radius:2px;float:left;margin-top:4px;"></div>
|
||||
<div style="overflow:hidden;">
|
||||
<div style="font-size:9px;color:#e20074;letter-spacing:2px;font-weight:700;text-transform:uppercase;margin-bottom:4px;">INTERNAL · AI 合规项目组 · 2026.05</div>
|
||||
<div style="font-size:16px;font-weight:900;color:#fff;line-height:1.2;">AI + 合规智能中枢<br><span style="color:#e20074;">阶段性进展汇报</span></div>
|
||||
<div style="font-size:8px;color:#888;margin-top:6px;">基于 Agent 协同的多模块法规合规智能平台</div>
|
||||
</div>
|
||||
<div style="position:absolute;right:14px;bottom:10px;display:flex;gap:6px;">
|
||||
<div style="background:rgba(226,0,116,0.15);border:1px solid rgba(226,0,116,0.4);border-radius:4px;padding:3px 8px;font-size:9px;color:#e20074;font-weight:700;">5 模块</div>
|
||||
<div style="background:rgba(0,137,106,0.15);border:1px solid rgba(0,137,106,0.4);border-radius:4px;padding:3px 8px;font-size:9px;color:#00896a;font-weight:700;">17+ API</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content slide preview -->
|
||||
<div class="slide-preview" style="background:#fff;border:1px solid #eee;">
|
||||
<div class="slide-content-bar">
|
||||
<div style="width:100%;height:3px;background:linear-gradient(90deg,#e20074,#be0060);border-radius:2px;margin-bottom:8px;"></div>
|
||||
<div style="font-size:8px;color:#e20074;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:3px;">阶段成果</div>
|
||||
<div style="font-size:12px;font-weight:700;color:#1a1a2e;margin-bottom:8px;">已完成的工作</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;">
|
||||
<div class="mini-card" style="background:rgba(226,0,116,0.07);border-left:3px solid #e20074;">
|
||||
<div style="font-size:9px;font-weight:700;color:#1a1a2e;">📡 法规感知</div>
|
||||
<div style="font-size:8px;color:#666;margin-top:2px;">六大法规源实时事件流</div>
|
||||
</div>
|
||||
<div class="mini-card" style="background:rgba(0,137,106,0.07);border-left:3px solid #00896a;">
|
||||
<div style="font-size:9px;font-weight:700;color:#1a1a2e;">💬 Agent 对话</div>
|
||||
<div style="font-size:8px;color:#666;margin-top:2px;">核心链路全通</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>A · T-Systems 风格</h3>
|
||||
<p>深色标题页 + 浅色内容页的"三明治"结构,Magenta 主色调,参考 Sherlock Deep Dive 原版风格。专业感强,与现有品牌高度一致。</p>
|
||||
<div class="tag-row">
|
||||
<span class="tag" style="background:#e200741a;color:#e20074;">Magenta #E20074</span>
|
||||
<span class="tag" style="background:#1a1a2e1a;color:#1a1a2e;">Navy #1A1A2E</span>
|
||||
<span class="tag" style="background:#00896a1a;color:#00896a;">Teal #00896A</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Option B: Bosch Dark Enterprise -->
|
||||
<div class="card" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="background:#1a1a1a; padding:16px;">
|
||||
|
||||
<div class="slide-preview" style="background:linear-gradient(145deg,#1a1a1a 0%,#2d0a0a 100%); border:1px solid #cc000030; margin-bottom:8px;">
|
||||
<div class="slide-title-bar">
|
||||
<div style="font-size:8px;color:#cc0000;letter-spacing:2px;font-weight:700;text-transform:uppercase;margin-bottom:6px;">■ AI COMPLIANCE INTELLIGENCE HUB</div>
|
||||
<div style="font-size:16px;font-weight:900;color:#fff;line-height:1.2;margin-bottom:6px;">团队阶段性汇报<br><span style="color:#ff6b35;">Q2 · 2026</span></div>
|
||||
<div style="display:flex;gap:8px;margin-top:8px;">
|
||||
<div style="border:1px solid rgba(204,0,0,0.5);border-radius:2px;padding:3px 8px;font-size:8px;color:#ff4444;font-weight:600;">5 模块上线</div>
|
||||
<div style="border:1px solid rgba(255,107,53,0.5);border-radius:2px;padding:3px 8px;font-size:8px;color:#ff6b35;font-weight:600;">17+ API</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slide-preview" style="background:#f4f4f4; border:1px solid #ddd;">
|
||||
<div class="slide-content-bar">
|
||||
<div style="background:#cc0000;height:4px;margin:-16px -20px 10px;"></div>
|
||||
<div style="font-size:8px;color:#cc0000;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:3px;">PROGRESS</div>
|
||||
<div style="font-size:12px;font-weight:700;color:#1a1a1a;margin-bottom:8px;">核心链路打通情况</div>
|
||||
<div style="display:flex;flex-direction:column;gap:5px;">
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<div style="width:8px;height:8px;border-radius:50%;background:#00a651;flex-shrink:0;"></div>
|
||||
<div style="font-size:9px;color:#333;">Agent 对话全链路 · 已完成</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<div style="width:8px;height:8px;border-radius:50%;background:#ff8c00;flex-shrink:0;"></div>
|
||||
<div style="font-size:9px;color:#333;">法规感知模块 · 进行中</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<div style="width:8px;height:8px;border-radius:50%;background:#cc0000;flex-shrink:0;"></div>
|
||||
<div style="font-size:9px;color:#333;">合规分析 · 进行中</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>B · Bosch 企业风格</h3>
|
||||
<p>全深色背景,红色主调,强调数据与进展状态。参考 Bosch Workshop PDF 的工程师汇报风格,适合技术向观众,视觉冲击力强。</p>
|
||||
<div class="tag-row">
|
||||
<span class="tag" style="background:#cc00001a;color:#cc0000;">Bosch Red #CC0000</span>
|
||||
<span class="tag" style="background:#1a1a1a1a;color:#333;">Charcoal #1A1A1A</span>
|
||||
<span class="tag" style="background:#ff6b351a;color:#ff6b35;">Orange #FF6B35</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Option C: Hybrid Elegant -->
|
||||
<div class="card" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="background:#0d1b3e; padding:16px;">
|
||||
|
||||
<div class="slide-preview" style="background:linear-gradient(160deg,#0d1b3e 0%,#1a2b5e 100%); border:1px solid #4a7fc130; margin-bottom:8px;">
|
||||
<div class="slide-title-bar">
|
||||
<div style="font-size:8px;color:#7eb8f7;letter-spacing:2px;text-transform:uppercase;margin-bottom:6px;">AI COMPLIANCE · INTERNAL REPORT · 2026.05</div>
|
||||
<div style="font-size:16px;font-weight:900;color:#fff;line-height:1.2;margin-bottom:4px;">AI + 合规智能中枢</div>
|
||||
<div style="font-size:11px;color:#e20074;font-weight:700;margin-bottom:10px;">阶段性进展汇报</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<div style="background:rgba(226,0,116,0.2);border:1px solid rgba(226,0,116,0.5);border-radius:20px;padding:2px 10px;font-size:8px;color:#e20074;font-weight:700;">5 功能模块</div>
|
||||
<div style="background:rgba(0,212,170,0.2);border:1px solid rgba(0,212,170,0.5);border-radius:20px;padding:2px 10px;font-size:8px;color:#00d4aa;font-weight:700;">17+ API 接口</div>
|
||||
<div style="background:rgba(255,180,50,0.2);border:1px solid rgba(255,180,50,0.5);border-radius:20px;padding:2px 10px;font-size:8px;color:#ffb432;font-weight:700;">6+ 法规源</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slide-preview" style="background:#fff; border:1px solid #dde3f0;">
|
||||
<div class="slide-content-bar">
|
||||
<div style="font-size:8px;color:#2a5aad;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:3px;">■ 阶段成果</div>
|
||||
<div style="font-size:12px;font-weight:700;color:#0d1b3e;margin-bottom:8px;">已完成的工作</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:5px;">
|
||||
<div style="background:#f0f5ff;border:1px solid #c5d5f5;border-radius:4px;padding:5px 8px;">
|
||||
<div style="font-size:8px;color:#e20074;font-weight:700;margin-bottom:2px;">● 核心链路</div>
|
||||
<div style="font-size:9px;color:#0d1b3e;font-weight:600;">Agent 对话全通</div>
|
||||
</div>
|
||||
<div style="background:#f0fff8;border:1px solid #b8f0dc;border-radius:4px;padding:5px 8px;">
|
||||
<div style="font-size:8px;color:#00896a;font-weight:700;margin-bottom:2px;">✓ 已完成</div>
|
||||
<div style="font-size:9px;color:#0d1b3e;font-weight:600;">系统监控</div>
|
||||
</div>
|
||||
<div style="background:#fff8f0;border:1px solid #f5d5b5;border-radius:4px;padding:5px 8px;">
|
||||
<div style="font-size:8px;color:#cc6200;font-weight:700;margin-bottom:2px;">⟳ 进行中</div>
|
||||
<div style="font-size:9px;color:#0d1b3e;font-weight:600;">法规感知</div>
|
||||
</div>
|
||||
<div style="background:#fff0f8;border:1px solid #f5c0dc;border-radius:4px;padding:5px 8px;">
|
||||
<div style="font-size:8px;color:#e20074;font-weight:700;margin-bottom:2px;">⟳ 进行中</div>
|
||||
<div style="font-size:9px;color:#0d1b3e;font-weight:600;">合规分析</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>C · 海军蓝 + 混合强调色</h3>
|
||||
<p>深蓝封面页 + 白色内容页,融合 Sherlock 的专业感与 boss-report 的多彩状态系统。用色丰富但有序,适合展示多模块进展。</p>
|
||||
<div class="tag-row">
|
||||
<span class="tag" style="background:#0d1b3e1a;color:#0d1b3e;">Navy #0D1B3E</span>
|
||||
<span class="tag" style="background:#e200741a;color:#e20074;">Magenta</span>
|
||||
<span class="tag" style="background:#00d4aa1a;color:#009a7a;">Teal</span>
|
||||
<span class="tag" style="background:#ffb4321a;color:#cc8800;">Gold</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,100 @@
|
||||
<h2>PPT 幻灯片结构方案</h2>
|
||||
<p class="subtitle">基于 boss-report.html 的内容,三种详细程度的结构 — 选择最适合你汇报场景的版本</p>
|
||||
|
||||
<style>
|
||||
.deck { display:flex; flex-direction:column; gap:6px; }
|
||||
.slide-row {
|
||||
display: flex; align-items: stretch; gap: 8px;
|
||||
background: #fff; border: 1px solid #e8e8f0; border-radius: 6px; overflow: hidden;
|
||||
}
|
||||
.slide-num {
|
||||
background: #e20074; color: #fff; font-weight: 800;
|
||||
font-size: 11px; padding: 0 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
min-width: 32px; writing-mode: horizontal-tb;
|
||||
}
|
||||
.slide-info { padding: 8px 12px; flex: 1; }
|
||||
.slide-title { font-size: 13px; font-weight: 700; color: #1a1a2e; margin-bottom: 2px; }
|
||||
.slide-sub { font-size: 11px; color: #666; }
|
||||
.slide-type {
|
||||
font-size: 9px; padding: 2px 7px; border-radius: 10px; font-weight: 700;
|
||||
align-self: center; white-space: nowrap; margin-right: 10px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.t-cover { background:#fff0f7; color:#e20074; border:1px solid rgba(226,0,116,0.2); }
|
||||
.t-section { background:#f0f4ff; color:#2a68c8; border:1px solid rgba(42,104,200,0.2); }
|
||||
.t-content { background:#f5f5f5; color:#555; border:1px solid #ddd; }
|
||||
.t-kpi { background:#f0fdf8; color:#00896a; border:1px solid rgba(0,137,106,0.2); }
|
||||
.t-arch { background:#fff8f0; color:#cc6200; border:1px solid rgba(204,98,0,0.2); }
|
||||
.t-close { background:#1a1a2e; color:#fff; border:1px solid #1a1a2e; }
|
||||
</style>
|
||||
|
||||
<div class="options">
|
||||
|
||||
<!-- Option A: Compact 8 slides -->
|
||||
<div class="option" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="letter">A</div>
|
||||
<div class="content">
|
||||
<h3>精简版 · 8 页</h3>
|
||||
<p>适合 10–15 分钟快速汇报,老板/领导层受众,重点突出进展和价值</p>
|
||||
<div class="deck" style="margin-top:10px;">
|
||||
<div class="slide-row"><div class="slide-num">01</div><div class="slide-info"><div class="slide-title">封面 — AI+合规智能中枢 阶段性汇报</div><div class="slide-sub">项目名 · 日期 · 核心数字(5模块 / 17+ API / 6+法规源)</div></div><span class="slide-type t-cover">封面</span></div>
|
||||
<div class="slide-row"><div class="slide-num">02</div><div class="slide-info"><div class="slide-title">项目背景 — 为什么做这个系统?</div><div class="slide-sub">3 大痛点:法规碎片化 · 响应周期长 · 人工成本高</div></div><span class="slide-type t-content">内容</span></div>
|
||||
<div class="slide-row"><div class="slide-num">03</div><div class="slide-info"><div class="slide-title">阶段成果总览 — 已完成的工作</div><div class="slide-sub">5 个模块状态(进行中/已完成),核心链路打通</div></div><span class="slide-type t-kpi">进展</span></div>
|
||||
<div class="slide-row"><div class="slide-num">04</div><div class="slide-info"><div class="slide-title">系统架构 — 5 层技术栈</div><div class="slide-sub">前端→API→AI引擎→基础设施,分层示意图</div></div><span class="slide-type t-arch">架构</span></div>
|
||||
<div class="slide-row"><div class="slide-num">05</div><div class="slide-info"><div class="slide-title">业务价值 — 有与没有的差距</div><div class="slide-sub">70% 重复工作减少 / 分钟级响应 / 6+ 法规源统一</div></div><span class="slide-type t-kpi">价值</span></div>
|
||||
<div class="slide-row"><div class="slide-num">06</div><div class="slide-info"><div class="slide-title">四阶段路线图</div><div class="slide-sub">当前处于阶段二 Demo 打磨,阶段三生产部署计划</div></div><span class="slide-type t-content">路线图</span></div>
|
||||
<div class="slide-row"><div class="slide-num">07</div><div class="slide-info"><div class="slide-title">近期行动项 — 需要决策/资源</div><div class="slide-sub">Demo收尾 · 业务对齐 · 技术债 · 资源申请</div></div><span class="slide-type t-content">行动</span></div>
|
||||
<div class="slide-row"><div class="slide-num">08</div><div class="slide-info"><div class="slide-title">封底 — Q&A</div><div class="slide-sub">联系方式 · 内部机密声明</div></div><span class="slide-type t-close">封底</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Option B: Standard 11 slides -->
|
||||
<div class="option" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="letter">B</div>
|
||||
<div class="content">
|
||||
<h3>标准版 · 11 页</h3>
|
||||
<p>适合 20–30 分钟完整汇报,技术+管理混合受众,各模块有独立页面</p>
|
||||
<div class="deck" style="margin-top:10px;">
|
||||
<div class="slide-row"><div class="slide-num">01</div><div class="slide-info"><div class="slide-title">封面</div><div class="slide-sub">项目名 · 核心 KPI 数字 · 日期</div></div><span class="slide-type t-cover">封面</span></div>
|
||||
<div class="slide-row"><div class="slide-num">02</div><div class="slide-info"><div class="slide-title">目录</div><div class="slide-sub">背景 · 成果 · 架构 · 功能演示 · 价值 · 路线图 · 行动项</div></div><span class="slide-type t-section">目录</span></div>
|
||||
<div class="slide-row"><div class="slide-num">03</div><div class="slide-info"><div class="slide-title">项目背景 — 3 大核心痛点</div><div class="slide-sub">法规碎片化 / 响应周期长 / 人工成本高(3列卡片)</div></div><span class="slide-type t-content">内容</span></div>
|
||||
<div class="slide-row"><div class="slide-num">04</div><div class="slide-info"><div class="slide-title">阶段成果 — 5 个功能模块进展</div><div class="slide-sub">每个模块状态 + 关键功能点(进行中/已完成)</div></div><span class="slide-type t-kpi">进展</span></div>
|
||||
<div class="slide-row"><div class="slide-num">05</div><div class="slide-info"><div class="slide-title">核心亮点 — Agent 对话链路打通</div><div class="slide-sub">Milvus 检索 → LLM 推理 → SSE 流式输出,全链路贯通</div></div><span class="slide-type t-kpi">亮点</span></div>
|
||||
<div class="slide-row"><div class="slide-num">06</div><div class="slide-info"><div class="slide-title">系统架构 — 清洁架构 5 层结构</div><div class="slide-sub">分层示意图:用户→前端→API→AI引擎→基础设施</div></div><span class="slide-type t-arch">架构</span></div>
|
||||
<div class="slide-row"><div class="slide-num">07</div><div class="slide-info"><div class="slide-title">技术栈全景</div><div class="slide-sub">React 19 / FastAPI / Milvus / Qwen · DeepSeek / Aliyun DocMind</div></div><span class="slide-type t-arch">技术</span></div>
|
||||
<div class="slide-row"><div class="slide-num">08</div><div class="slide-info"><div class="slide-title">业务价值 — 量化对比</div><div class="slide-sub">传统方式 vs AI中枢:4 大指标数字(70%+/分钟级/6+/5)</div></div><span class="slide-type t-kpi">价值</span></div>
|
||||
<div class="slide-row"><div class="slide-num">09</div><div class="slide-info"><div class="slide-title">四阶段路线图</div><div class="slide-sub">时间线:POC已完成 → Demo进行中 → 生产部署 → 规模推广</div></div><span class="slide-type t-content">路线图</span></div>
|
||||
<div class="slide-row"><div class="slide-num">10</div><div class="slide-info"><div class="slide-title">近期行动项 — 4 大类任务</div><div class="slide-sub">Demo收尾 / 业务对齐 / 技术债清理 / 资源&决策(2×2 卡片)</div></div><span class="slide-type t-content">行动</span></div>
|
||||
<div class="slide-row"><div class="slide-num">11</div><div class="slide-info"><div class="slide-title">封底 — Q&A</div><div class="slide-sub"></div></div><span class="slide-type t-close">封底</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Option C: Full 14 slides -->
|
||||
<div class="option" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="letter">C</div>
|
||||
<div class="content">
|
||||
<h3>完整版 · 14 页</h3>
|
||||
<p>适合正式汇报 + 存档,含各模块独立功能页,技术细节充分展示</p>
|
||||
<div class="deck" style="margin-top:10px;">
|
||||
<div class="slide-row"><div class="slide-num">01</div><div class="slide-info"><div class="slide-title">封面</div></div><span class="slide-type t-cover">封面</span></div>
|
||||
<div class="slide-row"><div class="slide-num">02</div><div class="slide-info"><div class="slide-title">目录</div></div><span class="slide-type t-section">目录</span></div>
|
||||
<div class="slide-row"><div class="slide-num">03</div><div class="slide-info"><div class="slide-title">项目背景 — 3 大痛点</div></div><span class="slide-type t-content">内容</span></div>
|
||||
<div class="slide-row"><div class="slide-num">04</div><div class="slide-info"><div class="slide-title">【分节页】— 阶段成果</div></div><span class="slide-type t-section">分节</span></div>
|
||||
<div class="slide-row"><div class="slide-num">05</div><div class="slide-info"><div class="slide-title">5 模块进展总览</div></div><span class="slide-type t-kpi">进展</span></div>
|
||||
<div class="slide-row"><div class="slide-num">06</div><div class="slide-info"><div class="slide-title">核心亮点 — Agent 链路</div></div><span class="slide-type t-kpi">亮点</span></div>
|
||||
<div class="slide-row"><div class="slide-num">07</div><div class="slide-info"><div class="slide-title">文档处理 Pipeline(5步)</div></div><span class="slide-type t-arch">流程</span></div>
|
||||
<div class="slide-row"><div class="slide-num">08</div><div class="slide-info"><div class="slide-title">【分节页】— 技术架构</div></div><span class="slide-type t-section">分节</span></div>
|
||||
<div class="slide-row"><div class="slide-num">09</div><div class="slide-info"><div class="slide-title">系统分层架构图</div></div><span class="slide-type t-arch">架构</span></div>
|
||||
<div class="slide-row"><div class="slide-num">10</div><div class="slide-info"><div class="slide-title">技术栈全景</div></div><span class="slide-type t-arch">技术</span></div>
|
||||
<div class="slide-row"><div class="slide-num">11</div><div class="slide-info"><div class="slide-title">业务价值量化对比</div></div><span class="slide-type t-kpi">价值</span></div>
|
||||
<div class="slide-row"><div class="slide-num">12</div><div class="slide-info"><div class="slide-title">四阶段路线图</div></div><span class="slide-type t-content">路线图</span></div>
|
||||
<div class="slide-row"><div class="slide-num">13</div><div class="slide-info"><div class="slide-title">近期行动项(4 类)</div></div><span class="slide-type t-content">行动</span></div>
|
||||
<div class="slide-row"><div class="slide-num">14</div><div class="slide-info"><div class="slide-title">封底 — Q&A</div></div><span class="slide-type t-close">封底</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
|
||||
<p class="subtitle">Continuing in terminal...</p>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
{"type":"server-started","port":56940,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:56940","screen_dir":"C:\\Projects\\AIProjects\\AIRegulations\\AIRegulation-DocAnalysis-Demo\\.superpowers\\brainstorm\\1652-1779893150\\content","state_dir":"C:\\Projects\\AIProjects\\AIRegulations\\AIRegulation-DocAnalysis-Demo\\.superpowers\\brainstorm\\1652-1779893150\\state"}
|
||||
1
.superpowers/brainstorm/1652-1779893150/state/server.pid
Normal file
1
.superpowers/brainstorm/1652-1779893150/state/server.pid
Normal file
@@ -0,0 +1 @@
|
||||
1652
|
||||
@@ -0,0 +1,88 @@
|
||||
<h2>法规对话模块优化方案</h2>
|
||||
<p class="subtitle">选择你偏好的整体策略,我会据此展开详细设计</p>
|
||||
|
||||
<div class="options">
|
||||
<div class="option" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="letter">A</div>
|
||||
<div class="content">
|
||||
<h3>分层优先(推荐)</h3>
|
||||
<p>按依赖关系分4个阶段逐步落地,每阶段可独立上线。</p>
|
||||
<div style="margin-top:12px;display:grid;grid-template-columns:1fr 1fr;gap:8px;">
|
||||
<div style="background:rgba(255,255,255,0.07);border-radius:6px;padding:10px;">
|
||||
<div class="label" style="color:#7dd3fc">Phase 1 · 第1周</div>
|
||||
<strong style="font-size:13px">接入真实服务</strong>
|
||||
<p style="font-size:12px;margin:4px 0 0;opacity:0.7">消灭 rag.py / compliance.py 中的 Mock 数据,让系统真正可用</p>
|
||||
</div>
|
||||
<div style="background:rgba(255,255,255,0.07);border-radius:6px;padding:10px;">
|
||||
<div class="label" style="color:#86efac">Phase 2 · 第2-3周</div>
|
||||
<strong style="font-size:13px">混合检索 + Reranking</strong>
|
||||
<p style="font-size:12px;margin:4px 0 0;opacity:0.7">Milvus sparse BM25 + dense RRF 融合 + Cross-encoder reranker</p>
|
||||
</div>
|
||||
<div style="background:rgba(255,255,255,0.07);border-radius:6px;padding:10px;">
|
||||
<div class="label" style="color:#fcd34d">Phase 3 · 第4周</div>
|
||||
<strong style="font-size:13px">引用溯源 + 筛选 UI</strong>
|
||||
<p style="font-size:12px;margin:4px 0 0;opacity:0.7">答案内联 [1][2] 跳转原文片段,法规类型/版本筛选栏</p>
|
||||
</div>
|
||||
<div style="background:rgba(255,255,255,0.07);border-radius:6px;padding:10px;">
|
||||
<div class="label" style="color:#f9a8d4">Phase 4 · 第5周</div>
|
||||
<strong style="font-size:13px">会话持久化 + 压缩</strong>
|
||||
<p style="font-size:12px;margin:4px 0 0;opacity:0.7">PostgreSQL 存储会话,长对话上下文压缩,快问后端化</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pros-cons" style="margin-top:12px">
|
||||
<div class="pros"><h4>优势</h4><ul><li>每阶段可独立验证</li><li>Phase 1 即可见效</li><li>风险最低</li></ul></div>
|
||||
<div class="cons"><h4>劣势</h4><ul><li>完整交付需 5 周</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="letter">B</div>
|
||||
<div class="content">
|
||||
<h3>检索优先</h3>
|
||||
<p>先升级检索质量(最有技术价值),再接入服务,最后做 UX。</p>
|
||||
<div style="margin-top:12px;display:flex;flex-direction:column;gap:8px;">
|
||||
<div style="background:rgba(255,255,255,0.07);border-radius:6px;padding:10px;">
|
||||
<div class="label" style="color:#86efac">Step 1</div>
|
||||
<strong style="font-size:13px">Milvus sparse + dense 混合索引</strong>
|
||||
<p style="font-size:12px;margin:4px 0 0;opacity:0.7">先在 Mock 环境验证检索效果,技术风险前移</p>
|
||||
</div>
|
||||
<div style="background:rgba(255,255,255,0.07);border-radius:6px;padding:10px;">
|
||||
<div class="label" style="color:#7dd3fc">Step 2</div>
|
||||
<strong style="font-size:13px">接入真实服务 + 端到端测试</strong>
|
||||
</div>
|
||||
<div style="background:rgba(255,255,255,0.07);border-radius:6px;padding:10px;">
|
||||
<div class="label" style="color:#fcd34d">Step 3</div>
|
||||
<strong style="font-size:13px">引用 + UX + 会话持久化</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pros-cons" style="margin-top:12px">
|
||||
<div class="pros"><h4>优势</h4><ul><li>技术风险前移验证</li></ul></div>
|
||||
<div class="cons"><h4>劣势</h4><ul><li>Mock 上测检索效果失真</li><li>用户最长时间看不到真实效果</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="letter">C</div>
|
||||
<div class="content">
|
||||
<h3>最小可行改进</h3>
|
||||
<p>只做最小必要改动,跳过 BM25/Reranking,快速交付可用版本。</p>
|
||||
<div style="margin-top:12px;display:flex;flex-direction:column;gap:8px;">
|
||||
<div style="background:rgba(255,255,255,0.07);border-radius:6px;padding:10px;">
|
||||
<div class="label" style="color:#7dd3fc">Step 1</div>
|
||||
<strong style="font-size:13px">接入真实服务(消灭 Mock)</strong>
|
||||
</div>
|
||||
<div style="background:rgba(255,255,255,0.07);border-radius:6px;padding:10px;">
|
||||
<div class="label" style="color:#fcd34d">Step 2</div>
|
||||
<strong style="font-size:13px">引用溯源 + 筛选 UI</strong>
|
||||
<p style="font-size:12px;margin:4px 0 0;opacity:0.7">跳过混合检索和会话持久化</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pros-cons" style="margin-top:12px">
|
||||
<div class="pros"><h4>优势</h4><ul><li>2周内完成</li><li>最低风险</li></ul></div>
|
||||
<div class="cons"><h4>劣势</h4><ul><li>检索质量无提升</li><li>会话仍会丢失</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,127 @@
|
||||
<h2>设计概览:法规对话模块优化路线</h2>
|
||||
<p class="subtitle">Section 1 of 4 — 架构演进全图</p>
|
||||
|
||||
<style>
|
||||
.arch-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 16px; }
|
||||
.arch-box { background: rgba(255,255,255,0.05); border-radius: 10px; padding: 16px; }
|
||||
.arch-box h3 { margin: 0 0 12px; font-size: 15px; }
|
||||
.pipe { display: flex; flex-direction: column; gap: 6px; }
|
||||
.node { border-radius: 6px; padding: 8px 12px; font-size: 13px; display: flex; align-items: center; gap: 8px; }
|
||||
.node-ok { background: rgba(134,239,172,0.15); border: 1px solid rgba(134,239,172,0.4); }
|
||||
.node-mock { background: rgba(248,113,113,0.15); border: 1px solid rgba(248,113,113,0.4); }
|
||||
.node-new { background: rgba(125,211,252,0.15); border: 1px solid rgba(125,211,252,0.5); }
|
||||
.node-upgrade { background: rgba(253,224,71,0.15); border: 1px solid rgba(253,224,71,0.4); }
|
||||
.arrow { text-align: center; font-size: 18px; opacity: 0.5; line-height: 1; }
|
||||
.badge { font-size: 10px; padding: 2px 6px; border-radius: 10px; font-weight: 600; margin-left: auto; white-space: nowrap; }
|
||||
.badge-mock { background: rgba(248,113,113,0.3); color: #fca5a5; }
|
||||
.badge-ok { background: rgba(134,239,172,0.3); color: #86efac; }
|
||||
.badge-p1 { background: rgba(125,211,252,0.3); color: #7dd3fc; }
|
||||
.badge-p2 { background: rgba(134,239,172,0.3); color: #86efac; }
|
||||
.badge-p3 { background: rgba(253,224,71,0.3); color: #fde047; }
|
||||
.badge-p4 { background: rgba(249,168,212,0.3); color: #f9a8d4; }
|
||||
.legend { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 12px; font-size: 12px; }
|
||||
.leg { display: flex; align-items: center; gap: 6px; }
|
||||
.leg-dot { width: 10px; height: 10px; border-radius: 3px; }
|
||||
</style>
|
||||
|
||||
<div class="arch-grid">
|
||||
<!-- LEFT: Current State -->
|
||||
<div class="arch-box">
|
||||
<h3>📍 当前状态</h3>
|
||||
<div class="pipe">
|
||||
<div class="node node-ok">用户提问 (RagChatPage / ChatPanel)</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-ok">
|
||||
<span>POST /agent/chat/stream</span>
|
||||
<span class="badge badge-ok">真实</span>
|
||||
</div>
|
||||
<div class="arrow">↓ ↗</div>
|
||||
|
||||
<div class="node node-mock">
|
||||
<span>POST /rag/chat & /compliance/chat/{id}</span>
|
||||
<span class="badge badge-mock">Mock 数据</span>
|
||||
</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-ok">
|
||||
<span>Dense 向量检索(COSINE)</span>
|
||||
<span class="badge badge-ok">可用</span>
|
||||
</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-ok">
|
||||
<span>LLM 生成(输出含 [1][2] 引用)</span>
|
||||
<span class="badge badge-ok">可用</span>
|
||||
</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-mock">
|
||||
<span>前端显示来源面板,[1][2] 未解析</span>
|
||||
<span class="badge badge-mock">未联动</span>
|
||||
</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-mock">
|
||||
<span>会话存内存(30min过期,max 100)</span>
|
||||
<span class="badge badge-mock">易丢失</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: Target State -->
|
||||
<div class="arch-box">
|
||||
<h3>🎯 目标状态(4个阶段后)</h3>
|
||||
<div class="pipe">
|
||||
<div class="node node-ok">用户提问 + 法规类型/版本筛选器 (P3)</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-new">
|
||||
<span>/compliance/chat → 真实 AgentService + Segment 上下文</span>
|
||||
<span class="badge badge-p1">Phase 1</span>
|
||||
</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-upgrade">
|
||||
<span>Hybrid 检索:Dense + Sparse BM25(Milvus)→ RRF 融合</span>
|
||||
<span class="badge badge-p2">Phase 2</span>
|
||||
</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-upgrade">
|
||||
<span>Cross-Encoder Reranker(Top-K 精排)</span>
|
||||
<span class="badge badge-p2">Phase 2</span>
|
||||
</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-ok">LLM 生成(含 [1][2] 引用编号)</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-new">
|
||||
<span>前端内联引用解析:[1] → 高亮原文跳转</span>
|
||||
<span class="badge badge-p3">Phase 3</span>
|
||||
</div>
|
||||
<div class="arrow">↓</div>
|
||||
|
||||
<div class="node node-new">
|
||||
<span>会话持久化(PostgreSQL)+ 上下文压缩</span>
|
||||
<span class="badge badge-p4">Phase 4</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="legend">
|
||||
<div class="leg"><div class="leg-dot" style="background:#86efac"></div>现有功能正常</div>
|
||||
<div class="leg"><div class="leg-dot" style="background:#f87171"></div>当前有问题</div>
|
||||
<div class="leg"><div class="leg-dot" style="background:#7dd3fc"></div>Phase 1 新增/修复</div>
|
||||
<div class="leg"><div class="leg-dot" style="background:#86efac;opacity:0.6"></div>Phase 2 升级</div>
|
||||
<div class="leg"><div class="leg-dot" style="background:#fde047"></div>Phase 3 新增</div>
|
||||
<div class="leg"><div class="leg-dot" style="background:#f9a8d4"></div>Phase 4 新增</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:20px;padding:14px;background:rgba(255,255,255,0.05);border-radius:8px;font-size:13px;">
|
||||
<strong>关键发现:</strong> RagChatPage 已通过 <code>/agent/chat/stream</code> 使用真实服务。
|
||||
最需要修复的是 <strong>CompliancePage 的 ChatPanel</strong>(合规对话面板仍是 Mock),以及前端快速问题硬编码问题。
|
||||
Phase 2 的 BM25 稀疏向量需要重建 Milvus Collection(或添加新 field)。
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
{"reason":"idle timeout","timestamp":1779289950370}
|
||||
1
.superpowers/brainstorm/1946-1779287418/state/server.pid
Normal file
1
.superpowers/brainstorm/1946-1779287418/state/server.pid
Normal file
@@ -0,0 +1 @@
|
||||
1946
|
||||
@@ -0,0 +1,51 @@
|
||||
<h2>团队阶段性汇报 PPT — 定位选择</h2>
|
||||
<p class="subtitle">这次汇报面向谁?确定受众和基调,才能决定内容侧重</p>
|
||||
|
||||
<div class="options">
|
||||
|
||||
<div class="option" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="letter">A</div>
|
||||
<div class="content">
|
||||
<h3>🎯 项目组内部复盘</h3>
|
||||
<p>受众:本团队成员(开发 + 产品 + 架构)</p>
|
||||
<p>基调:<strong>坦诚、细节、协作</strong></p>
|
||||
<ul style="margin-top:8px;padding-left:16px;font-size:13px;color:#888;line-height:2">
|
||||
<li>每个模块做了什么 / 谁负责</li>
|
||||
<li>哪些卡点 / 技术债</li>
|
||||
<li>下阶段分工与优先级</li>
|
||||
<li>风格:简洁实用,不必精致</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="letter">B</div>
|
||||
<div class="content">
|
||||
<h3>📊 跨团队 / 中层管理汇报</h3>
|
||||
<p>受众:其他部门 Leader、项目经理、IT 管理层</p>
|
||||
<p>基调:<strong>成果导向、清晰、有说服力</strong></p>
|
||||
<ul style="margin-top:8px;padding-left:16px;font-size:13px;color:#888;line-height:2">
|
||||
<li>我们做了什么 / 进度如何</li>
|
||||
<li>系统架构概览(不过深)</li>
|
||||
<li>下阶段计划 + 需要的支持</li>
|
||||
<li>风格:专业、品牌感、图文并茂</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="letter">C</div>
|
||||
<div class="content">
|
||||
<h3>🔧 技术分享 / 工程师视角</h3>
|
||||
<p>受众:技术团队、架构师、其他工程师</p>
|
||||
<p>基调:<strong>技术深度、方案决策、经验分享</strong></p>
|
||||
<ul style="margin-top:8px;padding-left:16px;font-size:13px;color:#888;line-height:2">
|
||||
<li>架构设计思路(清洁架构 / Ports&Adapters)</li>
|
||||
<li>Agent 协同实现方式</li>
|
||||
<li>踩坑与解决方案</li>
|
||||
<li>风格:代码 + 图表,技术感强</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,66 @@
|
||||
<h2>PPT 内容结构 — 选一个框架</h2>
|
||||
<p class="subtitle">boss-report 已有 10 张幻灯片,团队汇报可以更精简。哪种结构更符合你的预期?</p>
|
||||
|
||||
<div class="options">
|
||||
|
||||
<div class="option" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="letter">A</div>
|
||||
<div class="content">
|
||||
<h3>精简版 · 8 张</h3>
|
||||
<p>快速汇报,30 分钟内讲完</p>
|
||||
<ol style="margin-top:10px;padding-left:18px;font-size:13px;color:#888;line-height:2.2">
|
||||
<li>封面(项目名 + 团队 + 日期)</li>
|
||||
<li>背景与目标(为什么做)</li>
|
||||
<li>阶段成果总览(已完成/进行中/未开始)</li>
|
||||
<li>核心模块展示(5 个功能,每个 2-3 句)</li>
|
||||
<li>系统架构(5 层图,一张)</li>
|
||||
<li>业务价值(对比 + 4 个 KPI)</li>
|
||||
<li>四阶段路线图(当前位置高亮)</li>
|
||||
<li>近期行动项 + 结尾</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="letter">B</div>
|
||||
<div class="content">
|
||||
<h3>标准版 · 12 张</h3>
|
||||
<p>完整汇报,适合正式会议,45–60 分钟</p>
|
||||
<ol style="margin-top:10px;padding-left:18px;font-size:13px;color:#888;line-height:2.2">
|
||||
<li>封面</li>
|
||||
<li>背景与痛点(3 张卡片)</li>
|
||||
<li>项目目标 & 范围</li>
|
||||
<li>阶段成果总览</li>
|
||||
<li>模块详情 1/2(感知 / 文档 / 合规)</li>
|
||||
<li>模块详情 2/2(Agent 对话 / 监控)</li>
|
||||
<li>系统架构</li>
|
||||
<li>技术亮点(Agent 协同 / SSE / 双引擎)</li>
|
||||
<li>业务价值 & KPI</li>
|
||||
<li>四阶段路线图</li>
|
||||
<li>近期行动项 & 资源需求</li>
|
||||
<li>总结 & 致谢</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="letter">C</div>
|
||||
<div class="content">
|
||||
<h3>重点突出版 · 10 张(推荐)</h3>
|
||||
<p>与 boss-report 同等体量,但内容侧重<strong>团队视角</strong></p>
|
||||
<ol style="margin-top:10px;padding-left:18px;font-size:13px;color:#888;line-height:2.2">
|
||||
<li>封面(团队 + 汇报人 + 日期)</li>
|
||||
<li>项目背景 & 我们的目标</li>
|
||||
<li>本阶段工作总览(模块 + 状态 + 负责人)</li>
|
||||
<li>核心功能演示 1/2</li>
|
||||
<li>核心功能演示 2/2</li>
|
||||
<li>系统架构 & 技术选型</li>
|
||||
<li>业务价值量化</li>
|
||||
<li>四阶段路线图</li>
|
||||
<li>下阶段重点 & 分工</li>
|
||||
<li>结语 & Q&A</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,66 @@
|
||||
<h2>视觉风格选择</h2>
|
||||
<p class="subtitle">团队汇报的整体视觉基调——哪种感觉最符合你们的场合?</p>
|
||||
|
||||
<div class="cards">
|
||||
|
||||
<div class="card" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="background:linear-gradient(135deg,#0a0f2e 0%,#1a1040 100%);height:160px;display:flex;align-items:center;justify-content:center;gap:16px;flex-direction:column;">
|
||||
<div style="display:flex;gap:8px;align-items:center;">
|
||||
<div style="width:4px;height:40px;background:#e20074;border-radius:2px;"></div>
|
||||
<div>
|
||||
<div style="font-size:15px;font-weight:800;color:#fff;letter-spacing:1px;">AI + 合规智能中枢</div>
|
||||
<div style="font-size:11px;color:#e20074;margin-top:4px;letter-spacing:2px;">TEAM PROGRESS REPORT</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<div style="background:rgba(226,0,116,0.15);border:1px solid rgba(226,0,116,0.4);border-radius:4px;padding:4px 10px;font-size:10px;color:#e20074;">5 模块</div>
|
||||
<div style="background:rgba(0,212,170,0.1);border:1px solid rgba(0,212,170,0.3);border-radius:4px;padding:4px 10px;font-size:10px;color:#00d4aa;">17+ API</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>A · 深色科技风(沿用 boss-report 风格)</h3>
|
||||
<p>深海蓝 + 品牌洋红,科技感强,与现有 boss-report.pptx 视觉一致。适合 IT 氛围浓的场合。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="background:linear-gradient(135deg,#f7f7fa 0%,#f0eef8 100%);height:160px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;border-bottom:1px solid #e0e0ea;">
|
||||
<div style="display:flex;gap:8px;align-items:center;">
|
||||
<div style="width:4px;height:40px;background:#e20074;border-radius:2px;"></div>
|
||||
<div>
|
||||
<div style="font-size:15px;font-weight:800;color:#1a1a2e;letter-spacing:0.5px;">AI + 合规智能中枢</div>
|
||||
<div style="font-size:11px;color:#e20074;margin-top:4px;letter-spacing:2px;">团队阶段性汇报</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;">
|
||||
<div style="background:rgba(226,0,116,0.1);border:1px solid rgba(226,0,116,0.25);border-radius:4px;padding:3px 10px;font-size:10px;color:#be0060;">5 功能模块</div>
|
||||
<div style="background:rgba(0,137,106,0.08);border:1px solid rgba(0,137,106,0.2);border-radius:4px;padding:3px 10px;font-size:10px;color:#00896a;">核心链路已通</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>B · 浅色专业风(与 HTML 报告一致)</h3>
|
||||
<p>白底 + 洋红 + 深色文字,与 boss-report.html 视觉一致。阅读友好,投影仪效果好,商务感强。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" data-choice="c" onclick="toggleSelect(this)">
|
||||
<div class="card-image" style="background:linear-gradient(135deg,#1e3a5f 0%,#2d5986 100%);height:160px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;">
|
||||
<div style="display:flex;gap:8px;align-items:center;">
|
||||
<div style="width:4px;height:40px;background:#4fc3f7;border-radius:2px;"></div>
|
||||
<div>
|
||||
<div style="font-size:15px;font-weight:800;color:#fff;letter-spacing:0.5px;">AI + 合规智能中枢</div>
|
||||
<div style="font-size:11px;color:#4fc3f7;margin-top:4px;letter-spacing:2px;">TEAM REPORT · 2026.05</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;">
|
||||
<div style="background:rgba(79,195,247,0.15);border:1px solid rgba(79,195,247,0.35);border-radius:4px;padding:3px 10px;font-size:10px;color:#4fc3f7;">T-Systems Blue</div>
|
||||
<div style="background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.2);border-radius:4px;padding:3px 10px;font-size:10px;color:#fff;">专业蓝</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h3>C · 企业蓝风格(T-Systems 主色调)</h3>
|
||||
<p>深蓝 + 天蓝点缀,更接近 T-Systems 官方企业蓝调。适合对外正式会议或集团内部跨团队场合。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
|
||||
<p class="subtitle">Continuing in terminal...</p>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
{"reason":"idle timeout","timestamp":1779878105020}
|
||||
1
.superpowers/brainstorm/1959-1779875884/state/server.pid
Normal file
1
.superpowers/brainstorm/1959-1779875884/state/server.pid
Normal file
@@ -0,0 +1 @@
|
||||
1959
|
||||
259
01_Architecture.html
Normal file
259
01_Architecture.html
Normal file
@@ -0,0 +1,259 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI+合规智能中枢 — 分层次技术架构图</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&display=swap');
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
|
||||
background: linear-gradient(160deg, #fef5f9 0%, #f8eaf0 100%);
|
||||
min-height: 100vh;
|
||||
padding: 50px 24px;
|
||||
}
|
||||
|
||||
.container { max-width: 1300px; margin: 0 auto; }
|
||||
|
||||
/* ── 品牌色系 (T-systems PPT) ── */
|
||||
:root {
|
||||
--pink: #E20074;
|
||||
--pink-dark: #B0005A;
|
||||
--pink-light: #E20074;
|
||||
--pink-bg: #FDF2F7;
|
||||
--navy: #000E5E;
|
||||
--navy-mid: #1A2B6B;
|
||||
--blue-light: #D3E7F3;
|
||||
--blue-pale: #EAF4FA;
|
||||
--teal: #32B9AF;
|
||||
--teal-dark: #1A8A82;
|
||||
--teal-bg: #E0F7F5;
|
||||
--orange: #F26B43;
|
||||
--orange-dark:#D4532B;
|
||||
--orange-bg: #FEF0EB;
|
||||
--gray-text: #4B4B4B;
|
||||
--gray-mid: #7B7B7B;
|
||||
--gray-light: #B0B0B0;
|
||||
--gray-bg: #F0F0F0;
|
||||
}
|
||||
|
||||
/* ── 标题 ── */
|
||||
.header { text-align:center; margin-bottom:44px; }
|
||||
.header h1 { font-size:34px; font-weight:900; color:var(--navy); letter-spacing:2px; }
|
||||
.header .sub { font-size:16px; color:var(--gray-text); font-weight:500; margin-top:6px; }
|
||||
.header .tag { display:inline-block; background:var(--pink); color:#fff; padding:4px 18px; border-radius:20px; font-size:12px; margin-top:10px; }
|
||||
|
||||
/* ── 层 ── */
|
||||
.layer {
|
||||
border-radius:14px; border:2px solid; overflow:hidden;
|
||||
transition: transform .2s, box-shadow .2s;
|
||||
}
|
||||
.layer:hover { transform:translateY(-2px); box-shadow:0 6px 24px rgba(226,0,116,.1); }
|
||||
|
||||
.lh {
|
||||
padding:12px 22px; color:#fff; font-size:17px; font-weight:700;
|
||||
display:flex; align-items:center; gap:10px;
|
||||
}
|
||||
.lh .en { font-size:12px; font-weight:400; opacity:.75; }
|
||||
.lh .ico { font-size:20px; }
|
||||
|
||||
.lb { padding:18px 22px; display:grid; gap:10px; }
|
||||
.g5 { grid-template-columns:repeat(5,1fr); }
|
||||
.lb.g5 { display:flex; justify-content:space-between; gap:0; }
|
||||
.lb.g5 > .m { flex:0 0 18%; }
|
||||
|
||||
.m {
|
||||
background:#fff; border-radius:9px; padding:12px 14px; border:1.5px solid;
|
||||
transition: transform .12s, box-shadow .12s; cursor:default;
|
||||
}
|
||||
.m:hover { transform:translateY(-1px); box-shadow:0 3px 10px rgba(226,0,116,.08); }
|
||||
.m .n { font-size:14px; font-weight:700; margin-bottom:3px; }
|
||||
.m .d { font-size:11.5px; color:var(--gray-mid); line-height:1.55; }
|
||||
|
||||
/* ── 箭头 ── */
|
||||
.arr-row { display:flex; justify-content:center; gap:180px; padding:5px 0; }
|
||||
.arr { width:2px; height:18px; background:var(--pink-light); position:relative; }
|
||||
.arr::after { content:''; position:absolute; bottom:-5px; left:-4px;
|
||||
border-left:5px solid transparent; border-right:5px solid transparent; border-top:6px solid var(--pink-light); }
|
||||
|
||||
/* ── 颜色主题 ── */
|
||||
.c1 { border-color:var(--pink); background:var(--pink-bg); }
|
||||
.c1 .lh { background:linear-gradient(135deg,var(--pink-dark),var(--pink)); }
|
||||
.c1 .m { border-color:var(--pink-light); }
|
||||
.c1 .m .n { color:var(--pink-dark); }
|
||||
|
||||
.c2 { border-color:var(--navy); background:var(--blue-pale); }
|
||||
.c2 .lh { background:linear-gradient(135deg,var(--navy),var(--navy-mid)); }
|
||||
.c2 .m { border-color:var(--blue-light); }
|
||||
.c2 .m .n { color:var(--navy); }
|
||||
|
||||
.c3 { border-color:var(--pink); background:#FCEEF4; }
|
||||
.c3 .lh { background:linear-gradient(135deg,#C40068,#E20074); }
|
||||
.c3 .m { border-color:var(--pink-light); }
|
||||
.c3 .m .n { color:#C40068; }
|
||||
|
||||
.c4 { border-color:var(--teal); background:var(--teal-bg); }
|
||||
.c4 .lh { background:linear-gradient(135deg,var(--teal-dark),var(--teal)); }
|
||||
.c4 .m { border-color:#A0E0DB; }
|
||||
.c4 .m .n { color:var(--teal-dark); }
|
||||
|
||||
.c5 { border-color:var(--gray-text); background:var(--gray-bg); }
|
||||
.c5 .lh { background:linear-gradient(135deg,#2C2C2C,var(--gray-text)); }
|
||||
.c5 .m { border-color:#C8C8C8; }
|
||||
.c5 .m .n { color:#2C2C2C; }
|
||||
|
||||
/* ── 图例 ── */
|
||||
.legend {
|
||||
margin-top:36px; padding:18px 24px; background:#fff;
|
||||
border-radius:12px; border:1px solid #E0D0D8;
|
||||
}
|
||||
.legend h3 { font-size:15px; color:var(--navy); margin-bottom:10px; }
|
||||
.lg { display:flex; flex-wrap:wrap; gap:14px; }
|
||||
.li { display:flex; align-items:center; gap:7px; font-size:12.5px; color:var(--gray-text); }
|
||||
.ld { width:11px; height:11px; border-radius:50%; }
|
||||
|
||||
.footer { text-align:center; margin-top:24px; font-size:12px; color:var(--gray-light); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<div class="header">
|
||||
<h1>AI+合规智能中枢 — 分层技术架构</h1>
|
||||
<div class="sub">面向车企与工厂 | 全链路合规智能平台</div>
|
||||
<div class="tag">T-systems AI Regulations Team | 2026.04</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ L1 应用接入层 ═══ -->
|
||||
<div class="layer c1">
|
||||
<div class="lh"><span class="ico">🌐</span> 应用接入层 <span class="en">Application & Access Layer</span></div>
|
||||
<div class="lb g5">
|
||||
<div class="m"><div class="n">Web管理门户</div><div class="d">知识库问答 / 文档审查 / EHS管理</div></div>
|
||||
<div class="m"><div class="n">移动端 & 企业Bot</div><div class="d">飞书 / 钉钉 / Teams / 巡检App</div></div>
|
||||
<div class="m"><div class="n">企业系统集成</div><div class="d">风控中台 · PLM/ERP· Webhook</div></div>
|
||||
<div class="m"><div class="n">API Gateway</div><div class="d">Nginx · 限流 · TLS · 路由</div></div>
|
||||
<div class="m"><div class="n">RBAC权限网关</div><div class="d">四角色鉴权:研发 / 生产 / 采购 / 法务</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arr-row"><div class="arr"></div><div class="arr"></div><div class="arr"></div></div>
|
||||
|
||||
<!-- ═══ L2 业务能力层 ═══ -->
|
||||
<div class="layer c2">
|
||||
<div class="lh"><span class="ico">💼</span> 业务能力层 <span class="en">Business Capability Layer</span></div>
|
||||
<div class="lb g5">
|
||||
<div class="m"><div class="n">合规知识库问答</div><div class="d">混合检索 · 中英双语 · 引文溯源</div></div>
|
||||
<div class="m"><div class="n">智能文档审查</div><div class="d">上传审查 · 条款比对 · 风险标注</div></div>
|
||||
<div class="m"><div class="n">EHS隐患识别 & 体系审计</div><div class="d">SIF预测 · 四维根因 · ISO 45001扫描</div></div>
|
||||
<div class="m"><div class="n">法规变更监控 & 推送</div><div class="d">自动检测 · 增量索引 · 精准推送</div></div>
|
||||
<div class="m"><div class="n">个性化推荐 & 报告</div><div class="d">角色画像 · 风险等级 · 整改建议</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arr-row"><div class="arr"></div><div class="arr"></div><div class="arr"></div></div>
|
||||
|
||||
<!-- ═══ L3 法规感知 & 知识自动更新闭环 ═══ -->
|
||||
<div class="layer" style="border-color:var(--orange);background:var(--orange-bg);">
|
||||
<div class="lh" style="background:linear-gradient(135deg,var(--orange-dark),var(--orange));"><span class="ico">📡</span> 法规感知 & 知识自动更新闭环 <span class="en">Regulation Awareness & Auto-Update Loop</span></div>
|
||||
<div class="lb" style="grid-template-columns:1fr; gap:0;">
|
||||
<div style="display:flex;align-items:stretch;gap:0;background:#fff;border-radius:10px;border:1.5px solid #F5CCE0;padding:0;overflow:hidden;">
|
||||
<div style="flex:1;padding:14px 18px;border-right:2px solid var(--pink-light);text-align:center;">
|
||||
<div style="font-size:14px;font-weight:700;color:var(--pink-dark);margin-bottom:4px;">① 法规源监控</div>
|
||||
<div style="font-size:11px;color:var(--gray-mid);">定时爬取国标网 · 工信部 · UN-ECE<br>EUR-Lex · 碳交易平台 · 行业通报</div>
|
||||
</div>
|
||||
<div style="flex:0 0 30px;display:flex;align-items:center;justify-content:center;color:var(--pink);font-size:18px;">→</div>
|
||||
<div style="flex:1;padding:14px 18px;border-right:2px solid var(--pink-light);text-align:center;">
|
||||
<div style="font-size:14px;font-weight:700;color:var(--pink-dark);margin-bottom:4px;">② 智能变更感知</div>
|
||||
<div style="font-size:11px;color:var(--gray-mid);">NLP比对新旧版本 · 版本Diff提取<br>自动识别新增/修订/废止条款</div>
|
||||
</div>
|
||||
<div style="flex:0 0 30px;display:flex;align-items:center;justify-content:center;color:var(--pink);font-size:18px;">→</div>
|
||||
<div style="flex:1;padding:14px 18px;border-right:2px solid var(--pink-light);text-align:center;">
|
||||
<div style="font-size:14px;font-weight:700;color:var(--pink-dark);margin-bottom:4px;">③ 自动解析入库</div>
|
||||
<div style="font-size:11px;color:var(--gray-mid);">MinerU/OCR解析 · 条款级分块<br>BGE-M3嵌入 · Milvus+PostgreSQL写入</div>
|
||||
</div>
|
||||
<div style="flex:0 0 30px;display:flex;align-items:center;justify-content:center;color:var(--pink);font-size:18px;">→</div>
|
||||
<div style="flex:1;padding:14px 18px;border-right:2px solid var(--pink-light);text-align:center;">
|
||||
<div style="font-size:14px;font-weight:700;color:var(--pink-dark);margin-bottom:4px;">④ 知识图谱更新</div>
|
||||
<div style="font-size:11px;color:var(--gray-mid);">Neo4j关系同步 · 条款义务映射<br>影响范围分析 · 关联企业制度</div>
|
||||
</div>
|
||||
<div style="flex:0 0 30px;display:flex;align-items:center;justify-content:center;color:var(--pink);font-size:18px;">→</div>
|
||||
<div style="flex:1;padding:14px 18px;border-right:2px solid var(--pink-light);text-align:center;">
|
||||
<div style="font-size:14px;font-weight:700;color:var(--pink-dark);margin-bottom:4px;">⑤ 差距分析 & 推送</div>
|
||||
<div style="font-size:11px;color:var(--gray-mid);">AI对比企业现状与新法差距<br>按角色/业务域精准推送变更摘要</div>
|
||||
</div>
|
||||
<div style="flex:0 0 30px;display:flex;align-items:center;justify-content:center;color:var(--pink);font-size:18px;">→</div>
|
||||
<div style="flex:1;padding:14px 18px;text-align:center;">
|
||||
<div style="font-size:14px;font-weight:700;color:var(--pink-dark);margin-bottom:4px;">⑥ 触发整改闭环</div>
|
||||
<div style="font-size:11px;color:var(--gray-mid);">自动生成整改任务 · 关联责任人<br>整改进度跟踪 → 复审归档 ↺ 回到①</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arr-row"><div class="arr"></div><div class="arr"></div><div class="arr"></div></div>
|
||||
|
||||
<!-- ═══ L4 AI引擎层 ═══ -->
|
||||
<div class="layer c3">
|
||||
<div class="lh"><span class="ico">🧠</span> AI引擎层 <span class="en">AI Engine Layer</span></div>
|
||||
<div class="lb g5">
|
||||
<div class="m"><div class="n">RAG检索引擎</div><div class="d">BM25 + 向量双路召回 · Cross-Encoder精排</div></div>
|
||||
<div class="m"><div class="n">LLM问答生成</div><div class="d">DeepSeek / Qwen2.5 · 引文锚定输出 · 引文置信度</div></div>
|
||||
<div class="m"><div class="n">文档解析 & OCR</div><div class="d">MinerU · 阿里云解析 · 版面感知 109语言</div></div>
|
||||
<div class="m"><div class="n">知识图谱推理</div><div class="d">Neo4j · 法规-条款-义务关系 · 多跳推理</div></div>
|
||||
<div class="m"><div class="n">NLP & 合规比对</div><div class="d">实体识别 · 条款级语义对比 · 风险评分</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arr-row"><div class="arr"></div><div class="arr"></div><div class="arr"></div></div>
|
||||
|
||||
<!-- ═══ L5 数据 & 知识层 ═══ -->
|
||||
<div class="layer c4">
|
||||
<div class="lh"><span class="ico">💾</span> 数据 & 知识层 <span class="en">Data & Knowledge Layer</span></div>
|
||||
<div class="lb g5">
|
||||
<div class="m"><div class="n">Milvus 向量库</div><div class="d">Dense + Sparse + Hybrid 语义检索</div></div>
|
||||
<div class="m"><div class="n">PostgreSQL</div><div class="d">元数据 · 权限 · 任务状态 · 法规版本</div></div>
|
||||
<div class="m"><div class="n">Neo4j + S3/MinIO</div><div class="d">知识图谱存储 · 原始文件与解析产物</div></div>
|
||||
<div class="m"><div class="n">消息队列 & 缓存</div><div class="d">RabbitMQ/Kafka 任务分发 · Redis 热数据</div></div>
|
||||
<div class="m"><div class="n">法规知识库</div><div class="d">车辆安全 · 数据安全 · EHS · 碳排放 · 质量体系 · 案例库 · 术语库</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arr-row"><div class="arr"></div><div class="arr"></div><div class="arr"></div></div>
|
||||
|
||||
<!-- ═══ L6 基础设施层 ═══ -->
|
||||
<div class="layer c5">
|
||||
<div class="lh"><span class="ico">🏗️</span> 基础设施层 <span class="en">Infrastructure Layer</span></div>
|
||||
<div class="lb g5">
|
||||
<div class="m"><div class="n">安全与治理</div><div class="d">JWT/OAuth2 · RBAC · 数据脱敏 · 审计日志 · 私有化部署</div></div>
|
||||
<div class="m"><div class="n">容器编排</div><div class="d">Docker · 弹性伸缩 · GPU集群</div></div>
|
||||
<div class="m"><div class="n">运维观测</div><div class="d">Prometheus · Grafana · ELK · 链路追踪 · 告警</div></div>
|
||||
<div class="m"><div class="n">CI/CD & 网络</div><div class="d">GitLab CI · VPC隔离 · VPN · 备份灾备</div></div>
|
||||
<div class="m"><div class="n">多语言嵌入模型</div><div class="d">BGE-M3 · 中英双语 · 8192 tokens 上下文</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ 图例 ═══ -->
|
||||
<div class="legend">
|
||||
<h3>核心技术选型</h3>
|
||||
<div class="lg">
|
||||
<div class="li"><div class="ld" style="background:#E20074"></div>API: FastAPI</div>
|
||||
<div class="li"><div class="ld" style="background:#000E5E"></div>RAG: LangChain / LlamaIndex</div>
|
||||
<div class="li"><div class="ld" style="background:#E20074"></div>嵌入: BGE-M3</div>
|
||||
<div class="li"><div class="ld" style="background:#32B9AF"></div>向量库: Milvus</div>
|
||||
<div class="li"><div class="ld" style="background:#E20074"></div>解析: MinerU + 阿里云</div>
|
||||
<div class="li"><div class="ld" style="background:#000E5E"></div>图谱: Neo4j</div>
|
||||
<div class="li"><div class="ld" style="background:#E20074"></div>LLM: DeepSeek / Qwen2.5</div>
|
||||
<div class="li"><div class="ld" style="background:#4B4B4B"></div>队列: Kafka + RabbitMQ</div>
|
||||
<div class="li"><div class="ld" style="background:#32B9AF"></div>关系库: PostgreSQL</div>
|
||||
<div class="li"><div class="ld" style="background:#F26B43"></div>对象存储: S3 / MinIO</div>
|
||||
<div class="li"><div class="ld" style="background:#4B4B4B"></div>缓存: Redis</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">AI+合规智能中枢 v1.0 | T-systems AI Regulations Team | 2026.04</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
567
02_Architecture_Detail.html
Normal file
567
02_Architecture_Detail.html
Normal file
@@ -0,0 +1,567 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI+合规智能中枢 — 详细技术架构图</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&display=swap');
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
|
||||
background: linear-gradient(160deg, #fef5f9 0%, #f8eaf0 100%);
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.page { max-width: 1800px; margin: 0 auto; }
|
||||
|
||||
.header { text-align:center; margin-bottom:30px; }
|
||||
.header h1 { font-size:32px; font-weight:900; color:#000E5E; }
|
||||
.header .sub { font-size:16px; color:#4B4B4B; margin-top:4px; }
|
||||
|
||||
/* ── 总体布局:左主区 + 右侧栏 ── */
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 280px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* ── 主区域 ── */
|
||||
.main { display:flex; flex-direction:column; gap:16px; }
|
||||
|
||||
/* ── 通用框 ── */
|
||||
.box {
|
||||
border-radius: 12px;
|
||||
border: 2px solid;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
.box-header {
|
||||
padding: 10px 18px;
|
||||
color: white;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.box-header .en { font-size: 11px; opacity: 0.75; font-weight: 400; }
|
||||
.box-body { padding: 14px 16px; }
|
||||
.services-grid .box-header { min-height: 56px; }
|
||||
|
||||
/* ── 小模块 ── */
|
||||
.mod {
|
||||
background: #FAFBFC;
|
||||
border: 1.5px solid #DDE1E6;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 6px;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.mod:hover { background: #F0F4F8; }
|
||||
.mod-title { font-size: 13px; font-weight: 700; color: #000E5E; }
|
||||
.mod-desc { font-size: 11px; color: #7F8C8D; margin-top: 2px; }
|
||||
.mod-api { font-size: 10px; color: #2980B9; font-family: 'Consolas', monospace; margin-top: 2px; }
|
||||
|
||||
/* ── 用户行 ── */
|
||||
.user-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
.user-card {
|
||||
background: linear-gradient(135deg, #B0005A, #E20074);
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
padding: 12px 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.user-card .name { font-size: 14px; font-weight: 700; }
|
||||
.user-card .role { font-size: 11px; opacity: 0.8; margin-top: 2px; }
|
||||
|
||||
/* ── 网关横条 ── */
|
||||
.gw-bar {
|
||||
background: linear-gradient(135deg, #B0005A, #E20074);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.gw-bar .title { font-size: 14px; font-weight: 700; }
|
||||
.gw-bar .items { font-size: 11px; opacity: 0.85; }
|
||||
|
||||
/* ── 服务列 ── */
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
/* ── 数据流标注 ── */
|
||||
.flow-section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
border: 1.5px solid #DDE1E6;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
.flow-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #000E5E;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #EEF0F2;
|
||||
}
|
||||
.flow-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
color: #5D6D7E;
|
||||
}
|
||||
.flow-num {
|
||||
background: #E20074;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── 中间件行 ── */
|
||||
.mid-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
.mid-card {
|
||||
border-radius: 8px;
|
||||
border: 1.5px solid;
|
||||
padding: 10px 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.mid-card .name { font-size: 14px; font-weight: 700; }
|
||||
.mid-card .desc { font-size: 10px; margin-top: 4px; }
|
||||
|
||||
/* ── AI模型行 ── */
|
||||
.ai-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* ── 数据源行 ── */
|
||||
.src-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* ── 右侧栏 ── */
|
||||
.sidebar { display:flex; flex-direction:column; gap:16px; }
|
||||
|
||||
.side-box {
|
||||
border-radius: 12px;
|
||||
border: 2px solid;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
.side-header {
|
||||
padding: 8px 14px;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.side-body { padding: 10px 14px; }
|
||||
.side-item {
|
||||
font-size: 11px;
|
||||
color: #4B4B4B;
|
||||
padding: 3px 0;
|
||||
padding-left: 14px;
|
||||
position: relative;
|
||||
}
|
||||
.side-item::before {
|
||||
content: '▸';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #E20074;
|
||||
}
|
||||
|
||||
/* ── 箭头 ── */
|
||||
.arrows {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 180px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
.arr { width: 2px; height: 18px; background: #E20074; position: relative; }
|
||||
.arr::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -5px; left: -4px;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 6px solid #E20074;
|
||||
}
|
||||
|
||||
/* ── 颜色 ── */
|
||||
.c-teal { border-color: #148F77; }
|
||||
.c-teal .box-header, .c-teal .side-header { background: linear-gradient(135deg, #0E6655, #148F77); }
|
||||
.c-teal .mid-card { border-color: #A0D8C8; background: #E0F7F3; }
|
||||
.c-teal .mid-card .name { color: #0E6655; }
|
||||
.c-teal .mid-card .desc { color: #4B4B4B; }
|
||||
|
||||
.c-purple { border-color: #7D3CB5; }
|
||||
.c-purple .box-header, .c-purple .side-header { background: linear-gradient(135deg, #5B2C8B, #7D3CB5); }
|
||||
.c-purple .mid-card { border-color: #C8A8E0; background: #F0E6F6; }
|
||||
.c-purple .mid-card .name { color: #5B2C8B; }
|
||||
.c-purple .mid-card .desc { color: #4B4B4B; }
|
||||
|
||||
.c-green { border-color: #2D8B57; }
|
||||
.c-green .box-header, .c-green .side-header { background: linear-gradient(135deg, #1B5E3B, #2D8B57); }
|
||||
.c-green .mid-card { border-color: #A8D8B8; background: #E8F6EF; }
|
||||
.c-green .mid-card .name { color: #1B5E3B; }
|
||||
.c-green .mid-card .desc { color: #4B4B4B; }
|
||||
|
||||
.c-blue { border-color: #000E5E; }
|
||||
.c-blue .box-header, .c-blue .side-header { background: linear-gradient(135deg, #000E5E, #1A2B6B); }
|
||||
.c-blue .mid-card { border-color: #B0C4E8; background: #D3E7F3; }
|
||||
.c-blue .mid-card .name { color: #000E5E; }
|
||||
.c-blue .mid-card .desc { color: #4B4B4B; }
|
||||
|
||||
.c-orange { border-color: #F26B43; }
|
||||
.c-orange .box-header, .c-orange .side-header { background: linear-gradient(135deg, #D4532B, #F26B43); }
|
||||
.c-orange .mid-card { border-color: #E0B888; background: #FEF0EB; }
|
||||
.c-orange .mid-card .name { color: #D4532B; }
|
||||
.c-orange .mid-card .desc { color: #4B4B4B; }
|
||||
|
||||
.c-red { border-color: #E20074; }
|
||||
.c-red .box-header, .c-red .side-header { background: linear-gradient(135deg, #B0005A, #E20074); }
|
||||
|
||||
.c-gray { border-color: #4B4B4B; }
|
||||
.c-gray .side-header { background: linear-gradient(135deg, #2C2C2C, #4B4B4B); }
|
||||
|
||||
.c-dark { border-color: #000E5E; }
|
||||
.c-dark .side-header { background: linear-gradient(135deg, #000840, #000E5E); }
|
||||
|
||||
.footer { text-align:center; margin-top:30px; font-size:12px; color:#B0B0B0; }
|
||||
|
||||
/* 连接线标签 */
|
||||
.conn-label {
|
||||
font-size: 10px;
|
||||
color: #95A5A6;
|
||||
text-align: center;
|
||||
padding: 2px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<div class="header">
|
||||
<h1>AI+合规智能中枢 — 详细技术架构图</h1>
|
||||
<div class="sub">面向车企与工厂 | 全链路合规智能平台 | Detailed Architecture</div>
|
||||
</div>
|
||||
|
||||
<div class="layout">
|
||||
|
||||
<!-- ════════════════════ 主区域 ════════════════════ -->
|
||||
<div class="main">
|
||||
|
||||
<!-- 用户行 -->
|
||||
<div class="user-row">
|
||||
<div class="user-card"><div class="name">车企研发/法务</div><div class="role">Web门户 + API调用</div></div>
|
||||
<div class="user-card"><div class="name">工厂EHS工程师</div><div class="role">移动端 + Bot通知</div></div>
|
||||
<div class="user-card"><div class="name">采购/供应链</div><div class="role">PLM/ERP集成</div></div>
|
||||
<div class="user-card"><div class="name">管理层/审计</div><div class="role">Dashboard + 报表</div></div>
|
||||
<div class="user-card"><div class="name">外部供应商</div><div class="role">合规声明上传</div></div>
|
||||
</div>
|
||||
|
||||
<div class="arrows"><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div></div>
|
||||
|
||||
<!-- API网关 -->
|
||||
<div class="gw-bar">
|
||||
<div class="title">API Gateway / nginx / Traefik</div>
|
||||
<div class="items">TLS终止 | 限流熔断 | 路由分发 | 负载均衡 | JWT校验 | 请求日志</div>
|
||||
</div>
|
||||
|
||||
<div class="arrows"><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div></div>
|
||||
|
||||
<!-- 六大核心服务 -->
|
||||
<div class="services-grid" style="grid-template-columns:repeat(6,1fr);">
|
||||
<!-- kbmp-service -->
|
||||
<div class="box c-teal">
|
||||
<div class="box-header">kbmp-service <span class="en">知识库公开接口</span></div>
|
||||
|
||||
<div class="box-body">
|
||||
<div class="mod"><div class="mod-title">知识库CRUD</div><div class="mod-api">POST /workspace/create</div><div class="mod-desc">创建知识库空间</div></div>
|
||||
<div class="mod"><div class="mod-title">文件上传</div><div class="mod-api">POST /files/upload</div><div class="mod-desc">文件登记 + 任务投递</div></div>
|
||||
<div class="mod"><div class="mod-title">检索编排</div><div class="mod-api">POST /knowledge/retrieval</div><div class="mod-desc">意图识别→召回→重排→生成</div></div>
|
||||
<div class="mod"><div class="mod-title">Chunk召回</div><div class="mod-api">POST /chunks/recall</div><div class="mod-desc">向量+关键词混合召回</div></div>
|
||||
<div class="mod"><div class="mod-title">任务投递</div><div class="mod-desc">解析/索引任务→消息队列</div></div>
|
||||
<div class="mod"><div class="mod-title">Worker入口</div><div class="mod-desc">worker启动/心跳/状态上报</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- mcp-server -->
|
||||
<div class="box c-purple">
|
||||
<div class="box-header">mcp-server <span class="en">文档解析服务</span></div>
|
||||
<div class="box-body">
|
||||
<div class="mod"><div class="mod-title">阿里云解析</div><div class="mod-api">POST /parse-document</div><div class="mod-desc">云端高精度解析</div></div>
|
||||
<div class="mod"><div class="mod-title">MinerU解析</div><div class="mod-api">POST /mineru-parse</div><div class="mod-desc">本地多模态解析引擎</div></div>
|
||||
<div class="mod"><div class="mod-title">OCR引擎</div><div class="mod-desc">版面感知 109语言支持</div></div>
|
||||
<div class="mod"><div class="mod-title">Markdown生成</div><div class="mod-desc">结构化文本输出</div></div>
|
||||
<div class="mod"><div class="mod-title">表格/图片提取</div><div class="mod-desc">PDF/Word/Excel多格式</div></div>
|
||||
<div class="mod"><div class="mod-title">解析回退策略</div><div class="mod-desc">阿里云→MinerU→本地Fallback</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 合规业务后端 -->
|
||||
<div class="box c-green">
|
||||
<div class="box-header">合规业务后端 <span class="en">法规 + 审查 + 推送</span></div>
|
||||
<div class="box-body">
|
||||
<div class="mod"><div class="mod-title">法规下载</div><div class="mod-api">POST /compliance/regulations/download</div><div class="mod-desc">从互联网下载法规文档</div></div>
|
||||
<div class="mod"><div class="mod-title">法规更新/同步</div><div class="mod-api">POST /compliance/regulations/update</div><div class="mod-desc">版本管理+增量索引同步</div></div>
|
||||
<div class="mod"><div class="mod-title">权限分级管理</div><div class="mod-api">POST /compliance/access-control</div><div class="mod-desc">研发/生产/采购/法务四角色</div></div>
|
||||
<div class="mod"><div class="mod-title">智能合规审查</div><div class="mod-api">POST /compliance/check</div><div class="mod-desc">条款级比对+风险评分</div></div>
|
||||
<div class="mod"><div class="mod-title">合规结果查询</div><div class="mod-api">GET /compliance/query</div><div class="mod-desc">审查结果+风险项+整改建议</div></div>
|
||||
<div class="mod"><div class="mod-title">事件订阅推送</div><div class="mod-api">POST /compliance/subscribe</div><div class="mod-desc">Webhook+多渠道推送</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 法规感知引擎 (新增) -->
|
||||
<div class="box c-red">
|
||||
<div class="box-header">法规感知引擎 <span class="en">Regulation Awareness Engine</span></div>
|
||||
<div class="box-body">
|
||||
<div class="mod"><div class="mod-title">法规源监控</div><div class="mod-desc">定时爬取国标网/工信部/UN-ECE<br>EUR-Lex/碳交易/行业通报</div></div>
|
||||
<div class="mod"><div class="mod-title">智能变更感知</div><div class="mod-desc">NLP比对新旧版本Diff<br>自动识别新增/修订/废止条款</div></div>
|
||||
<div class="mod"><div class="mod-title">自动解析入库</div><div class="mod-desc">触发MinerU解析→条款分块<br>→BGE-M3嵌入→Milvus+PG写入</div></div>
|
||||
<div class="mod"><div class="mod-title">知识图谱同步</div><div class="mod-desc">Neo4j关系更新<br>条款义务映射+影响范围分析</div></div>
|
||||
<div class="mod"><div class="mod-title">差距分析</div><div class="mod-desc">AI对比企业制度与新法差距<br>自动生成变更影响评估</div></div>
|
||||
<div class="mod"><div class="mod-title">变更推送 & 整改触发</div><div class="mod-desc">按角色/域精准推送摘要<br>自动创建整改任务→闭环跟踪</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI推理引擎 -->
|
||||
<div class="box c-blue">
|
||||
<div class="box-header">AI推理引擎 <span class="en">RAG + LLM + 图谱</span></div>
|
||||
<div class="box-body">
|
||||
<div class="mod"><div class="mod-title">混合检索</div><div class="mod-desc">BM25关键词 + BGE-M3向量<br>本地+网络双路召回</div></div>
|
||||
<div class="mod"><div class="mod-title">BGE-M3嵌入</div><div class="mod-desc">中英双语 8192 tokens<br>Dense+Sparse+Multi-vec</div></div>
|
||||
<div class="mod"><div class="mod-title">Reranker精排</div><div class="mod-desc">Cross-Encoder语义精排<br>Top-K结果重排序</div></div>
|
||||
<div class="mod"><div class="mod-title">LLM生成</div><div class="mod-desc">DeepSeek/Qwen2.5<br>引文锚定+置信度评分</div></div>
|
||||
<div class="mod"><div class="mod-title">知识图谱</div><div class="mod-desc">Neo4j法规实体关系图<br>多跳推理+条款关联</div></div>
|
||||
<div class="mod"><div class="mod-title">NLP分析</div><div class="mod-desc">实体识别/文档分类<br>隐患实体抽取</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Worker集群 -->
|
||||
<div class="box c-orange">
|
||||
<div class="box-header">Worker集群 <span class="en">异步任务执行</span></div>
|
||||
<div class="box-body">
|
||||
<div class="mod"><div class="mod-title">解析Worker</div><div class="mod-desc">消费解析任务→调用mcp-server</div></div>
|
||||
<div class="mod"><div class="mod-title">向量化Worker</div><div class="mod-desc">文本清洗→切分→嵌入→入库</div></div>
|
||||
<div class="mod"><div class="mod-title">合规Worker</div><div class="mod-desc">比对法规→风险评分→报告</div></div>
|
||||
<div class="mod"><div class="mod-title">感知Worker</div><div class="mod-desc">法规变更检测→增量重索引</div></div>
|
||||
<div class="mod"><div class="mod-title">推送Worker</div><div class="mod-desc">消息分发→Email/Bot/站内</div></div>
|
||||
<div class="mod"><div class="mod-title">调度框架</div><div class="mod-desc">Celery + Cron定时<br>失败重试+死信队列</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conn-label">服务 ↔ 中间件 双向通信</div>
|
||||
<div class="arrows"><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div></div>
|
||||
|
||||
<!-- 数据存储与中间件 -->
|
||||
<div class="mid-grid">
|
||||
<div class="mid-card" style="border-color:#148F77;background:#E0F7F3;"><div class="name" style="color:#0E6655">Milvus</div><div class="desc">向量数据库<br>Dense+Sparse+Hybrid</div></div>
|
||||
<div class="mid-card" style="border-color:#000E5E;background:#D3E7F3;"><div class="name" style="color:#000E5E">PostgreSQL</div><div class="desc">关系数据库<br>元数据/权限/任务</div></div>
|
||||
<div class="mid-card" style="border-color:#F26B43;background:#FEF0EB;"><div class="name" style="color:#D4532B">S3 / MinIO</div><div class="desc">对象存储<br>原始文件/解析产物</div></div>
|
||||
<div class="mid-card" style="border-color:#7D3CB5;background:#F0E6F6;"><div class="name" style="color:#5B2C8B">Neo4j</div><div class="desc">图数据库<br>法规实体关系图谱</div></div>
|
||||
<div class="mid-card" style="border-color:#E20074;background:#FDF2F7;"><div class="name" style="color:#B0005A">RabbitMQ</div><div class="desc">消息队列<br>异步任务分发</div></div>
|
||||
<div class="mid-card" style="border-color:#32B9AF;background:#E0F7F5;"><div class="name" style="color:#1A8A82">Redis 7.x</div><div class="desc">缓存/会话<br>热数据/分布式锁</div></div>
|
||||
</div>
|
||||
|
||||
<div class="conn-label">中间件 ↔ AI模型 调用链路</div>
|
||||
<div class="arrows"><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div></div>
|
||||
|
||||
<!-- AI模型层 -->
|
||||
<div class="ai-grid">
|
||||
<div class="box c-teal">
|
||||
<div class="box-header">嵌入模型</div>
|
||||
<div class="box-body">
|
||||
<div class="mod"><div class="mod-title">BGE-M3 (主模型)</div><div class="mod-desc">中英双语 100+语言</div></div>
|
||||
<div class="mod"><div class="mod-title">bge-large-zh-v1.5</div><div class="mod-desc">中文专项嵌入</div></div>
|
||||
<div class="mod"><div class="mod-title">多语言E5</div><div class="mod-desc">跨语言检索备选</div></div>
|
||||
<div class="mod"><div class="mod-title">8192 token上下文</div><div class="mod-desc">长文档向量化支持</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box c-purple">
|
||||
<div class="box-header">LLM大模型</div>
|
||||
<div class="box-body">
|
||||
<div class="mod"><div class="mod-title">DeepSeek-V3 / R1</div><div class="mod-desc">推理能力强, 国产开源</div></div>
|
||||
<div class="mod"><div class="mod-title">Qwen2.5-72B</div><div class="mod-desc">中英双语, 合规场景优化</div></div>
|
||||
<div class="mod"><div class="mod-title">本地私有化部署</div><div class="mod-desc">vLLM/TGI推理加速</div></div>
|
||||
<div class="mod"><div class="mod-title">引文锚定生成</div><div class="mod-desc">输出含原文出处+页码</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box c-orange">
|
||||
<div class="box-header">文档解析模型</div>
|
||||
<div class="box-body">
|
||||
<div class="mod"><div class="mod-title">MinerU (多模态)</div><div class="mod-desc">版面感知PDF解析</div></div>
|
||||
<div class="mod"><div class="mod-title">阿里云文档解析</div><div class="mod-desc">云端高精度解析</div></div>
|
||||
<div class="mod"><div class="mod-title">版面感知OCR</div><div class="mod-desc">109语言扫描件识别</div></div>
|
||||
<div class="mod"><div class="mod-title">表格/图片识别</div><div class="mod-desc">复杂版面结构提取</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box c-blue">
|
||||
<div class="box-header">专项模型</div>
|
||||
<div class="box-body">
|
||||
<div class="mod"><div class="mod-title">Cross-Encoder</div><div class="mod-desc">Reranker语义精排</div></div>
|
||||
<div class="mod"><div class="mod-title">NLP实体抽取</div><div class="mod-desc">法规条款/隐患实体</div></div>
|
||||
<div class="mod"><div class="mod-title">SIF风险评分</div><div class="mod-desc">高严重性事件潜力预测</div></div>
|
||||
<div class="mod"><div class="mod-title">合规分类器</div><div class="mod-desc">法规域/文档类型分类</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conn-label">AI模型 ← 法规数据源 学习与检索</div>
|
||||
<div class="arrows"><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div><div class="arr"></div></div>
|
||||
|
||||
<!-- 法规数据源 -->
|
||||
<div class="src-grid">
|
||||
<div class="mid-card" style="border-color:#E20074;background:#FDF2F7;"><div class="name" style="color:#B0005A">车辆安全法规</div><div class="desc">GB 7258 · GB 18384<br>UN-ECE R155/156</div></div>
|
||||
<div class="mid-card" style="border-color:#7D3CB5;background:#F0E6F6;"><div class="name" style="color:#5B2C8B">数据安全法规</div><div class="desc">PIPL · DSL · GDPR<br>GB/T 35273</div></div>
|
||||
<div class="mid-card" style="border-color:#148F77;background:#E0F7F3;"><div class="name" style="color:#0E6655">工厂EHS法规</div><div class="desc">GB 6441 · AQ/T系列<br>ISO 45001 · IATF 16949</div></div>
|
||||
<div class="mid-card" style="border-color:#32B9AF;background:#E0F7F5;"><div class="name" style="color:#1A8A82">碳排放法规</div><div class="desc">NEV积分 · CCER<br>CBAM碳边境税</div></div>
|
||||
<div class="mid-card" style="border-color:#F26B43;background:#FEF0EB;"><div class="name" style="color:#D4532B">企业内部文档</div><div class="desc">Confluence · SharePoint<br>历史报告 · 审计记录</div></div>
|
||||
<div class="mid-card" style="border-color:#000E5E;background:#D3E7F3;"><div class="name" style="color:#000E5E">行业案例库</div><div class="desc">处罚案例 · 事故通报<br>整改最佳实践</div></div>
|
||||
</div>
|
||||
|
||||
<!-- 数据流 -->
|
||||
<div class="flow-section">
|
||||
<div class="flow-title">核心数据流 (Data Flows)</div>
|
||||
<div class="flow-item"><div class="flow-num" style="background:#E20074;">1</div><b style="color:#E20074;">法规感知闭环:</b> 定时爬取法规源 → NLP变更感知(Diff) → 自动解析入库(MinerU+嵌入) → Milvus+PostgreSQL+Neo4j同步 → 差距分析 → 按角色推送 → 触发整改 ↺ 持续监控</div>
|
||||
<div class="flow-item"><div class="flow-num">2</div><b>上传→解析→入库:</b> 用户上传 → API Gateway → kbmp-service → 队列 → Worker → mcp-server解析 → 文本切分 → BGE-M3嵌入 → Milvus+PostgreSQL写入</div>
|
||||
<div class="flow-item"><div class="flow-num">3</div><b>检索→问答:</b> 用户提问 → 意图识别 → BM25+向量双路召回 → Cross-Encoder精排 → LLM生成(引文锚定) → 返回结果</div>
|
||||
<div class="flow-item"><div class="flow-num">4</div><b>合规审查:</b> 文件上传 → OCR解析 → 条款级分块 → 法规域匹配 → 语义比对 → 风险评分 → 整改建议 → 报告生成</div>
|
||||
<div class="flow-item"><div class="flow-num">5</div><b>EHS隐患:</b> 巡检文本NLP → 隐患实体抽取 → SIF风险评分 → 四维根因分析 → 整改工单 → 验收关闭 → 模型优化</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /main -->
|
||||
|
||||
<!-- ════════════════════ 右侧栏 ════════════════════ -->
|
||||
<div class="sidebar">
|
||||
<!-- 安全 -->
|
||||
<div class="side-box c-gray">
|
||||
<div class="side-header" style="background:linear-gradient(135deg,#000E5E,#1A2B6B);">🔐 安全与治理</div>
|
||||
<div class="side-body">
|
||||
<div class="side-item">Token鉴权 (JWT/OAuth2)</div>
|
||||
<div class="side-item">RBAC角色权限矩阵</div>
|
||||
<div class="side-item">知识库分区隔离</div>
|
||||
<div class="side-item">敏感数据脱敏</div>
|
||||
<div class="side-item">全链路审计日志</div>
|
||||
<div class="side-item">PIPL/DSL数据主权</div>
|
||||
<div class="side-item">私有化部署 (数据不出厂)</div>
|
||||
<div class="side-item">WAF & DDoS防护</div>
|
||||
<div class="side-item">TLS端到端加密</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 运维 -->
|
||||
<div class="side-box c-blue">
|
||||
<div class="side-header" style="background:linear-gradient(135deg,#1A8A82,#32B9AF);">📊 运维观测</div>
|
||||
<div class="side-body">
|
||||
<div class="side-item">Prometheus指标采集</div>
|
||||
<div class="side-item">Grafana可视化面板</div>
|
||||
<div class="side-item">Loki/ELK日志聚合</div>
|
||||
<div class="side-item">分布式链路追踪</div>
|
||||
<div class="side-item">告警规则引擎</div>
|
||||
<div class="side-item">SLA可用性监控</div>
|
||||
<div class="side-item">性能基线管理</div>
|
||||
<div class="side-item">容量规划报表</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 基础设施 -->
|
||||
<div class="side-box c-dark">
|
||||
<div class="side-header">🏗️ 基础设施</div>
|
||||
<div class="side-body">
|
||||
<div class="side-item">Kubernetes容器编排</div>
|
||||
<div class="side-item">Docker容器运行时</div>
|
||||
<div class="side-item">GPU集群 (A100/H100)</div>
|
||||
<div class="side-item">vLLM/TGI推理加速</div>
|
||||
<div class="side-item">CI/CD流水线</div>
|
||||
<div class="side-item">Nginx/Traefik网关</div>
|
||||
<div class="side-item">VPN & 网络隔离</div>
|
||||
<div class="side-item">数据备份 & 灾备</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 开源技术栈 -->
|
||||
<div class="side-box" style="border-color:#000E5E;">
|
||||
<div class="side-header" style="background:linear-gradient(135deg,#000E5E,#1A2B6B);">🔧 核心开源技术栈</div>
|
||||
<div class="side-body">
|
||||
<div class="side-item"><b>LangChain</b> RAG编排框架</div>
|
||||
<div class="side-item"><b>LlamaIndex</b> 数据索引引擎</div>
|
||||
<div class="side-item"><b>RAGFlow</b> 文档理解</div>
|
||||
<div class="side-item"><b>BGE-M3</b> 多语言嵌入</div>
|
||||
<div class="side-item"><b>MinerU</b> 文档解析OCR</div>
|
||||
<div class="side-item"><b>Milvus</b> 向量数据库</div>
|
||||
<div class="side-item"><b>Neo4j</b> 知识图谱</div>
|
||||
<div class="side-item"><b>FastAPI</b> API框架</div>
|
||||
<div class="side-item"><b>Celery</b> 异步任务队列</div>
|
||||
<div class="side-item"><b>DeepSeek</b> 推理LLM</div>
|
||||
<div class="side-item"><b>Qwen2.5</b> 双语LLM</div>
|
||||
<div class="side-item"><b>PyMuPDF</b> PDF处理</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 合规闭环 -->
|
||||
<div class="side-box" style="border-color:#E20074;">
|
||||
<div class="side-header" style="background:linear-gradient(135deg,#B0005A,#E20074);">📡 法规感知自动更新闭环</div>
|
||||
<div class="side-body">
|
||||
<div class="side-item"><b>① 法规源监控</b></div>
|
||||
<div class="side-item" style="padding-left:24px;">定时爬取国标网·工信部·UN-ECE</div>
|
||||
<div class="side-item"><b>② 智能变更感知</b></div>
|
||||
<div class="side-item" style="padding-left:24px;">NLP Diff · 新增/修订/废止识别</div>
|
||||
<div class="side-item"><b>③ 自动解析入库</b></div>
|
||||
<div class="side-item" style="padding-left:24px;">解析→分块→嵌入→Milvus+PG</div>
|
||||
<div class="side-item"><b>④ 知识图谱同步</b></div>
|
||||
<div class="side-item" style="padding-left:24px;">Neo4j关系更新·影响范围分析</div>
|
||||
<div class="side-item"><b>⑤ 差距分析&推送</b></div>
|
||||
<div class="side-item" style="padding-left:24px;">AI比对制度差距·按角色推送</div>
|
||||
<div class="side-item"><b>⑥ 触发整改闭环 ↺</b></div>
|
||||
<div class="side-item" style="padding-left:24px;">自动创建整改任务·闭环跟踪</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="side-box" style="border-color:#F26B43;">
|
||||
<div class="side-header" style="background:linear-gradient(135deg,#D4532B,#F26B43);">🔄 三类合规闭环</div>
|
||||
<div class="side-body">
|
||||
<div class="side-item"><b>法规变更闭环</b></div>
|
||||
<div class="side-item" style="padding-left:24px;">监控→感知→更新→推送→整改→归档</div>
|
||||
<div class="side-item"><b>文档审查闭环</b></div>
|
||||
<div class="side-item" style="padding-left:24px;">上传→解析→比对→标注→整改→复审</div>
|
||||
<div class="side-item"><b>EHS安全闭环</b></div>
|
||||
<div class="side-item" style="padding-left:24px;">发现→评级→派发→跟踪→验收→优化</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /sidebar -->
|
||||
|
||||
</div><!-- /layout -->
|
||||
|
||||
<div class="footer">AI+合规智能中枢 v1.0 | T-systems AI Regulations Team | 2026.04</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
AI_Regulations_Architecture.docx
Normal file
BIN
AI_Regulations_Architecture.docx
Normal file
Binary file not shown.
BIN
AI_Regulations_Report.pptx
Normal file
BIN
AI_Regulations_Report.pptx
Normal file
Binary file not shown.
BIN
Bosch_Workshop_2026_March_AI.pdf
Normal file
BIN
Bosch_Workshop_2026_March_AI.pdf
Normal file
Binary file not shown.
@@ -0,0 +1,535 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>AI + Compliance Hub - Compliance Analysis</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #fafafa;
|
||||
--surface: #ffffff;
|
||||
--surface-warm: var(--surface);
|
||||
--fg: #111111;
|
||||
--fg-2: var(--fg);
|
||||
--muted: #6b6b6b;
|
||||
--meta: var(--muted);
|
||||
--border: #e5e5e5;
|
||||
--border-soft: var(--border);
|
||||
--primary: #e20074;
|
||||
--accent: var(--primary);
|
||||
--accent-on: #ffffff;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), black 8%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 14%);
|
||||
--success: #17a34a;
|
||||
--warn: #eab308;
|
||||
--danger: #dc2626;
|
||||
--font-display: "TeleNeoWeb-Bold", "TeleNeoWeb-Medium", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-body: "TeleNeoWeb-Regular", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
||||
--text-xs: 12px;
|
||||
--text-sm: 14px;
|
||||
--text-base: 16px;
|
||||
--text-lg: 20px;
|
||||
--text-xl: 24px;
|
||||
--text-2xl: 32px;
|
||||
--text-3xl: 48px;
|
||||
--text-4xl: 64px;
|
||||
--leading-body: 1.5;
|
||||
--leading-tight: 1.2;
|
||||
--tracking-display: -0.01em;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-12: 48px;
|
||||
--space-20: 80px;
|
||||
--section-y-desktop: 80px;
|
||||
--section-y-tablet: 48px;
|
||||
--section-y-phone: 32px;
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-pill: 9999px;
|
||||
--elev-flat: none;
|
||||
--elev-ring: 0 0 0 1px var(--border);
|
||||
--elev-raised: 0 2px 8px color-mix(in oklab, var(--fg), transparent 92%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 70%);
|
||||
--motion-fast: 150ms;
|
||||
--motion-base: 200ms;
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--container-max: 1600px;
|
||||
--container-gutter-desktop: 24px;
|
||||
--container-gutter-tablet: 16px;
|
||||
--container-gutter-phone: 12px;
|
||||
--sidebar-w: 240px;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-body);
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
h1, h2, h3, h4 {
|
||||
margin: 0;
|
||||
font-family: var(--font-display);
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-display);
|
||||
}
|
||||
p { margin: 0; text-wrap: pretty; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
a:hover { color: var(--fg); text-decoration: underline; }
|
||||
button { font: inherit; }
|
||||
|
||||
/* ── Sidebar shell ── */
|
||||
.app-shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
|
||||
.sidebar { position: sticky; top: 0; height: 100vh; overflow-y: auto; display: flex; flex-direction: column; background: var(--surface); border-right: 1px solid var(--border); z-index: 10; }
|
||||
.sidebar-brand { display: flex; align-items: center; gap: 10px; height: 56px; padding: 0 16px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
||||
.brand-logo { width: 26px; height: 26px; background: var(--accent); border-radius: 6px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.brand-logo svg { color: #fff; }
|
||||
.sidebar-brand-name { font-family: var(--font-display); font-size: 13px; font-weight: 700; line-height: 1.2; }
|
||||
.sidebar-brand-sub { font-size: 10px; color: var(--muted); font-family: var(--font-mono); letter-spacing: 0.04em; }
|
||||
.sidebar-nav { flex: 1; padding: 12px 0; overflow-y: auto; }
|
||||
.nav-group { padding: 0 8px 4px; }
|
||||
.nav-group + .nav-group { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
|
||||
.nav-group-label { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); padding: 0 8px 6px; display: block; }
|
||||
.nav-item { display: flex; align-items: center; gap: 10px; height: 36px; padding: 0 8px; border-radius: 6px; color: var(--muted); font-size: 13px; cursor: pointer; transition: background 140ms, color 140ms; position: relative; }
|
||||
.nav-item:hover { background: color-mix(in oklab, var(--fg), transparent 94%); color: var(--fg); text-decoration: none; }
|
||||
.nav-item.active { background: color-mix(in oklab, var(--accent), transparent 90%); color: var(--accent); font-weight: 600; }
|
||||
.nav-item.active::before { content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px; border-radius: 0 3px 3px 0; background: var(--accent); }
|
||||
.nav-icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.7; }
|
||||
.nav-item.active .nav-icon { opacity: 1; }
|
||||
.sidebar-footer { border-top: 1px solid var(--border); padding: 10px 8px; flex-shrink: 0; display: flex; flex-direction: column; gap: 4px; }
|
||||
.sidebar-user { display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: 6px; cursor: pointer; }
|
||||
.sidebar-user:hover { background: color-mix(in oklab, var(--fg), transparent 94%); }
|
||||
.avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); color: #fff; font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.sidebar-user-info { min-width: 0; }
|
||||
.sidebar-user-name { font-size: 13px; font-weight: 600; color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.sidebar-user-role { font-size: 11px; color: var(--muted); font-family: var(--font-mono); }
|
||||
.sidebar-action { display: flex; align-items: center; gap: 10px; height: 34px; padding: 0 8px; border-radius: 6px; color: var(--muted); font-size: 13px; cursor: pointer; border: none; background: transparent; width: 100%; text-align: left; transition: background 140ms, color 140ms; }
|
||||
.sidebar-action:hover { background: color-mix(in oklab, var(--fg), transparent 94%); color: var(--fg); }
|
||||
.content-area { display: flex; flex-direction: column; min-width: 0; min-height: 100vh; }
|
||||
.content-topbar { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 12px; height: 56px; padding: 0 24px; border-bottom: 1px solid var(--border); background: color-mix(in oklab, var(--bg), transparent 4%); backdrop-filter: blur(10px); }
|
||||
.topbar-title { font-weight: 600; font-size: 15px; color: var(--fg); flex: 1; }
|
||||
.search { display: flex; align-items: center; gap: 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface); padding: 0 12px; height: 34px; width: 240px; }
|
||||
.search input { border: 0; outline: none; background: transparent; width: 100%; color: var(--fg); font-size: 13px; }
|
||||
.search input::placeholder { color: var(--muted); }
|
||||
.footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 34px; padding: 0 24px; border-top: 1px solid var(--border); color: var(--muted); font-size: 11px; font-family: var(--font-mono); letter-spacing: 0.1em; text-transform: uppercase; }
|
||||
.footer-status { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.footer-dot { width: 7px; height: 7px; border-radius: 50%; background: #19d3a2; box-shadow: 0 0 0 3px color-mix(in oklab, #19d3a2, transparent 82%); }
|
||||
@media (max-width: 700px) { .app-shell { grid-template-columns: 1fr; } .sidebar { display: none; } }
|
||||
|
||||
/* ── Page / component styles ── */
|
||||
.page {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
.hero {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
.eyebrow {
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.hero h1 { font-size: clamp(30px, 4vw, 44px); }
|
||||
.hero p { color: var(--muted); max-width: 74ch; }
|
||||
.btn {
|
||||
min-height: 44px;
|
||||
padding: 0 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
}
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent-on);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
}
|
||||
.workspace {
|
||||
display: grid;
|
||||
grid-template-columns: 0.95fr 1.25fr 0.9fr;
|
||||
gap: 16px;
|
||||
min-height: 760px;
|
||||
}
|
||||
.card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface);
|
||||
padding: 18px;
|
||||
min-width: 0;
|
||||
}
|
||||
.section-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.section-head h2 { font-size: var(--text-xl); }
|
||||
.helper {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.mono {
|
||||
font-family: var(--font-mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.source-list,
|
||||
.finding-list,
|
||||
.actions {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.source-item,
|
||||
.finding,
|
||||
.action-item,
|
||||
.stage {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 14%);
|
||||
padding: 14px;
|
||||
}
|
||||
.pill,
|
||||
.status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: fit-content;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.status::before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: currentColor;
|
||||
}
|
||||
.status.ok { color: var(--success); }
|
||||
.status.warn { color: color-mix(in oklab, var(--warn), black 24%); }
|
||||
.status.risk { color: var(--danger); }
|
||||
.paragraph {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface);
|
||||
padding: 18px;
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
.paragraph mark {
|
||||
background: color-mix(in oklab, var(--accent), white 80%);
|
||||
color: inherit;
|
||||
padding: 0 3px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.stage-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.finding strong,
|
||||
.source-item strong,
|
||||
.action-item strong,
|
||||
.stage strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.score-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.progress {
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in oklab, var(--fg), transparent 95%);
|
||||
overflow: hidden;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.progress > span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: color-mix(in oklab, var(--accent), white 28%);
|
||||
}
|
||||
.conclusion {
|
||||
margin-top: 16px;
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: 16px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.conclusion-box {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 16px;
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 10%);
|
||||
}
|
||||
@media (max-width: 1280px) {
|
||||
.workspace { grid-template-columns: 1fr; }
|
||||
.hero { flex-direction: column; align-items: start; }
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.page { padding: 12px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page="analysis">
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar" aria-label="Primary navigation">
|
||||
<div class="sidebar-brand">
|
||||
<div class="brand-logo">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M2 3.5h12v1.5H2zm5.25 1.5h1.5v8h-1.5zm-3 2h2.25v1.5H4.25zm5.25 0h2.25v1.5H9.5zm-3 3h2.5v1.5H6.5z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sidebar-brand-name">T-Systems</div>
|
||||
<div class="sidebar-brand-sub">Regulation Hub</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="sidebar-nav" aria-label="Primary">
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">主导航</span>
|
||||
<a class="nav-item" href="index.html"><svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M2 2h5v5H2zm7 0h5v5H9zM2 9h5v5H2zm7 0h5v5H9z" fill="currentColor" opacity=".7"/></svg>概览</a>
|
||||
<a class="nav-item" href="dashboard.html"><svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M1.5 2.5h13v1H1.5zm0 3h13v1H1.5zm0 3h8v1h-8zm0 3h6v1h-6z" fill="currentColor"/></svg>系统状态</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">工作台</span>
|
||||
<a class="nav-item" href="document-management.html"><svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M3 1h7l3 3v11H3V1zm1 1v12h8V5h-3V2H4zm5 .5V4h1.5L9 1.5zM6 7h4v1H6zm0 2h4v1H6zm0 2h3v1H6z" fill="currentColor"/></svg>文档管理</a>
|
||||
<a class="nav-item active" href="compliance-analysis.html"><svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M8 1l7 3-1 6a7 7 0 01-6 5A7 7 0 011 10L0 4l8-3zm0 1.2L1.3 4.8l.8 5.1A6 6 0 008 14.8a6 6 0 005.9-4.9l.8-5.1L8 2.2zM7.5 5h1v4.5l-1 .5V5zm0 5.5h1v1h-1v-1z" fill="currentColor"/></svg>合规分析</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">对话</span>
|
||||
<a class="nav-item" href="regulation-chat.html"><svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M2 2h12a1 1 0 011 1v8a1 1 0 01-1 1H5l-3 2.5V3a1 1 0 011-1zm0 1v9.5L4.5 11H14V3H2zm2 2h8v1H4zm0 2h6v1H4z" fill="currentColor"/></svg>法规对话</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="avatar">TS</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">T-Systems User</div>
|
||||
<div class="sidebar-user-role">Compliance Analyst</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="sidebar-action od-theme-toggle" type="button" data-od-theme aria-label="Toggle theme">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M8 3a5 5 0 100 10A5 5 0 008 3zM2 8a6 6 0 1112 0A6 6 0 012 8z" fill="currentColor"/></svg>
|
||||
主题
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="content-area">
|
||||
<header class="content-topbar">
|
||||
<div class="topbar-title">合规分析</div>
|
||||
<div class="search">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M6.5 1a5.5 5.5 0 014.23 9.02l3.62 3.62-.7.71-3.63-3.63A5.5 5.5 0 116.5 1zm0 1a4.5 4.5 0 100 9 4.5 4.5 0 000-9z" fill="currentColor" opacity=".5"/></svg>
|
||||
<input type="search" placeholder="Search findings…" aria-label="Search" />
|
||||
</div>
|
||||
<button class="btn btn-primary">New analysis</button>
|
||||
</header>
|
||||
<main class="page">
|
||||
<section class="hero" data-od-id="analysis-hero">
|
||||
<div>
|
||||
<div class="eyebrow">Compliance analysis workspace</div>
|
||||
<h1>From retrieved regulation to conclusion-ready action.</h1>
|
||||
<p>This screen is built for a reviewer comparing one paragraph of product documentation against candidate regulations, system reasoning, and recommended changes. The rhythm is left-to-right: evidence retrieval, close reading, and decision output.</p>
|
||||
</div>
|
||||
<div style="display:flex; gap:12px; flex-wrap:wrap;">
|
||||
<a class="btn" href="document-detail.html">Open parse detail</a>
|
||||
<a class="btn btn-primary" href="regulation-chat.html">Ask follow-up in chat</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="workspace" data-od-id="analysis-workspace">
|
||||
<aside class="card">
|
||||
<div class="section-head">
|
||||
<h2>Retrieved regulations</h2>
|
||||
<span class="helper">Top 10 by dense similarity</span>
|
||||
</div>
|
||||
<div class="source-list">
|
||||
<div class="source-item">
|
||||
<strong>GB 26112-2010 §4.2 Roof crush resistance</strong>
|
||||
<div class="helper">Primary match · mandatory requirement · promoted as lead citation</div>
|
||||
<div class="score-row">
|
||||
<span class="pill">Vehicle safety</span>
|
||||
<span class="status risk">High impact</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="source-item">
|
||||
<strong>C-NCAP rulebook §3.1 occupant safety context</strong>
|
||||
<div class="helper">Supporting match · interpretation context · not mandatory on its own</div>
|
||||
<div class="score-row">
|
||||
<span class="pill">Assessment</span>
|
||||
<span class="status warn">Context only</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="source-item">
|
||||
<strong>Internal design note: structure validation test plan</strong>
|
||||
<div class="helper">Evidence match · useful support artifact with no direct legal force</div>
|
||||
<div class="score-row">
|
||||
<span class="pill">Evidence</span>
|
||||
<span class="status ok">Supporting</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section class="card">
|
||||
<div class="section-head">
|
||||
<h2>Document paragraph under review</h2>
|
||||
<span class="helper">Chunk 148 · supplier safety narrative</span>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p><strong>Source text</strong></p>
|
||||
<p>The roof support structure is designed to satisfy national crush-resistance requirements, and the module enclosure preserves occupant safety under static load conditions. Validation results are available in the body engineering report and are considered aligned with the current certification baseline.</p>
|
||||
<p><strong>Analysis focus</strong></p>
|
||||
<p>The system flags that the narrative claims compliance but omits the explicit load threshold. The phrase <mark>satisfy national crush-resistance requirements</mark> should be tied to a stated requirement derived from <mark>GB 26112-2010 §4.2</mark> to avoid unsupported compliance language.</p>
|
||||
</div>
|
||||
|
||||
<div class="section-head" style="margin-top:18px;">
|
||||
<h2>Analysis stages</h2>
|
||||
<span class="helper">Streaming reasoning workflow</span>
|
||||
</div>
|
||||
<div class="stage-list">
|
||||
<div class="stage">
|
||||
<strong>1. Clause retrieval</strong>
|
||||
<div class="helper">Dense retrieval found 10 nearby standards; 3 were promoted after category scoring.</div>
|
||||
<div class="progress"><span style="width:100%"></span></div>
|
||||
</div>
|
||||
<div class="stage">
|
||||
<strong>2. Requirement extraction</strong>
|
||||
<div class="helper">Relevant mandatory threshold identified and isolated for reviewer verification.</div>
|
||||
<div class="progress"><span style="width:100%"></span></div>
|
||||
</div>
|
||||
<div class="stage">
|
||||
<strong>3. Gap analysis</strong>
|
||||
<div class="helper">Document contains claim language but no direct numeric evidence or linked report identifier.</div>
|
||||
<div class="progress"><span style="width:88%"></span></div>
|
||||
</div>
|
||||
<div class="stage">
|
||||
<strong>4. Recommendation synthesis</strong>
|
||||
<div class="helper">Drafting precise remediation text for reviewer approval.</div>
|
||||
<div class="progress"><span style="width:62%"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="card">
|
||||
<div class="section-head">
|
||||
<h2>Findings and conclusion</h2>
|
||||
<span class="helper">Reviewer-ready output</span>
|
||||
</div>
|
||||
<div class="finding-list">
|
||||
<div class="finding">
|
||||
<strong>Unsupported compliance statement</strong>
|
||||
<p class="helper">The paragraph asserts conformity without quoting the actual threshold or citing a specific verification artifact.</p>
|
||||
<span class="status risk" style="margin-top:10px;">Needs revision</span>
|
||||
</div>
|
||||
<div class="finding">
|
||||
<strong>Evidence linkage incomplete</strong>
|
||||
<p class="helper">Body engineering report exists, but the report ID and page range are missing from the narrative used in the dossier.</p>
|
||||
<span class="status warn" style="margin-top:10px;">Evidence gap</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conclusion">
|
||||
<div class="conclusion-box">
|
||||
<strong>Recommended replacement text</strong>
|
||||
<p class="helper">"The roof support structure was validated in accordance with GB 26112-2010 §4.2; see the linked body engineering report for the supporting test result and formal threshold statement."</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="action-item">
|
||||
<strong>Next action</strong>
|
||||
<p class="helper">Assign to Body Structure team for wording update, then rerun the same paragraph through clause verification.</p>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<strong>Escalation</strong>
|
||||
<p class="helper">If report ID cannot be linked within 24 hours, downgrade dossier status and notify homologation lead.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="footer">
|
||||
<span>T-Systems Regulation Hub</span>
|
||||
<div class="footer-status"><span class="footer-dot" aria-hidden="true"></span><span>Online</span></div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script src="ui-preferences.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
808
Prototype/cc29bcb0-df2d-4d50-9428-7caa406ecb29/dashboard.html
Normal file
808
Prototype/cc29bcb0-df2d-4d50-9428-7caa406ecb29/dashboard.html
Normal file
@@ -0,0 +1,808 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>AI + Compliance Hub - Dashboard</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #fafafa;
|
||||
--surface: #ffffff;
|
||||
--surface-warm: var(--surface);
|
||||
--fg: #111111;
|
||||
--fg-2: var(--fg);
|
||||
--muted: #6b6b6b;
|
||||
--meta: var(--muted);
|
||||
--border: #e5e5e5;
|
||||
--border-soft: var(--border);
|
||||
--primary: #e20074;
|
||||
--accent: var(--primary);
|
||||
--accent-on: #ffffff;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), black 8%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 14%);
|
||||
--success: #17a34a;
|
||||
--warn: #d97706;
|
||||
--danger: #dc2626;
|
||||
--font-display: "TeleNeoWeb-Bold", "TeleNeoWeb-Medium", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-body: "TeleNeoWeb-Regular", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
||||
--text-xs: 11px;
|
||||
--text-sm: 13px;
|
||||
--text-base: 15px;
|
||||
--text-lg: 18px;
|
||||
--text-xl: 22px;
|
||||
--text-2xl: 28px;
|
||||
--leading-body: 1.55;
|
||||
--leading-tight: 1.2;
|
||||
--tracking-display: -0.01em;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 14px;
|
||||
--radius-pill: 9999px;
|
||||
--elev-flat: none;
|
||||
--elev-ring: 0 0 0 1px var(--border);
|
||||
--elev-raised: 0 2px 8px color-mix(in oklab, var(--fg), transparent 92%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 70%);
|
||||
--motion-fast: 140ms;
|
||||
--motion-base: 200ms;
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--sidebar-w: 240px;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--fg: #f2f4f8;
|
||||
--fg-2: #e2e6ef;
|
||||
--muted: #9aa2b0;
|
||||
--border: #252830;
|
||||
--border-soft: #1e2028;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 72%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #0f1014; --surface: #17181d; --fg: #f2f4f8; --fg-2: #e2e6ef;
|
||||
--muted: #9aa2b0; --border: #252830; --border-soft: #1e2028;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e; --warn: #facc15; --danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 72%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
:root[data-theme="light"] { color-scheme: light; }
|
||||
|
||||
* { box-sizing: border-box; margin: 0; }
|
||||
html { -webkit-text-size-adjust: 100%; height: 100%; }
|
||||
body {
|
||||
height: 100%;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-body);
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
h1,h2,h3,h4 {
|
||||
font-family: var(--font-display);
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-display);
|
||||
text-wrap: balance;
|
||||
}
|
||||
p { text-wrap: pretty; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
button, input, select, textarea { font: inherit; }
|
||||
|
||||
/* ── App chrome ─────────────────────────────── */
|
||||
.app-shell {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-w) 1fr;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ── Sidebar ────────────────────────────────── */
|
||||
.sidebar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--surface);
|
||||
border-right: 1px solid var(--border);
|
||||
padding: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
.sidebar-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
height: 56px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.brand-logo {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
background: var(--accent);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.brand-logo svg { color: #fff; }
|
||||
.sidebar-brand-name {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: var(--fg);
|
||||
}
|
||||
.sidebar-brand-sub {
|
||||
font-size: 10px;
|
||||
color: var(--muted);
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
flex: 1;
|
||||
padding: 12px 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.nav-group {
|
||||
padding: 0 8px 4px;
|
||||
}
|
||||
.nav-group + .nav-group {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.nav-group-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--muted);
|
||||
padding: 0 8px 6px;
|
||||
display: block;
|
||||
}
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
height: 36px;
|
||||
padding: 0 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
transition: background var(--motion-fast) var(--ease-standard),
|
||||
color var(--motion-fast) var(--ease-standard);
|
||||
position: relative;
|
||||
}
|
||||
.nav-item:hover {
|
||||
background: color-mix(in oklab, var(--fg), transparent 94%);
|
||||
color: var(--fg);
|
||||
text-decoration: none;
|
||||
}
|
||||
.nav-item.active {
|
||||
background: color-mix(in oklab, var(--accent), transparent 90%);
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
.nav-item.active::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 6px;
|
||||
bottom: 6px;
|
||||
width: 3px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
background: var(--accent);
|
||||
}
|
||||
.nav-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.nav-item.active .nav-icon { opacity: 1; }
|
||||
.nav-badge {
|
||||
margin-left: auto;
|
||||
min-width: 20px;
|
||||
height: 18px;
|
||||
padding: 0 6px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: color-mix(in oklab, var(--accent), transparent 84%);
|
||||
color: var(--accent);
|
||||
font-size: 10px;
|
||||
font-family: var(--font-mono);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 10px 8px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.sidebar-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
.sidebar-user:hover { background: color-mix(in oklab, var(--fg), transparent 94%); }
|
||||
.avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar-user-info { min-width: 0; }
|
||||
.sidebar-user-name {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 600;
|
||||
color: var(--fg);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.sidebar-user-role {
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.sidebar-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
height: 34px;
|
||||
padding: 0 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
transition: background var(--motion-fast), color var(--motion-fast);
|
||||
}
|
||||
.sidebar-action:hover {
|
||||
background: color-mix(in oklab, var(--fg), transparent 94%);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
/* ── Content area ─────────────────────────── */
|
||||
.content-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.content-topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 56px;
|
||||
padding: 0 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: color-mix(in oklab, var(--bg), transparent 4%);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.topbar-title {
|
||||
font-weight: 600;
|
||||
font-size: var(--text-base);
|
||||
color: var(--fg);
|
||||
flex: 1;
|
||||
}
|
||||
.search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface);
|
||||
padding: 0 12px;
|
||||
height: 34px;
|
||||
width: 260px;
|
||||
}
|
||||
.search input {
|
||||
border: 0; outline: none; background: transparent;
|
||||
width: 100%; color: var(--fg); font-size: var(--text-sm);
|
||||
}
|
||||
.search input::placeholder { color: var(--muted); }
|
||||
|
||||
.btn {
|
||||
height: 34px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0 14px;
|
||||
background: var(--surface);
|
||||
color: var(--fg);
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
transition: background var(--motion-fast), border-color var(--motion-fast);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn:hover { border-color: var(--fg); }
|
||||
.btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
|
||||
.btn-primary {
|
||||
background: var(--accent); border-color: var(--accent); color: var(--accent-on);
|
||||
}
|
||||
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
||||
|
||||
/* ── Page ─────────────────────────────────── */
|
||||
.page {
|
||||
padding: 24px;
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
flex: 1;
|
||||
}
|
||||
.page-head {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
.eyebrow {
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.page-head h1 { font-size: clamp(22px, 3vw, 32px); }
|
||||
.page-head p { color: var(--muted); max-width: 68ch; font-size: var(--text-sm); }
|
||||
|
||||
/* ── Cards ────────────────────────────────── */
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 18px 20px;
|
||||
}
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0,1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
.stat-card .label {
|
||||
color: var(--muted); font-size: var(--text-xs);
|
||||
font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.08em;
|
||||
}
|
||||
.stat-card .value {
|
||||
margin-top: 12px; font-size: 32px; line-height: 1;
|
||||
font-family: var(--font-display); font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.stat-card .sub {
|
||||
margin-top: 10px; color: var(--muted); font-size: var(--text-xs); line-height: 1.5;
|
||||
}
|
||||
.panel-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.4fr 0.9fr;
|
||||
gap: 20px;
|
||||
}
|
||||
.stack { display: grid; gap: 20px; }
|
||||
.section-head {
|
||||
display: flex; align-items: center;
|
||||
justify-content: space-between; gap: 12px; margin-bottom: 14px;
|
||||
}
|
||||
.section-head h2 { font-size: var(--text-lg); }
|
||||
.ghost-link {
|
||||
color: var(--muted); font-size: var(--text-sm);
|
||||
border-radius: var(--radius-sm); padding: 4px 0;
|
||||
transition: color var(--motion-fast);
|
||||
}
|
||||
.ghost-link:hover { color: var(--fg); text-decoration: none; }
|
||||
|
||||
/* ── Data rows ─────────────────────────────── */
|
||||
.task-list, .program-list, .event-list { display: grid; gap: 10px; }
|
||||
.task-row, .program-row, .event-row {
|
||||
display: grid; gap: 10px;
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
padding: 12px 14px;
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 20%);
|
||||
}
|
||||
.task-row { grid-template-columns: 1.5fr 0.9fr 0.9fr 0.7fr; align-items: center; }
|
||||
.program-row { grid-template-columns: 1fr auto; align-items: start; }
|
||||
.event-row { grid-template-columns: 90px 1fr; align-items: start; }
|
||||
|
||||
.kpi-strip { display: grid; grid-template-columns: repeat(3,1fr); gap: 10px; margin-top: 14px; }
|
||||
.kpi {
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
padding: 12px; background: color-mix(in oklab, var(--surface), var(--bg) 18%);
|
||||
}
|
||||
.kpi strong { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 18px; }
|
||||
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
|
||||
|
||||
.status {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
width: fit-content; padding: 3px 9px; border-radius: var(--radius-pill);
|
||||
font-size: var(--text-xs); border: 1px solid var(--border); font-family: var(--font-mono);
|
||||
}
|
||||
.status::before {
|
||||
content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor;
|
||||
}
|
||||
.status.ok { color: var(--success); }
|
||||
.status.warn { color: var(--warn); }
|
||||
.status.risk { color: var(--danger); }
|
||||
|
||||
.meter {
|
||||
height: 6px; border-radius: 999px;
|
||||
background: color-mix(in oklab, var(--fg), transparent 94%);
|
||||
overflow: hidden; margin-top: 10px;
|
||||
}
|
||||
.meter > span {
|
||||
display: block; height: 100%;
|
||||
background: color-mix(in oklab, var(--accent), white 30%);
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.pill {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
height: 20px; padding: 0 7px; border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border); color: var(--muted);
|
||||
font-size: var(--text-xs); font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.footer-note { color: var(--muted); font-size: var(--text-xs); line-height: 1.5; }
|
||||
|
||||
/* ── Footer ───────────────────────────────── */
|
||||
.footer {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 16px; min-height: 34px; padding: 0 24px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted); font-size: var(--text-xs);
|
||||
font-family: var(--font-mono); letter-spacing: 0.1em; text-transform: uppercase;
|
||||
background: color-mix(in oklab, var(--bg), var(--surface) 12%);
|
||||
}
|
||||
.footer-status { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.footer-dot {
|
||||
width: 7px; height: 7px; border-radius: 50%; background: #19d3a2;
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, #19d3a2, transparent 82%);
|
||||
}
|
||||
|
||||
/* ── Responsive ───────────────────────────── */
|
||||
@media (max-width: 1180px) {
|
||||
.stats-grid, .panel-grid, .kpi-strip { grid-template-columns: 1fr 1fr; }
|
||||
.task-row { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
:root { --sidebar-w: 200px; }
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.app-shell { grid-template-columns: 1fr; }
|
||||
.sidebar { display: none; }
|
||||
.stats-grid, .panel-grid, .kpi-strip { grid-template-columns: 1fr; }
|
||||
.page-head { flex-direction: column; align-items: start; }
|
||||
.event-row, .program-row { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page="dashboard">
|
||||
<div class="app-shell">
|
||||
|
||||
<!-- ── Sidebar ── -->
|
||||
<aside class="sidebar" aria-label="Primary navigation">
|
||||
<div class="sidebar-brand">
|
||||
<div class="brand-logo">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M2 3.5h12v1.5H2zm5.25 1.5h1.5v8h-1.5zm-3 2h2.25v1.5H4.25zm5.25 0h2.25v1.5H9.5zm-3 3h2.5v1.5H6.5z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sidebar-brand-name">T-Systems</div>
|
||||
<div class="sidebar-brand-sub">Regulation Hub</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav" aria-label="Primary">
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">主导航</span>
|
||||
<a class="nav-item" href="index.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M2 2h5v5H2zm7 0h5v5H9zM2 9h5v5H2zm7 0h5v5H9z" fill="currentColor" opacity=".7"/>
|
||||
</svg>
|
||||
概览
|
||||
</a>
|
||||
<a class="nav-item active" href="dashboard.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M1.5 2.5h13v1H1.5zm0 3h13v1H1.5zm0 3h8v1h-8zm0 3h6v1h-6zM12 8.5a3.5 3.5 0 110 7 3.5 3.5 0 010-7zm0 1a2.5 2.5 0 100 5 2.5 2.5 0 000-5zm.5 1v2h1.5v1H11v-3h1.5z" fill="currentColor"/>
|
||||
</svg>
|
||||
系统状态
|
||||
<span class="nav-badge">3</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">工作台</span>
|
||||
<a class="nav-item" href="document-management.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M3 1h7l3 3v11H3V1zm1 1v12h8V5h-3V2H4zm5 .5V4h1.5L9 1.5zM6 7h4v1H6zm0 2h4v1H6zm0 2h3v1H6z" fill="currentColor"/>
|
||||
</svg>
|
||||
文档管理
|
||||
</a>
|
||||
<a class="nav-item" href="compliance-analysis.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M8 1l7 3-1 6a7 7 0 01-6 5A7 7 0 011 10L0 4l8-3zm0 1.2L1.3 4.8l.8 5.1A6 6 0 008 14.8a6 6 0 005.9-4.9l.8-5.1L8 2.2zM7.5 5h1v4.5l-1 .5V5zm0 5.5h1v1h-1v-1z" fill="currentColor"/>
|
||||
</svg>
|
||||
合规分析
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">对话</span>
|
||||
<a class="nav-item" href="regulation-chat.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M2 2h12a1 1 0 011 1v8a1 1 0 01-1 1H5l-3 2.5V3a1 1 0 011-1zm0 1v9.5L4.5 11H14V3H2zm2 2h8v1H4zm0 2h6v1H4z" fill="currentColor"/>
|
||||
</svg>
|
||||
法规对话
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="avatar">TS</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">T-Systems User</div>
|
||||
<div class="sidebar-user-role">Compliance Analyst</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="sidebar-action od-theme-toggle" type="button" data-od-theme aria-label="Toggle theme">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M8 3a5 5 0 100 10A5 5 0 008 3zM2 8a6 6 0 1112 0A6 6 0 012 8z" fill="currentColor"/>
|
||||
</svg>
|
||||
主题
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ── Content area ── -->
|
||||
<div class="content-area">
|
||||
<header class="content-topbar">
|
||||
<div class="topbar-title">系统状态</div>
|
||||
<div class="search">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M6.5 1a5.5 5.5 0 014.23 9.02l3.62 3.62-.7.71-3.63-3.63A5.5 5.5 0 116.5 1zm0 1a4.5 4.5 0 100 9 4.5 4.5 0 000-9z" fill="currentColor" opacity=".5"/>
|
||||
</svg>
|
||||
<input type="search" placeholder="Search regulations, documents…" aria-label="Search" />
|
||||
</div>
|
||||
<button class="btn">Export status</button>
|
||||
<a class="btn btn-primary" href="upload-modal.html">New upload</a>
|
||||
</header>
|
||||
|
||||
<main class="page">
|
||||
<section class="page-head" data-od-id="dashboard-head">
|
||||
<div>
|
||||
<div class="eyebrow">Operations dashboard</div>
|
||||
<h1>Track ingestion health and active compliance work.</h1>
|
||||
<p style="margin-top:6px;">Designed for the team lead who needs to know which documents are blocked, which standards changed, and which program teams need intervention today.</p>
|
||||
</div>
|
||||
<span class="pill">v1.0.0</span>
|
||||
</section>
|
||||
|
||||
<section class="stats-grid" data-od-id="dashboard-stats">
|
||||
<article class="card stat-card">
|
||||
<div class="label">Documents total</div>
|
||||
<div class="value mono">--</div>
|
||||
<div class="sub">Live document totals populate here from the operations snapshot.</div>
|
||||
</article>
|
||||
<article class="card stat-card">
|
||||
<div class="label">Vector chunks</div>
|
||||
<div class="value mono">--</div>
|
||||
<div class="sub">Dense collection `regulations_dense_1024_v2` currently serving retrieval</div>
|
||||
</article>
|
||||
<article class="card stat-card">
|
||||
<div class="label">Analysis backlog</div>
|
||||
<div class="value mono">--</div>
|
||||
<div class="sub">Open investigations, reviewer backlog, and blocked runs roll up here.</div>
|
||||
</article>
|
||||
<article class="card stat-card">
|
||||
<div class="label">Average ingest time</div>
|
||||
<div class="value mono">--</div>
|
||||
<div class="sub">Aliyun parse plus embedding latency trends appear here once runs are active.</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="panel-grid" data-od-id="dashboard-main">
|
||||
<div class="stack">
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Workflow queue</h2>
|
||||
<a class="ghost-link" href="document-management.html">Open documents →</a>
|
||||
</div>
|
||||
<div class="task-list">
|
||||
<div class="task-row">
|
||||
<div>
|
||||
<strong>GB/T 31484-2015 battery density revision</strong>
|
||||
<div class="footer-note">Uploaded by EV Safety Team · version 2026-04 addendum</div>
|
||||
</div>
|
||||
<span class="status warn">Embedding</span>
|
||||
<span class="mono" style="font-size:12px;">chunk build active</span>
|
||||
<a class="ghost-link" href="document-detail.html">Inspect</a>
|
||||
</div>
|
||||
<div class="task-row">
|
||||
<div>
|
||||
<strong>UNECE R155 annex interpretation note</strong>
|
||||
<div class="footer-note">Parser artifacts ready · waiting for compliance analyst assignment</div>
|
||||
</div>
|
||||
<span class="status ok">Ready</span>
|
||||
<span class="mono" style="font-size:12px;">19 clauses linked</span>
|
||||
<a class="ghost-link" href="compliance-analysis.html">Analyze</a>
|
||||
</div>
|
||||
<div class="task-row">
|
||||
<div>
|
||||
<strong>GB 26112-2010 roof strength scan</strong>
|
||||
<div class="footer-note">OCR confidence dropped below threshold on 6 pages</div>
|
||||
</div>
|
||||
<span class="status risk">Failed</span>
|
||||
<span class="mono" style="font-size:12px;">Retry #2</span>
|
||||
<a class="ghost-link" href="document-management.html">Resolve</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Active compliance programs</h2>
|
||||
<a class="ghost-link" href="compliance-analysis.html">Review findings →</a>
|
||||
</div>
|
||||
<div class="program-list">
|
||||
<div class="program-row">
|
||||
<div>
|
||||
<strong>Intelligent cockpit homologation</strong>
|
||||
<p class="footer-note">42 related standards across driver monitoring, EMC, and child safety. Four findings still open for MY27 platform.</p>
|
||||
</div>
|
||||
<span class="status risk">High risk</span>
|
||||
</div>
|
||||
<div class="program-row">
|
||||
<div>
|
||||
<strong>Battery swap certification dossier</strong>
|
||||
<p class="footer-note">Clause mapping complete. Thermal event test evidence package still awaiting supplier document refresh.</p>
|
||||
</div>
|
||||
<span class="status warn">Pending</span>
|
||||
</div>
|
||||
<div class="program-row">
|
||||
<div>
|
||||
<strong>Connected fleet cybersecurity</strong>
|
||||
<p class="footer-note">RAG checks aligned with UNECE R155. Chat follow-up requested on remote key rotation obligations.</p>
|
||||
</div>
|
||||
<span class="status ok">On track</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-strip">
|
||||
<div class="kpi">
|
||||
<div class="footer-note">Retrieval hit rate</div>
|
||||
<strong>87%</strong>
|
||||
<div class="meter"><span style="width:87%"></span></div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="footer-note">Evidence coverage</div>
|
||||
<strong>72%</strong>
|
||||
<div class="meter"><span style="width:72%"></span></div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="footer-note">Reviewer SLA</div>
|
||||
<strong>18h</strong>
|
||||
<div class="meter"><span style="width:64%"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="stack">
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>System health</h2>
|
||||
<a class="ghost-link" href="#">Refresh</a>
|
||||
</div>
|
||||
<div class="task-list">
|
||||
<div class="task-row" style="grid-template-columns: 1fr auto;">
|
||||
<div>
|
||||
<strong>Aliyun parser backend</strong>
|
||||
<div class="footer-note">Poll interval 5 s · timeout 900 s</div>
|
||||
</div>
|
||||
<span class="status warn">Queue depth 7</span>
|
||||
</div>
|
||||
<div class="task-row" style="grid-template-columns: 1fr auto;">
|
||||
<div>
|
||||
<strong>Embedding model</strong>
|
||||
<div class="footer-note">text-embedding-v3 · dimension 1024</div>
|
||||
</div>
|
||||
<span class="status ok">Healthy</span>
|
||||
</div>
|
||||
<div class="task-row" style="grid-template-columns: 1fr auto;">
|
||||
<div>
|
||||
<strong>Vector store</strong>
|
||||
<div class="footer-note">Milvus `regulations_dense_1024_v2`</div>
|
||||
</div>
|
||||
<span class="status ok">Serving</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Regulatory watch</h2>
|
||||
<a class="ghost-link" href="regulation-chat.html">Ask chat →</a>
|
||||
</div>
|
||||
<div class="event-list">
|
||||
<div class="event-row">
|
||||
<span class="mono footer-note">Recent</span>
|
||||
<div>
|
||||
<strong>GB 38031 thermal propagation draft updated</strong>
|
||||
<p class="footer-note">Potential impact on current battery enclosure narrative. Evidence gap flagged in two supplier submissions.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-row">
|
||||
<span class="mono footer-note">Recent</span>
|
||||
<div>
|
||||
<strong>UNECE R155 Q&A added note on incident response logs</strong>
|
||||
<p class="footer-note">Connected fleet program must confirm retention windows and ownership controls.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-row">
|
||||
<span class="mono footer-note">Recent</span>
|
||||
<div>
|
||||
<strong>GB/T 18487 charging interface interpretation circulated</strong>
|
||||
<p class="footer-note">No blocker yet, but three documents should be re-run against the new clause wording.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<span>T-Systems Regulation Hub</span>
|
||||
<div class="footer-status">
|
||||
<span class="footer-dot" aria-hidden="true"></span>
|
||||
<span>Online</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script src="ui-preferences.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,623 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>AI + Compliance Hub - Document Detail</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #fafafa;
|
||||
--surface: #ffffff;
|
||||
--surface-warm: var(--surface);
|
||||
--fg: #111111;
|
||||
--fg-2: var(--fg);
|
||||
--muted: #6b6b6b;
|
||||
--meta: var(--muted);
|
||||
--border: #e5e5e5;
|
||||
--border-soft: var(--border);
|
||||
--primary: #e20074;
|
||||
--accent: var(--primary);
|
||||
--accent-on: #ffffff;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), black 8%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 14%);
|
||||
--success: #17a34a;
|
||||
--warn: #eab308;
|
||||
--danger: #dc2626;
|
||||
--font-display: "TeleNeoWeb-Bold", "TeleNeoWeb-Medium", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-body: "TeleNeoWeb-Regular", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
||||
--text-xs: 12px;
|
||||
--text-sm: 14px;
|
||||
--text-base: 16px;
|
||||
--text-lg: 20px;
|
||||
--text-xl: 24px;
|
||||
--text-2xl: 32px;
|
||||
--text-3xl: 48px;
|
||||
--text-4xl: 64px;
|
||||
--leading-body: 1.5;
|
||||
--leading-tight: 1.2;
|
||||
--tracking-display: -0.01em;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-12: 48px;
|
||||
--space-20: 80px;
|
||||
--section-y-desktop: 80px;
|
||||
--section-y-tablet: 48px;
|
||||
--section-y-phone: 32px;
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-pill: 9999px;
|
||||
--elev-flat: none;
|
||||
--elev-ring: 0 0 0 1px var(--border);
|
||||
--elev-raised: 0 2px 8px color-mix(in oklab, var(--fg), transparent 92%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 70%);
|
||||
--motion-fast: 150ms;
|
||||
--motion-base: 200ms;
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--container-max: 1440px;
|
||||
--container-gutter-desktop: 24px;
|
||||
--container-gutter-tablet: 16px;
|
||||
--container-gutter-phone: 12px;
|
||||
--sidebar-w: 240px;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-body);
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
margin: 0;
|
||||
font-family: var(--font-display);
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-display);
|
||||
}
|
||||
p { margin: 0; text-wrap: pretty; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
a:hover { color: var(--fg); text-decoration: underline; }
|
||||
button { font: inherit; }
|
||||
|
||||
/* ── Sidebar shell ── */
|
||||
.app-shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
|
||||
.sidebar { position: sticky; top: 0; height: 100vh; overflow-y: auto; display: flex; flex-direction: column; background: var(--surface); border-right: 1px solid var(--border); z-index: 10; }
|
||||
.sidebar-brand { display: flex; align-items: center; gap: 10px; height: 56px; padding: 0 16px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
||||
.brand-logo { width: 26px; height: 26px; background: var(--accent); border-radius: 6px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.brand-logo svg { color: #fff; }
|
||||
.sidebar-brand-name { font-family: var(--font-display); font-size: 13px; font-weight: 700; line-height: 1.2; }
|
||||
.sidebar-brand-sub { font-size: 10px; color: var(--muted); font-family: var(--font-mono); letter-spacing: 0.04em; }
|
||||
.sidebar-nav { flex: 1; padding: 12px 0; overflow-y: auto; }
|
||||
.nav-group { padding: 0 8px 4px; }
|
||||
.nav-group + .nav-group { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
|
||||
.nav-group-label { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); padding: 0 8px 6px; display: block; }
|
||||
.nav-item { display: flex; align-items: center; gap: 10px; height: 36px; padding: 0 8px; border-radius: 6px; color: var(--muted); font-size: 13px; cursor: pointer; transition: background 140ms, color 140ms; position: relative; }
|
||||
.nav-item:hover { background: color-mix(in oklab, var(--fg), transparent 94%); color: var(--fg); text-decoration: none; }
|
||||
.nav-item.active { background: color-mix(in oklab, var(--accent), transparent 90%); color: var(--accent); font-weight: 600; }
|
||||
.nav-item.active::before { content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px; border-radius: 0 3px 3px 0; background: var(--accent); }
|
||||
.nav-icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.7; }
|
||||
.nav-item.active .nav-icon { opacity: 1; }
|
||||
.sidebar-footer { border-top: 1px solid var(--border); padding: 10px 8px; flex-shrink: 0; display: flex; flex-direction: column; gap: 4px; }
|
||||
.sidebar-user { display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: 6px; cursor: pointer; }
|
||||
.sidebar-user:hover { background: color-mix(in oklab, var(--fg), transparent 94%); }
|
||||
.avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); color: #fff; font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.sidebar-user-info { min-width: 0; }
|
||||
.sidebar-user-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.sidebar-user-role { font-size: 11px; color: var(--muted); font-family: var(--font-mono); }
|
||||
.sidebar-action { display: flex; align-items: center; gap: 10px; height: 34px; padding: 0 8px; border-radius: 6px; color: var(--muted); font-size: 13px; cursor: pointer; border: none; background: transparent; width: 100%; text-align: left; transition: background 140ms, color 140ms; }
|
||||
.sidebar-action:hover { background: color-mix(in oklab, var(--fg), transparent 94%); color: var(--fg); }
|
||||
.content-area { display: flex; flex-direction: column; min-width: 0; min-height: 100vh; }
|
||||
.content-topbar { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 12px; height: 56px; padding: 0 24px; border-bottom: 1px solid var(--border); background: color-mix(in oklab, var(--bg), transparent 4%); backdrop-filter: blur(10px); }
|
||||
.topbar-title { font-weight: 600; font-size: 15px; color: var(--fg); flex: 1; }
|
||||
.footer-dot { width: 7px; height: 7px; border-radius: 50%; background: #19d3a2; box-shadow: 0 0 0 3px color-mix(in oklab, #19d3a2, transparent 82%); }
|
||||
.footer-status { display: inline-flex; align-items: center; gap: 8px; }
|
||||
@media (max-width: 700px) { .app-shell { grid-template-columns: 1fr; } .sidebar { display: none; } }
|
||||
|
||||
/* ── Page-specific styles ── */
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: 36px;
|
||||
padding: 0 24px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
background: color-mix(in oklab, var(--bg), var(--surface) 12%);
|
||||
}
|
||||
.page {
|
||||
max-width: 1440px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
.topline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
min-height: 56px;
|
||||
}
|
||||
.back { color: var(--muted); font-size: var(--text-sm); }
|
||||
.eyebrow {
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.hero {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
}
|
||||
.hero h1 { font-size: clamp(30px, 4vw, 46px); }
|
||||
.hero p { max-width: 72ch; color: var(--muted); }
|
||||
.meta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.chip,
|
||||
.status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.status::before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: currentColor;
|
||||
}
|
||||
.status.ok { color: var(--success); }
|
||||
.status.warn { color: color-mix(in oklab, var(--warn), black 24%); }
|
||||
.status.risk { color: var(--danger); }
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 0.9fr 1.4fr;
|
||||
gap: 20px;
|
||||
}
|
||||
.stack {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
.card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface);
|
||||
padding: 18px;
|
||||
}
|
||||
.section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.section-head h2 { font-size: var(--text-xl); }
|
||||
.helper {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.mono {
|
||||
font-family: var(--font-mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.timeline {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
.step {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 14px 14px 14px 16px;
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 14%);
|
||||
position: relative;
|
||||
}
|
||||
.step::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
border-radius: 3px 0 0 3px;
|
||||
background: var(--border);
|
||||
}
|
||||
.step.active::before { background: var(--accent); }
|
||||
.step.done::before { background: var(--success); }
|
||||
.step.fail::before { background: var(--danger); }
|
||||
.progress {
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in oklab, var(--fg), transparent 95%);
|
||||
overflow: hidden;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.progress > span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: color-mix(in oklab, var(--accent), white 28%);
|
||||
border-radius: inherit;
|
||||
}
|
||||
.artifact-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.artifact {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 14px;
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 16%);
|
||||
}
|
||||
.artifact strong { display: block; margin-bottom: 6px; }
|
||||
.table {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 0.9fr 0.9fr 0.9fr;
|
||||
gap: 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 12px 14px;
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 12%);
|
||||
}
|
||||
.log {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.log-item {
|
||||
display: grid;
|
||||
grid-template-columns: 90px 1fr;
|
||||
gap: 12px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.grid,
|
||||
.artifact-grid,
|
||||
.table-row { grid-template-columns: 1fr; }
|
||||
.hero { flex-direction: column; align-items: start; }
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.page { padding: 12px; }
|
||||
.topline { align-items: start; flex-direction: column; }
|
||||
.log-item { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page="detail">
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar" aria-label="Primary navigation">
|
||||
<div class="sidebar-brand">
|
||||
<div class="brand-logo">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M2 3.5h12v1.5H2zm5.25 1.5h1.5v8h-1.5zm-3 2h2.25v1.5H4.25zm5.25 0h2.25v1.5H9.5zm-3 3h2.5v1.5H6.5z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sidebar-brand-name">T-Systems</div>
|
||||
<div class="sidebar-brand-sub">Regulation Hub</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="sidebar-nav" aria-label="Primary">
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">主导航</span>
|
||||
<a class="nav-item" href="index.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M2 2h5v5H2zm7 0h5v5H9zM2 9h5v5H2zm7 0h5v5H9z" fill="currentColor" opacity=".7"/></svg>
|
||||
概览
|
||||
</a>
|
||||
<a class="nav-item" href="dashboard.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M1.5 2.5h13v1H1.5zm0 3h13v1H1.5zm0 3h8v1h-8zm0 3h6v1h-6z" fill="currentColor"/></svg>
|
||||
系统状态
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">工作台</span>
|
||||
<a class="nav-item active" href="document-management.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M3 1h7l3 3v11H3V1zm1 1v12h8V5h-3V2H4zm5 .5V4h1.5L9 1.5zM6 7h4v1H6zm0 2h4v1H6zm0 2h3v1H6z" fill="currentColor"/></svg>
|
||||
文档管理
|
||||
</a>
|
||||
<a class="nav-item" href="compliance-analysis.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M8 1l7 3-1 6a7 7 0 01-6 5A7 7 0 011 10L0 4l8-3zm0 1.2L1.3 4.8l.8 5.1A6 6 0 008 14.8a6 6 0 005.9-4.9l.8-5.1L8 2.2zM7.5 5h1v4.5l-1 .5V5zm0 5.5h1v1h-1v-1z" fill="currentColor"/></svg>
|
||||
合规分析
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">对话</span>
|
||||
<a class="nav-item" href="regulation-chat.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M2 2h12a1 1 0 011 1v8a1 1 0 01-1 1H5l-3 2.5V3a1 1 0 011-1zm0 1v9.5L4.5 11H14V3H2zm2 2h8v1H4zm0 2h6v1H4z" fill="currentColor"/></svg>
|
||||
法规对话
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="avatar">TS</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">T-Systems User</div>
|
||||
<div class="sidebar-user-role">Compliance Analyst</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="sidebar-action od-theme-toggle" type="button" data-od-theme aria-label="Toggle theme">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M8 3a5 5 0 100 10A5 5 0 008 3zM2 8a6 6 0 1112 0A6 6 0 012 8z" fill="currentColor"/></svg>
|
||||
主题
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="content-area">
|
||||
<header class="content-topbar">
|
||||
<span class="topbar-title">文档解析详情</span>
|
||||
</header>
|
||||
|
||||
<main class="page">
|
||||
<section class="topline" data-od-id="detail-top">
|
||||
<a class="back" href="document-management.html">← Back to document management</a>
|
||||
<div class="meta-row">
|
||||
<span class="chip">Battery safety</span>
|
||||
<span class="chip mono">doc_id: GBT-31484-2015-r2</span>
|
||||
<span class="status warn">Embedding in progress</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="hero" data-od-id="detail-hero">
|
||||
<div>
|
||||
<div class="eyebrow">Document detail</div>
|
||||
<h1>Trace parse artifacts from raw upload to vector index.</h1>
|
||||
<p>This view is for operators diagnosing why one document is delayed or degraded. It surfaces parser settings, semantic structure, chunk generation, and Milvus insertion as separate observable stages.</p>
|
||||
</div>
|
||||
<div class="card" style="min-width:280px;">
|
||||
<div class="helper">Current run</div>
|
||||
<h2 style="font-size:24px; margin-top:8px;">Battery density addendum review</h2>
|
||||
<div class="helper" style="margin-top:8px;">Uploaded 09:14 by Battery Safety Team · parser backend `aliyun`</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid" data-od-id="detail-main">
|
||||
<div class="stack">
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Pipeline progression</h2>
|
||||
<span class="helper">Live state</span>
|
||||
</div>
|
||||
<div class="timeline">
|
||||
<div class="step done">
|
||||
<strong>1. Object storage ingestion</strong>
|
||||
<div class="helper">Stored in bucket `upload-files` with artifact prefix `artifacts`</div>
|
||||
<div class="progress"><span style="width:100%"></span></div>
|
||||
</div>
|
||||
<div class="step done">
|
||||
<strong>2. Aliyun parse layout extraction</strong>
|
||||
<div class="helper">Parsed pages, recovered tables, and OCR confidence summarize here once the run completes.</div>
|
||||
<div class="progress"><span style="width:100%"></span></div>
|
||||
</div>
|
||||
<div class="step done">
|
||||
<strong>3. Semantic blocks</strong>
|
||||
<div class="helper">Semantic block persistence is tracked here after parse artifact storage completes.</div>
|
||||
<div class="progress"><span style="width:100%"></span></div>
|
||||
</div>
|
||||
<div class="step active">
|
||||
<strong>4. Vector chunk build</strong>
|
||||
<div class="helper">Using overlap chunking with header-prefixed embedding text</div>
|
||||
<div class="progress"><span style="width:76%"></span></div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<strong>5. Embedding generation</strong>
|
||||
<div class="helper">Target model `text-embedding-v3` · dimension 1024</div>
|
||||
<div class="progress"><span style="width:38%"></span></div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<strong>6. Milvus insertion</strong>
|
||||
<div class="helper">Waiting for chunk vectors before collection sync</div>
|
||||
<div class="progress"><span style="width:8%"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Run log</h2>
|
||||
<span class="helper">Recent events</span>
|
||||
</div>
|
||||
<div class="log">
|
||||
<div class="log-item">
|
||||
<span class="mono helper">09:18:11</span>
|
||||
<div>
|
||||
<strong>Semantic block serialization completed</strong>
|
||||
<div class="helper">Stored block tree and section hierarchy in Postgres parse artifact store.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<span class="mono helper">09:20:44</span>
|
||||
<div>
|
||||
<strong>Chunk builder emitted overlap windows</strong>
|
||||
<div class="helper">Header context is prepended to vector chunks for downstream retrieval quality.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-item">
|
||||
<span class="mono helper">09:22:08</span>
|
||||
<div>
|
||||
<strong>Embedding worker rate-limited temporarily</strong>
|
||||
<div class="helper">Retry budget still healthy. No manual action required unless latency exceeds 15 minutes.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="stack">
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Artifacts generated</h2>
|
||||
<span class="helper">Output layers</span>
|
||||
</div>
|
||||
<div class="artifact-grid">
|
||||
<div class="artifact">
|
||||
<strong>Layout JSON</strong>
|
||||
<span class="helper">Page, table, and text-span counts populate from the parser artifact output.</span>
|
||||
</div>
|
||||
<div class="artifact">
|
||||
<strong>Semantic blocks</strong>
|
||||
<span class="helper">Semantic nodes are mapped into chapter and clause hierarchy here.</span>
|
||||
</div>
|
||||
<div class="artifact">
|
||||
<strong>Vector chunks</strong>
|
||||
<span class="helper">Overlap windows and embedding text populate after chunk generation.</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Chunk profile</h2>
|
||||
<span class="helper">Top segments</span>
|
||||
</div>
|
||||
<div class="table">
|
||||
<div class="table-row">
|
||||
<div>
|
||||
<strong>4.2 Energy density threshold</strong>
|
||||
<div class="helper">Critical requirement clause</div>
|
||||
</div>
|
||||
<span class="mono">chunk count pending</span>
|
||||
<span class="mono">character count pending</span>
|
||||
<span class="status ok">Linked</span>
|
||||
</div>
|
||||
<div class="table-row">
|
||||
<div>
|
||||
<strong>5.1 Thermal event test method</strong>
|
||||
<div class="helper">Supplier evidence cross-reference</div>
|
||||
</div>
|
||||
<span class="mono">chunk count pending</span>
|
||||
<span class="mono">character count pending</span>
|
||||
<span class="status warn">Review</span>
|
||||
</div>
|
||||
<div class="table-row">
|
||||
<div>
|
||||
<strong>Appendix A formulas and tables</strong>
|
||||
<div class="helper">Dense table extraction from scan</div>
|
||||
</div>
|
||||
<span class="mono">chunk count pending</span>
|
||||
<span class="mono">character count pending</span>
|
||||
<span class="status warn">Noisy</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Configuration snapshot</h2>
|
||||
<span class="helper">Runtime values</span>
|
||||
</div>
|
||||
<div class="table">
|
||||
<div class="table-row">
|
||||
<div><strong>Parser backend</strong><div class="helper">Document extraction engine</div></div>
|
||||
<span class="mono">aliyun</span>
|
||||
<span class="mono">5 s poll</span>
|
||||
<span class="mono">900 s timeout</span>
|
||||
</div>
|
||||
<div class="table-row">
|
||||
<div><strong>Embedding target</strong><div class="helper">Vector generation</div></div>
|
||||
<span class="mono">text-embedding-v3</span>
|
||||
<span class="mono">1024 dim</span>
|
||||
<span class="mono">top_k 10</span>
|
||||
</div>
|
||||
<div class="table-row">
|
||||
<div><strong>Collection</strong><div class="helper">Milvus destination</div></div>
|
||||
<span class="mono">regulations_dense_1024_v2</span>
|
||||
<span class="mono">dense-only</span>
|
||||
<span class="mono">ready</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<span>T-Systems Regulation</span>
|
||||
<div class="footer-status">
|
||||
<span>Desktop Web</span>
|
||||
<span class="footer-dot" aria-hidden="true"></span>
|
||||
<span>Online</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script src="ui-preferences.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,693 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>AI + Compliance Hub - Document Management</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #fafafa;
|
||||
--surface: #ffffff;
|
||||
--surface-warm: var(--surface);
|
||||
--fg: #111111;
|
||||
--fg-2: var(--fg);
|
||||
--muted: #6b6b6b;
|
||||
--meta: var(--muted);
|
||||
--border: #e5e5e5;
|
||||
--border-soft: var(--border);
|
||||
--primary: #e20074;
|
||||
--accent: var(--primary);
|
||||
--accent-on: #ffffff;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), black 8%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 14%);
|
||||
--success: #17a34a;
|
||||
--warn: #eab308;
|
||||
--danger: #dc2626;
|
||||
--font-display: "TeleNeoWeb-Bold", "TeleNeoWeb-Medium", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-body: "TeleNeoWeb-Regular", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
||||
--text-xs: 11px;
|
||||
--text-sm: 13px;
|
||||
--text-base: 15px;
|
||||
--text-lg: 20px;
|
||||
--text-xl: 24px;
|
||||
--text-2xl: 32px;
|
||||
--text-3xl: 48px;
|
||||
--text-4xl: 64px;
|
||||
--leading-body: 1.5;
|
||||
--leading-tight: 1.2;
|
||||
--tracking-display: -0.01em;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-12: 48px;
|
||||
--space-20: 80px;
|
||||
--section-y-desktop: 80px;
|
||||
--section-y-tablet: 48px;
|
||||
--section-y-phone: 32px;
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-pill: 9999px;
|
||||
--elev-flat: none;
|
||||
--elev-ring: 0 0 0 1px var(--border);
|
||||
--elev-raised: 0 2px 8px color-mix(in oklab, var(--fg), transparent 92%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 70%);
|
||||
--motion-fast: 150ms;
|
||||
--motion-base: 200ms;
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--container-max: 1440px;
|
||||
--container-gutter-desktop: 24px;
|
||||
--container-gutter-tablet: 16px;
|
||||
--container-gutter-phone: 12px;
|
||||
--sidebar-w: 240px;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-body);
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
margin: 0;
|
||||
font-family: var(--font-display);
|
||||
letter-spacing: var(--tracking-display);
|
||||
line-height: var(--leading-tight);
|
||||
}
|
||||
p { margin: 0; text-wrap: pretty; }
|
||||
button, input, select { font: inherit; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
a:hover { color: var(--fg); text-decoration: underline; }
|
||||
|
||||
/* ── App shell ── */
|
||||
.app-shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
|
||||
|
||||
/* ── Sidebar ── */
|
||||
.sidebar { position: sticky; top: 0; height: 100vh; overflow-y: auto; display: flex; flex-direction: column; background: var(--surface); border-right: 1px solid var(--border); z-index: 10; }
|
||||
.sidebar-brand { display: flex; align-items: center; gap: 10px; height: 56px; padding: 0 16px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
||||
.brand-logo { width: 26px; height: 26px; background: var(--accent); border-radius: 6px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.brand-logo svg { color: #fff; }
|
||||
.sidebar-brand-name { font-family: var(--font-display); font-size: 13px; font-weight: 700; line-height: 1.2; }
|
||||
.sidebar-brand-sub { font-size: 10px; color: var(--muted); font-family: var(--font-mono); letter-spacing: 0.04em; }
|
||||
.sidebar-nav { flex: 1; padding: 12px 0; overflow-y: auto; }
|
||||
.nav-group { padding: 0 8px 4px; }
|
||||
.nav-group + .nav-group { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
|
||||
.nav-group-label { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); padding: 0 8px 6px; display: block; }
|
||||
.nav-item { display: flex; align-items: center; gap: 10px; height: 36px; padding: 0 8px; border-radius: 6px; color: var(--muted); font-size: 13px; cursor: pointer; transition: background 140ms, color 140ms; position: relative; }
|
||||
.nav-item:hover { background: color-mix(in oklab, var(--fg), transparent 94%); color: var(--fg); text-decoration: none; }
|
||||
.nav-item.active { background: color-mix(in oklab, var(--accent), transparent 90%); color: var(--accent); font-weight: 600; }
|
||||
.nav-item.active::before { content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px; border-radius: 0 3px 3px 0; background: var(--accent); }
|
||||
.nav-icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.7; }
|
||||
.nav-item.active .nav-icon { opacity: 1; }
|
||||
.nav-badge { margin-left: auto; min-width: 20px; height: 18px; padding: 0 6px; border-radius: 9999px; background: color-mix(in oklab, var(--accent), transparent 84%); color: var(--accent); font-size: 10px; font-family: var(--font-mono); display: flex; align-items: center; justify-content: center; font-weight: 700; }
|
||||
.sidebar-footer { border-top: 1px solid var(--border); padding: 10px 8px; flex-shrink: 0; display: flex; flex-direction: column; gap: 4px; }
|
||||
.sidebar-user { display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: 6px; cursor: pointer; }
|
||||
.sidebar-user:hover { background: color-mix(in oklab, var(--fg), transparent 94%); }
|
||||
.avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); color: #fff; font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.sidebar-user-info { min-width: 0; }
|
||||
.sidebar-user-name { font-size: 13px; font-weight: 600; color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.sidebar-user-role { font-size: 11px; color: var(--muted); font-family: var(--font-mono); }
|
||||
.sidebar-action { display: flex; align-items: center; gap: 10px; height: 34px; padding: 0 8px; border-radius: 6px; color: var(--muted); font-size: 13px; cursor: pointer; border: none; background: transparent; width: 100%; text-align: left; transition: background 140ms, color 140ms; }
|
||||
.sidebar-action:hover { background: color-mix(in oklab, var(--fg), transparent 94%); color: var(--fg); }
|
||||
|
||||
/* ── Content area ── */
|
||||
.content-area { display: flex; flex-direction: column; min-width: 0; min-height: 100vh; }
|
||||
.content-topbar { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 12px; height: 56px; padding: 0 24px; border-bottom: 1px solid var(--border); background: color-mix(in oklab, var(--bg), transparent 4%); backdrop-filter: blur(10px); }
|
||||
.topbar-title { font-weight: 600; font-size: 15px; color: var(--fg); flex: 1; }
|
||||
.search { display: flex; align-items: center; gap: 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface); padding: 0 12px; height: 34px; width: 240px; }
|
||||
.search input { border: 0; outline: none; background: transparent; width: 100%; color: var(--fg); font-size: 13px; }
|
||||
.search input::placeholder { color: var(--muted); }
|
||||
|
||||
/* ── Buttons ── */
|
||||
.btn {
|
||||
min-height: 34px;
|
||||
padding: 0 14px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background var(--motion-fast) var(--ease-standard), border-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
.btn:hover { border-color: var(--fg); }
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent-on);
|
||||
}
|
||||
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
||||
|
||||
.btn:focus-visible,
|
||||
.control:focus-visible,
|
||||
.table-row:focus-within {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
|
||||
/* ── Page layout ── */
|
||||
.page {
|
||||
padding: 24px;
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
.page-head {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
.eyebrow {
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.page-head h1 { font-size: clamp(30px, 4vw, 44px); }
|
||||
.page-head p { max-width: 70ch; color: var(--muted); }
|
||||
.control-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr repeat(4, minmax(0, 180px));
|
||||
gap: 12px;
|
||||
}
|
||||
.control {
|
||||
min-height: 44px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface);
|
||||
padding: 0 12px;
|
||||
color: var(--fg);
|
||||
}
|
||||
.control::placeholder { color: var(--muted); }
|
||||
.batch-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 16%);
|
||||
padding: 14px 16px;
|
||||
}
|
||||
.batch-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface);
|
||||
overflow: hidden;
|
||||
}
|
||||
.table-head,
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 28px 1.4fr 0.8fr 0.85fr 0.85fr 0.75fr 0.75fr 0.6fr;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
.table-head {
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 24%);
|
||||
}
|
||||
.table-row {
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
}
|
||||
.table-row:last-child { border-bottom: 0; }
|
||||
.table-row:hover { background: color-mix(in oklab, var(--fg), transparent 97%); }
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: 36px;
|
||||
padding: 0 24px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
background: color-mix(in oklab, var(--bg), var(--surface) 12%);
|
||||
margin-top: auto;
|
||||
}
|
||||
.footer-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.footer-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #19d3a2;
|
||||
box-shadow: 0 0 0 4px color-mix(in oklab, #19d3a2, transparent 84%);
|
||||
}
|
||||
.doc-title {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
.doc-title strong { font-size: var(--text-sm); }
|
||||
.doc-title span,
|
||||
.helper {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.mono {
|
||||
font-family: var(--font-mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: fit-content;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
.status::before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: currentColor;
|
||||
}
|
||||
.status.ok { color: var(--success); }
|
||||
.status.warn { color: color-mix(in oklab, var(--warn), black 24%); }
|
||||
.status.risk { color: var(--danger); }
|
||||
.link-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.text-link {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.text-link:hover { color: var(--accent); }
|
||||
.summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.summary-card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface);
|
||||
padding: 16px;
|
||||
}
|
||||
.summary-card strong {
|
||||
display: block;
|
||||
font-size: 28px;
|
||||
line-height: 1;
|
||||
margin: 10px 0 8px;
|
||||
}
|
||||
.summary-card span {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 22px;
|
||||
padding: 0 8px;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 700px) {
|
||||
.app-shell { grid-template-columns: 1fr; }
|
||||
.sidebar { display: none; }
|
||||
}
|
||||
@media (max-width: 1280px) {
|
||||
.control-bar,
|
||||
.summary { grid-template-columns: 1fr 1fr; }
|
||||
.table-head,
|
||||
.table-row { grid-template-columns: 28px 1.3fr 0.9fr 0.9fr 0.8fr 0.7fr; }
|
||||
.table-head > :nth-child(7),
|
||||
.table-head > :nth-child(8),
|
||||
.table-row > :nth-child(7),
|
||||
.table-row > :nth-child(8) { display: none; }
|
||||
}
|
||||
@media (max-width: 840px) {
|
||||
.content-topbar,
|
||||
.page { padding-inline: 12px; }
|
||||
.control-bar,
|
||||
.summary,
|
||||
.page-head { grid-template-columns: 1fr; }
|
||||
.page-head { display: grid; align-items: start; }
|
||||
.table-head { display: none; }
|
||||
.table-row {
|
||||
grid-template-columns: 28px 1fr;
|
||||
align-items: start;
|
||||
}
|
||||
.table-row > *:nth-child(n+3) {
|
||||
grid-column: 2;
|
||||
}
|
||||
.batch-bar {
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
}
|
||||
.footer {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding-block: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page="documents">
|
||||
<div class="app-shell">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar" aria-label="Primary navigation">
|
||||
<div class="sidebar-brand">
|
||||
<div class="brand-logo">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M2 3.5h12v1.5H2zm5.25 1.5h1.5v8h-1.5zm-3 2h2.25v1.5H4.25zm5.25 0h2.25v1.5H9.5zm-3 3h2.5v1.5H6.5z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sidebar-brand-name">T-Systems</div>
|
||||
<div class="sidebar-brand-sub">Regulation Hub</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="sidebar-nav" aria-label="Primary">
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">主导航</span>
|
||||
<a class="nav-item" href="index.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M2 2h5v5H2zm7 0h5v5H9zM2 9h5v5H2zm7 0h5v5H9z" fill="currentColor" opacity=".7"/></svg>
|
||||
概览
|
||||
</a>
|
||||
<a class="nav-item" href="dashboard.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M1.5 2.5h13v1H1.5zm0 3h13v1H1.5zm0 3h8v1h-8zm0 3h6v1h-6zM12 8.5a3.5 3.5 0 110 7 3.5 3.5 0 010-7zm0 1a2.5 2.5 0 100 5 2.5 2.5 0 000-5zm.5 1v2h1.5v1H11v-3h1.5z" fill="currentColor"/></svg>
|
||||
系统状态
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">工作台</span>
|
||||
<a class="nav-item active" href="document-management.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M3 1h7l3 3v11H3V1zm1 1v12h8V5h-3V2H4zm5 .5V4h1.5L9 1.5zM6 7h4v1H6zm0 2h4v1H6zm0 2h3v1H6z" fill="currentColor"/></svg>
|
||||
文档管理
|
||||
</a>
|
||||
<a class="nav-item" href="compliance-analysis.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M8 1l7 3-1 6a7 7 0 01-6 5A7 7 0 011 10L0 4l8-3zm0 1.2L1.3 4.8l.8 5.1A6 6 0 008 14.8a6 6 0 005.9-4.9l.8-5.1L8 2.2zM7.5 5h1v4.5l-1 .5V5zm0 5.5h1v1h-1v-1z" fill="currentColor"/></svg>
|
||||
合规分析
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">对话</span>
|
||||
<a class="nav-item" href="regulation-chat.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M2 2h12a1 1 0 011 1v8a1 1 0 01-1 1H5l-3 2.5V3a1 1 0 011-1zm0 1v9.5L4.5 11H14V3H2zm2 2h8v1H4zm0 2h6v1H4z" fill="currentColor"/></svg>
|
||||
法规对话
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="avatar">TS</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">T-Systems User</div>
|
||||
<div class="sidebar-user-role">Compliance Analyst</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="sidebar-action od-theme-toggle" type="button" data-od-theme aria-label="Toggle theme">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M8 3a5 5 0 100 10A5 5 0 008 3zM2 8a6 6 0 1112 0A6 6 0 012 8z" fill="currentColor"/></svg>
|
||||
主题
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Content area -->
|
||||
<div class="content-area">
|
||||
<header class="content-topbar">
|
||||
<div class="topbar-title">文档管理</div>
|
||||
<div class="search">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M6.5 1a5.5 5.5 0 014.23 9.02l3.62 3.62-.7.71-3.63-3.63A5.5 5.5 0 116.5 1zm0 1a4.5 4.5 0 100 9 4.5 4.5 0 000-9z" fill="currentColor" opacity=".5"/></svg>
|
||||
<input type="search" placeholder="Search documents…" aria-label="Search" />
|
||||
</div>
|
||||
<button class="btn">Import history</button>
|
||||
<a class="btn btn-primary" href="upload-modal.html">Upload documents</a>
|
||||
</header>
|
||||
|
||||
<main class="page">
|
||||
<section class="page-head" data-od-id="docs-head">
|
||||
<div>
|
||||
<div class="eyebrow">Document management</div>
|
||||
<h1>Library control for ingestion and indexing.</h1>
|
||||
</div>
|
||||
<div style="display:grid; gap:12px; justify-items:start;">
|
||||
<p style="margin:0;">Analysts can triage failed jobs, normalize metadata, and move a document from upload to parse-ready without leaving the operations workspace.</p>
|
||||
<div style="display:flex; gap:12px; align-items:center; flex-wrap:wrap;">
|
||||
<span class="pill">Queue</span>
|
||||
<button class="btn">Import history</button>
|
||||
<a class="btn btn-primary" href="upload-modal.html">Upload documents</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="summary" data-od-id="docs-summary">
|
||||
<div class="summary-card">
|
||||
<div class="helper">Total library</div>
|
||||
<strong class="mono">--</strong>
|
||||
<span>Across GB, GB/T, UNECE, ISO, and enterprise interpretation notes</span>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="helper">Processing</div>
|
||||
<strong class="mono">--</strong>
|
||||
<span>Waiting on parse, chunking, embedding, or index sync</span>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="helper">Failed</div>
|
||||
<strong class="mono">--</strong>
|
||||
<span>Timeout, OCR confidence, duplicate ID, or vector schema mismatch</span>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<div class="helper">Avg summary latency</div>
|
||||
<strong class="mono">--</strong>
|
||||
<span>Document summary generation after artifacts complete</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="control-bar" data-od-id="docs-filters">
|
||||
<input class="control" type="text" value="GB/T" aria-label="Keyword filter" />
|
||||
<select class="control" aria-label="Status filter">
|
||||
<option>All statuses</option>
|
||||
<option selected>Processing + failed</option>
|
||||
<option>Indexed</option>
|
||||
</select>
|
||||
<select class="control" aria-label="Regulation type filter">
|
||||
<option selected>Vehicle safety</option>
|
||||
<option>Cybersecurity</option>
|
||||
<option>Battery</option>
|
||||
</select>
|
||||
<select class="control" aria-label="Parser filter">
|
||||
<option selected>Aliyun parser</option>
|
||||
<option>Legacy local parser</option>
|
||||
</select>
|
||||
<select class="control" aria-label="Owner filter">
|
||||
<option selected>All owners</option>
|
||||
<option>Battery Safety Team</option>
|
||||
<option>Connected Vehicle Team</option>
|
||||
</select>
|
||||
</section>
|
||||
|
||||
<section class="batch-bar" data-od-id="docs-batch">
|
||||
<div>
|
||||
<strong>4 selected</strong>
|
||||
<span class="helper">Batch actions apply metadata, retry parse, or archive stale drafts.</span>
|
||||
</div>
|
||||
<div class="batch-actions">
|
||||
<button class="btn">Assign category</button>
|
||||
<button class="btn">Retry parse</button>
|
||||
<button class="btn">Mark superseded</button>
|
||||
<button class="btn">Delete</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card" data-od-id="docs-table">
|
||||
<div class="table-head">
|
||||
<span></span>
|
||||
<span>Document</span>
|
||||
<span>Type</span>
|
||||
<span>Status</span>
|
||||
<span>Artifacts</span>
|
||||
<span>Updated</span>
|
||||
<span>Owner</span>
|
||||
<span>Actions</span>
|
||||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
<input type="checkbox" checked aria-label="Select document" />
|
||||
<div class="doc-title">
|
||||
<strong>GB/T 31484-2015 battery energy density methods</strong>
|
||||
<span class="mono">doc_id: GBT-31484-2015-r2 · version 2026 addendum</span>
|
||||
</div>
|
||||
<span>Battery</span>
|
||||
<span class="status warn">Embedding</span>
|
||||
<span class="mono">chunk build active</span>
|
||||
<span class="mono">09:42</span>
|
||||
<span>Battery Safety</span>
|
||||
<div class="link-row">
|
||||
<a class="text-link" href="document-detail.html">Inspect</a>
|
||||
<a class="text-link" href="#">Retry</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
<input type="checkbox" checked aria-label="Select document" />
|
||||
<div class="doc-title">
|
||||
<strong>UNECE R155 cybersecurity management Q&A</strong>
|
||||
<span class="mono">doc_id: UNECE-R155-qa-2026-05 · summary ready</span>
|
||||
</div>
|
||||
<span>Cybersecurity</span>
|
||||
<span class="status ok">Indexed</span>
|
||||
<span class="mono">retrieval-ready</span>
|
||||
<span class="mono">08:18</span>
|
||||
<span>Connected Fleet</span>
|
||||
<div class="link-row">
|
||||
<a class="text-link" href="compliance-analysis.html">Analyze</a>
|
||||
<a class="text-link" href="regulation-chat.html">Chat</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
<input type="checkbox" aria-label="Select document" />
|
||||
<div class="doc-title">
|
||||
<strong>GB 26112-2010 roof strength scan</strong>
|
||||
<span class="mono">doc_id: GB-26112-scan-ocr · 6 pages low confidence</span>
|
||||
</div>
|
||||
<span>Vehicle safety</span>
|
||||
<span class="status risk">Failed</span>
|
||||
<span class="mono">OCR blocked</span>
|
||||
<span class="mono">Yesterday</span>
|
||||
<span>Body Structure</span>
|
||||
<div class="link-row">
|
||||
<a class="text-link" href="#">View error</a>
|
||||
<a class="text-link" href="#">Re-upload</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
<input type="checkbox" checked aria-label="Select document" />
|
||||
<div class="doc-title">
|
||||
<strong>GB/T 18487 charging interface interpretation</strong>
|
||||
<span class="mono">doc_id: GBT-18487-note-2026 · duplicate metadata candidate</span>
|
||||
</div>
|
||||
<span>Charging</span>
|
||||
<span class="status warn">Review</span>
|
||||
<span class="mono">Summary only</span>
|
||||
<span class="mono">11:06</span>
|
||||
<span>EV Platform</span>
|
||||
<div class="link-row">
|
||||
<a class="text-link" href="#">Normalize</a>
|
||||
<a class="text-link" href="#">Merge</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
<input type="checkbox" aria-label="Select document" />
|
||||
<div class="doc-title">
|
||||
<strong>Supplier thermal runaway test report</strong>
|
||||
<span class="mono">doc_id: supplier-pack-773A · confidential appendix attached</span>
|
||||
</div>
|
||||
<span>Evidence</span>
|
||||
<span class="status ok">Indexed</span>
|
||||
<span class="mono">248 chunks</span>
|
||||
<span class="mono">Monday</span>
|
||||
<span>Supplier QA</span>
|
||||
<div class="link-row">
|
||||
<a class="text-link" href="document-detail.html">Inspect</a>
|
||||
<a class="text-link" href="#">Download</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<span>T-Systems Regulation Hub</span>
|
||||
<div class="footer-status">
|
||||
<span class="footer-dot" aria-hidden="true"></span>
|
||||
<span>Online</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script src="ui-preferences.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
696
Prototype/cc29bcb0-df2d-4d50-9428-7caa406ecb29/index.html
Normal file
696
Prototype/cc29bcb0-df2d-4d50-9428-7caa406ecb29/index.html
Normal file
@@ -0,0 +1,696 @@
|
||||
<!doctype html>
|
||||
<html lang="en"><head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AI + Compliance Hub Prototype Suite</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #fafafa;
|
||||
--surface: #ffffff;
|
||||
--surface-warm: var(--surface);
|
||||
--fg: #111111;
|
||||
--fg-2: var(--fg);
|
||||
--muted: #6b6b6b;
|
||||
--meta: var(--muted);
|
||||
--border: #e5e5e5;
|
||||
--border-soft: var(--border);
|
||||
--primary: #e20074;
|
||||
--accent: var(--primary);
|
||||
--accent-on: #ffffff;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), black 8%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 14%);
|
||||
--success: #17a34a;
|
||||
--warn: #eab308;
|
||||
--danger: #dc2626;
|
||||
--font-display: "TeleNeoWeb-Bold", "TeleNeoWeb-Medium", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-body: "TeleNeoWeb-Regular", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
||||
--text-xs: 12px;
|
||||
--text-sm: 14px;
|
||||
--text-base: 16px;
|
||||
--text-lg: 20px;
|
||||
--text-xl: 24px;
|
||||
--text-2xl: 32px;
|
||||
--text-3xl: 48px;
|
||||
--text-4xl: 64px;
|
||||
--leading-body: 1.5;
|
||||
--leading-tight: 1.2;
|
||||
--tracking-display: -0.01em;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-12: 48px;
|
||||
--space-20: 80px;
|
||||
--section-y-desktop: 80px;
|
||||
--section-y-tablet: 48px;
|
||||
--section-y-phone: 32px;
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-pill: 9999px;
|
||||
--elev-flat: none;
|
||||
--elev-ring: 0 0 0 1px var(--border);
|
||||
--elev-raised: 0 2px 8px color-mix(in oklab, var(--fg), transparent 92%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 70%);
|
||||
--motion-fast: 150ms;
|
||||
--motion-base: 200ms;
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--container-max: 1200px;
|
||||
--container-gutter-desktop: 24px;
|
||||
--container-gutter-tablet: 16px;
|
||||
--container-gutter-phone: 12px;
|
||||
--sidebar-w: 240px;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html { -webkit-text-size-adjust: 100%; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-body);
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
transition: color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
a:hover { color: var(--fg); text-decoration: underline; }
|
||||
button, input, select { font: inherit; }
|
||||
p { text-wrap: pretty; }
|
||||
h1, h2, h3 { font-family: var(--font-display); line-height: var(--leading-tight); letter-spacing: var(--tracking-display); margin: 0; text-wrap: balance; }
|
||||
|
||||
/* ── Sidebar shell ── */
|
||||
.app-shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
|
||||
.sidebar { position: sticky; top: 0; height: 100vh; overflow-y: auto; display: flex; flex-direction: column; background: var(--surface); border-right: 1px solid var(--border); z-index: 10; }
|
||||
.sidebar-brand { display: flex; align-items: center; gap: 10px; height: 56px; padding: 0 16px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
||||
.brand-logo { width: 26px; height: 26px; background: var(--accent); border-radius: 6px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.brand-logo svg { color: #fff; }
|
||||
.sidebar-brand-name { font-family: var(--font-display); font-size: 13px; font-weight: 700; line-height: 1.2; }
|
||||
.sidebar-brand-sub { font-size: 10px; color: var(--muted); font-family: var(--font-mono); letter-spacing: 0.04em; }
|
||||
.sidebar-nav { flex: 1; padding: 12px 0; overflow-y: auto; }
|
||||
.nav-group { padding: 0 8px 4px; }
|
||||
.nav-group + .nav-group { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
|
||||
.nav-group-label { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); padding: 0 8px 6px; display: block; }
|
||||
.nav-item { display: flex; align-items: center; gap: 10px; height: 36px; padding: 0 8px; border-radius: 6px; color: var(--muted); font-size: 13px; cursor: pointer; transition: background 140ms, color 140ms; position: relative; }
|
||||
.nav-item:hover { background: color-mix(in oklab, var(--fg), transparent 94%); color: var(--fg); text-decoration: none; }
|
||||
.nav-item.active { background: color-mix(in oklab, var(--accent), transparent 90%); color: var(--accent); font-weight: 600; }
|
||||
.nav-item.active::before { content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px; border-radius: 0 3px 3px 0; background: var(--accent); }
|
||||
.nav-icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.7; }
|
||||
.nav-item.active .nav-icon { opacity: 1; }
|
||||
.sidebar-footer { border-top: 1px solid var(--border); padding: 10px 8px; flex-shrink: 0; display: flex; flex-direction: column; gap: 4px; }
|
||||
.sidebar-user { display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: 6px; cursor: pointer; }
|
||||
.sidebar-user:hover { background: color-mix(in oklab, var(--fg), transparent 94%); }
|
||||
.avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); color: #fff; font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.sidebar-user-info { min-width: 0; }
|
||||
.sidebar-user-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.sidebar-user-role { font-size: 11px; color: var(--muted); font-family: var(--font-mono); }
|
||||
.sidebar-action { display: flex; align-items: center; gap: 10px; height: 34px; padding: 0 8px; border-radius: 6px; color: var(--muted); font-size: 13px; cursor: pointer; border: none; background: transparent; width: 100%; text-align: left; transition: background 140ms, color 140ms; }
|
||||
.sidebar-action:hover { background: color-mix(in oklab, var(--fg), transparent 94%); color: var(--fg); }
|
||||
.content-area { display: flex; flex-direction: column; min-width: 0; min-height: 100vh; }
|
||||
.content-topbar { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 12px; height: 56px; padding: 0 24px; border-bottom: 1px solid var(--border); background: color-mix(in oklab, var(--bg), transparent 4%); backdrop-filter: blur(10px); }
|
||||
.topbar-title { font-weight: 600; font-size: 15px; color: var(--fg); flex: 1; }
|
||||
.footer-dot { width: 7px; height: 7px; border-radius: 50%; background: #19d3a2; box-shadow: 0 0 0 3px color-mix(in oklab, #19d3a2, transparent 82%); }
|
||||
.footer-status { display: inline-flex; align-items: center; gap: 8px; }
|
||||
@media (max-width: 700px) { .app-shell { grid-template-columns: 1fr; } .sidebar { display: none; } }
|
||||
|
||||
/* ── Page-specific styles ── */
|
||||
.container {
|
||||
max-width: var(--container-max);
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--container-gutter-desktop);
|
||||
}
|
||||
.meta {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.hero {
|
||||
padding: 72px 0 36px;
|
||||
display: grid;
|
||||
grid-template-columns: 1.3fr 0.9fr;
|
||||
gap: var(--space-12);
|
||||
align-items: start;
|
||||
}
|
||||
.eyebrow {
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin: 0 0 var(--space-4);
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: clamp(40px, 6vw, 64px);
|
||||
max-width: 11ch;
|
||||
}
|
||||
.hero-copy {
|
||||
margin-top: var(--space-5);
|
||||
max-width: 58ch;
|
||||
color: var(--muted);
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-3);
|
||||
margin-top: var(--space-6);
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
min-height: 44px;
|
||||
padding: 0 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
transition: background var(--motion-fast) var(--ease-standard), border-color var(--motion-fast) var(--ease-standard), color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
.btn:focus-visible,
|
||||
.screen-card a:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent-on);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: var(--accent-hover);
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
border-color: var(--fg);
|
||||
text-decoration: none;
|
||||
}
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-5);
|
||||
box-shadow: var(--elev-flat);
|
||||
}
|
||||
.summary-grid,
|
||||
.screen-grid {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
.summary-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
margin-bottom: 44px;
|
||||
}
|
||||
.screen-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
.screen-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
min-height: 280px;
|
||||
}
|
||||
.screen-card a {
|
||||
color: inherit;
|
||||
display: block;
|
||||
border-radius: inherit;
|
||||
}
|
||||
.screen-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border);
|
||||
padding: 4px 10px;
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.mini-shot {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background:
|
||||
linear-gradient(180deg, color-mix(in oklab, var(--accent), white 94%), transparent),
|
||||
var(--surface);
|
||||
padding: var(--space-4);
|
||||
min-height: 148px;
|
||||
}
|
||||
.mini-toolbar {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
.mini-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--border);
|
||||
}
|
||||
.mini-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 140px 1fr;
|
||||
gap: var(--space-3);
|
||||
min-height: 94px;
|
||||
}
|
||||
.mini-nav,
|
||||
.mini-body,
|
||||
.mini-row,
|
||||
.mini-block {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 24%);
|
||||
}
|
||||
.mini-nav { padding: var(--space-3); }
|
||||
.mini-body { padding: var(--space-3); display: grid; gap: var(--space-2); }
|
||||
.mini-row {
|
||||
height: 12px;
|
||||
background: color-mix(in oklab, var(--fg), transparent 96%);
|
||||
}
|
||||
.mini-row.accent {
|
||||
width: 42%;
|
||||
background: color-mix(in oklab, var(--accent), white 76%);
|
||||
border-color: color-mix(in oklab, var(--accent), white 70%);
|
||||
}
|
||||
.mini-block { height: 56px; }
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.section-title h2 { font-size: var(--text-2xl); }
|
||||
.section-title p { margin: 0; max-width: 58ch; color: var(--muted); }
|
||||
.flow {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
gap: var(--space-3);
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
.flow-step {
|
||||
position: relative;
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface);
|
||||
}
|
||||
.flow-step strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.flow-step span {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
.flow-step::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -13px;
|
||||
width: 10px;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
}
|
||||
.flow-step:last-child::after { display: none; }
|
||||
.footer {
|
||||
padding: 24px 0 48px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.hero,
|
||||
.summary-grid,
|
||||
.screen-grid,
|
||||
.flow {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.container { padding: 0 var(--container-gutter-tablet); }
|
||||
.flow-step::after { display: none; }
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
.container { padding: 0 var(--container-gutter-phone); }
|
||||
.hero { padding-top: 48px; }
|
||||
.hero-copy { font-size: var(--text-base); }
|
||||
.screen-head { align-items: start; flex-direction: column; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page="index">
|
||||
<div class="app-shell">
|
||||
<aside class="sidebar" aria-label="Primary navigation">
|
||||
<div class="sidebar-brand">
|
||||
<div class="brand-logo">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M2 3.5h12v1.5H2zm5.25 1.5h1.5v8h-1.5zm-3 2h2.25v1.5H4.25zm5.25 0h2.25v1.5H9.5zm-3 3h2.5v1.5H6.5z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sidebar-brand-name">T-Systems</div>
|
||||
<div class="sidebar-brand-sub">Regulation Hub</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="sidebar-nav" aria-label="Primary">
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">主导航</span>
|
||||
<a class="nav-item active" href="index.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M2 2h5v5H2zm7 0h5v5H9zM2 9h5v5H2zm7 0h5v5H9z" fill="currentColor" opacity=".7"/></svg>
|
||||
概览
|
||||
</a>
|
||||
<a class="nav-item" href="dashboard.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M1.5 2.5h13v1H1.5zm0 3h13v1H1.5zm0 3h8v1h-8zm0 3h6v1h-6z" fill="currentColor"/></svg>
|
||||
系统状态
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">工作台</span>
|
||||
<a class="nav-item" href="document-management.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M3 1h7l3 3v11H3V1zm1 1v12h8V5h-3V2H4zm5 .5V4h1.5L9 1.5zM6 7h4v1H6zm0 2h4v1H6zm0 2h3v1H6z" fill="currentColor"/></svg>
|
||||
文档管理
|
||||
</a>
|
||||
<a class="nav-item" href="compliance-analysis.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M8 1l7 3-1 6a7 7 0 01-6 5A7 7 0 011 10L0 4l8-3zm0 1.2L1.3 4.8l.8 5.1A6 6 0 008 14.8a6 6 0 005.9-4.9l.8-5.1L8 2.2zM7.5 5h1v4.5l-1 .5V5zm0 5.5h1v1h-1v-1z" fill="currentColor"/></svg>
|
||||
合规分析
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">对话</span>
|
||||
<a class="nav-item" href="regulation-chat.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M2 2h12a1 1 0 011 1v8a1 1 0 01-1 1H5l-3 2.5V3a1 1 0 011-1zm0 1v9.5L4.5 11H14V3H2zm2 2h8v1H4zm0 2h6v1H4z" fill="currentColor"/></svg>
|
||||
法规对话
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="avatar">TS</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">T-Systems User</div>
|
||||
<div class="sidebar-user-role">Compliance Analyst</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="sidebar-action od-theme-toggle" type="button" data-od-theme aria-label="Toggle theme">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M8 3a5 5 0 100 10A5 5 0 008 3zM2 8a6 6 0 1112 0A6 6 0 012 8z" fill="currentColor"/></svg>
|
||||
主题
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="content-area">
|
||||
<header class="content-topbar">
|
||||
<span class="topbar-title">概览</span>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<section class="hero" data-od-id="hero">
|
||||
<div>
|
||||
<p class="eyebrow">Prototype suite</p>
|
||||
<h1>Operational screens for AI document compliance work.</h1>
|
||||
<p class="hero-copy">
|
||||
This launcher maps the full desktop workflow: intake, parsing, embeddings, retrieval-led analysis, and citation-backed chat.
|
||||
Each screen is isolated as its own product surface so reviewers can inspect decisions without switching fake demo controls.
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<a class="btn btn-primary" href="dashboard.html">Open dashboard</a>
|
||||
<a class="btn btn-secondary" href="regulation-chat.html">Jump to regulation chat</a>
|
||||
</div>
|
||||
</div>
|
||||
<aside class="card">
|
||||
<p class="eyebrow">Scope</p>
|
||||
<div class="summary-grid" style="grid-template-columns: 1fr; margin: 0; gap: 14px;">
|
||||
<div>
|
||||
<strong>6 product screens</strong>
|
||||
<div class="meta">Launcher, operations overview, doc management, upload, parse detail, analysis, chat</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Backend-aware flows</strong>
|
||||
<div class="meta">Aliyun parsing, chunk generation, text-embedding-v3, dense vector collection, citation retrieval</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Review posture</strong>
|
||||
<div class="meta">Quiet utility chrome, clear status states, one accent reserved for action and escalation</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section data-od-id="workflow">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<p class="eyebrow">Interaction rhythm</p>
|
||||
<h2>One sequence, six focused stops.</h2>
|
||||
</div>
|
||||
<p>The product cadence moves from portfolio awareness to precise intervention. Each screen hands off to the next likely action instead of collapsing everything into a single dense page.</p>
|
||||
</div>
|
||||
<div class="flow">
|
||||
<div class="flow-step">
|
||||
<strong>01 Dashboard</strong>
|
||||
<span>Watch ingestion health, queue pressure, policy risk, and active investigations.</span>
|
||||
</div>
|
||||
<div class="flow-step">
|
||||
<strong>02 Library</strong>
|
||||
<span>Filter standards, inspect states, trigger retry, delete, and batch assign metadata.</span>
|
||||
</div>
|
||||
<div class="flow-step">
|
||||
<strong>03 Upload</strong>
|
||||
<span>Stage files, assign regulation type and version, and monitor import queue progress.</span>
|
||||
</div>
|
||||
<div class="flow-step">
|
||||
<strong>04 Parse detail</strong>
|
||||
<span>Follow document parsing, semantic blocks, vector chunks, and embedding/index milestones.</span>
|
||||
</div>
|
||||
<div class="flow-step">
|
||||
<strong>05 Analysis</strong>
|
||||
<span>Compare source passages with retrieved regulations, findings, and conclusion-ready actions.</span>
|
||||
</div>
|
||||
<div class="flow-step">
|
||||
<strong>06 Chat</strong>
|
||||
<span>Interrogate a clause with citations, trace history, and export reasoning with sources.</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section data-od-id="screens">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<p class="eyebrow">Screens</p>
|
||||
<h2>Open any surface directly.</h2>
|
||||
</div>
|
||||
<p>Each tile previews its UI structure and primary job. These are entry points into a realistic desktop workflow, not storyboards.</p>
|
||||
</div>
|
||||
<div class="screen-grid">
|
||||
<article class="screen-card card">
|
||||
<div class="screen-head">
|
||||
<div>
|
||||
<h3>Dashboard</h3>
|
||||
<div class="meta">Overview of system health, backlog, and current compliance programs</div>
|
||||
</div>
|
||||
<span class="chip">Operations</span>
|
||||
</div>
|
||||
<a href="dashboard.html">
|
||||
<div class="mini-shot" aria-hidden="true">
|
||||
<div class="mini-toolbar"><span class="mini-dot"></span><span class="mini-dot"></span><span class="mini-dot"></span></div>
|
||||
<div class="mini-layout">
|
||||
<div class="mini-nav"></div>
|
||||
<div class="mini-body">
|
||||
<div class="mini-row accent"></div>
|
||||
<div class="mini-block"></div>
|
||||
<div class="mini-row"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="screen-card card">
|
||||
<div class="screen-head">
|
||||
<div>
|
||||
<h3>Document management</h3>
|
||||
<div class="meta">Library, filters, batch actions, and ingestion state control</div>
|
||||
</div>
|
||||
<span class="chip">Library</span>
|
||||
</div>
|
||||
<a href="document-management.html">
|
||||
<div class="mini-shot" aria-hidden="true">
|
||||
<div class="mini-toolbar"><span class="mini-dot"></span><span class="mini-dot"></span><span class="mini-dot"></span></div>
|
||||
<div class="mini-layout">
|
||||
<div class="mini-nav"></div>
|
||||
<div class="mini-body">
|
||||
<div class="mini-row accent"></div>
|
||||
<div class="mini-row"></div>
|
||||
<div class="mini-block"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="screen-card card">
|
||||
<div class="screen-head">
|
||||
<div>
|
||||
<h3>Upload modal</h3>
|
||||
<div class="meta">Drag-drop intake, metadata assignment, and import queue feedback</div>
|
||||
</div>
|
||||
<span class="chip">Intake</span>
|
||||
</div>
|
||||
<a href="upload-modal.html">
|
||||
<div class="mini-shot" aria-hidden="true">
|
||||
<div class="mini-toolbar"><span class="mini-dot"></span><span class="mini-dot"></span><span class="mini-dot"></span></div>
|
||||
<div class="mini-layout" style="grid-template-columns: 1fr;">
|
||||
<div class="mini-body">
|
||||
<div class="mini-block"></div>
|
||||
<div class="mini-row accent"></div>
|
||||
<div class="mini-row"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="screen-card card">
|
||||
<div class="screen-head">
|
||||
<div>
|
||||
<h3>Document detail</h3>
|
||||
<div class="meta">Parsing, chunking, embedding, and vector store progress by artifact stage</div>
|
||||
</div>
|
||||
<span class="chip">Pipeline</span>
|
||||
</div>
|
||||
<a href="document-detail.html">
|
||||
<div class="mini-shot" aria-hidden="true">
|
||||
<div class="mini-toolbar"><span class="mini-dot"></span><span class="mini-dot"></span><span class="mini-dot"></span></div>
|
||||
<div class="mini-layout">
|
||||
<div class="mini-nav"></div>
|
||||
<div class="mini-body">
|
||||
<div class="mini-row accent"></div>
|
||||
<div class="mini-block"></div>
|
||||
<div class="mini-block"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="screen-card card">
|
||||
<div class="screen-head">
|
||||
<div>
|
||||
<h3>Compliance analysis</h3>
|
||||
<div class="meta">Retrieval to reasoning to conclusion workspace with tracked evidence</div>
|
||||
</div>
|
||||
<span class="chip">Analysis</span>
|
||||
</div>
|
||||
<a href="compliance-analysis.html">
|
||||
<div class="mini-shot" aria-hidden="true">
|
||||
<div class="mini-toolbar"><span class="mini-dot"></span><span class="mini-dot"></span><span class="mini-dot"></span></div>
|
||||
<div class="mini-layout">
|
||||
<div class="mini-nav"></div>
|
||||
<div class="mini-body">
|
||||
<div class="mini-row accent"></div>
|
||||
<div class="mini-row"></div>
|
||||
<div class="mini-block"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="screen-card card">
|
||||
<div class="screen-head">
|
||||
<div>
|
||||
<h3>Regulation chat</h3>
|
||||
<div class="meta">Source-backed question answering with history, quick prompts, and citation rail</div>
|
||||
</div>
|
||||
<span class="chip">Copilot</span>
|
||||
</div>
|
||||
<a href="regulation-chat.html">
|
||||
<div class="mini-shot" aria-hidden="true">
|
||||
<div class="mini-toolbar"><span class="mini-dot"></span><span class="mini-dot"></span><span class="mini-dot"></span></div>
|
||||
<div class="mini-layout">
|
||||
<div class="mini-nav"></div>
|
||||
<div class="mini-body">
|
||||
<div class="mini-row accent"></div>
|
||||
<div class="mini-row"></div>
|
||||
<div class="mini-row"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">Prototype prepared for product evaluators reviewing document ingestion, AI parsing, and compliance reasoning workflows.</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script src="ui-preferences.js"></script>
|
||||
|
||||
|
||||
</body></html>
|
||||
@@ -0,0 +1,608 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>AI + Compliance Hub - Regulation Chat</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #fafafa;
|
||||
--surface: #ffffff;
|
||||
--surface-warm: var(--surface);
|
||||
--fg: #111111;
|
||||
--fg-2: var(--fg);
|
||||
--muted: #6b6b6b;
|
||||
--meta: var(--muted);
|
||||
--border: #e5e5e5;
|
||||
--border-soft: var(--border);
|
||||
--primary: #e20074;
|
||||
--accent: var(--primary);
|
||||
--accent-on: #ffffff;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), black 8%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 14%);
|
||||
--success: #17a34a;
|
||||
--warn: #eab308;
|
||||
--danger: #dc2626;
|
||||
--font-display: "TeleNeoWeb-Bold", "TeleNeoWeb-Medium", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-body: "TeleNeoWeb-Regular", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
||||
--text-xs: 12px;
|
||||
--text-sm: 14px;
|
||||
--text-base: 16px;
|
||||
--text-lg: 20px;
|
||||
--text-xl: 24px;
|
||||
--text-2xl: 32px;
|
||||
--text-3xl: 48px;
|
||||
--text-4xl: 64px;
|
||||
--leading-body: 1.5;
|
||||
--leading-tight: 1.2;
|
||||
--tracking-display: -0.01em;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-12: 48px;
|
||||
--space-20: 80px;
|
||||
--section-y-desktop: 80px;
|
||||
--section-y-tablet: 48px;
|
||||
--section-y-phone: 32px;
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-pill: 9999px;
|
||||
--elev-flat: none;
|
||||
--elev-ring: 0 0 0 1px var(--border);
|
||||
--elev-raised: 0 2px 8px color-mix(in oklab, var(--fg), transparent 92%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 70%);
|
||||
--motion-fast: 150ms;
|
||||
--motion-base: 200ms;
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--container-max: 1600px;
|
||||
--container-gutter-desktop: 24px;
|
||||
--container-gutter-tablet: 16px;
|
||||
--container-gutter-phone: 12px;
|
||||
--sidebar-w: 240px;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-body);
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
margin: 0;
|
||||
font-family: var(--font-display);
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-display);
|
||||
}
|
||||
p { margin: 0; text-wrap: pretty; }
|
||||
button, input { font: inherit; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
a:hover { color: var(--fg); text-decoration: underline; }
|
||||
|
||||
/* ── Sidebar shell ── */
|
||||
.app-shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
|
||||
.sidebar { position: sticky; top: 0; height: 100vh; overflow-y: auto; display: flex; flex-direction: column; background: var(--surface); border-right: 1px solid var(--border); z-index: 10; }
|
||||
.sidebar-brand { display: flex; align-items: center; gap: 10px; height: 56px; padding: 0 16px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
||||
.brand-logo { width: 26px; height: 26px; background: var(--accent); border-radius: 6px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.brand-logo svg { color: #fff; }
|
||||
.sidebar-brand-name { font-family: var(--font-display); font-size: 13px; font-weight: 700; line-height: 1.2; }
|
||||
.sidebar-brand-sub { font-size: 10px; color: var(--muted); font-family: var(--font-mono); letter-spacing: 0.04em; }
|
||||
.sidebar-nav { flex: 1; padding: 12px 0; overflow-y: auto; }
|
||||
.nav-group { padding: 0 8px 4px; }
|
||||
.nav-group + .nav-group { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
|
||||
.nav-group-label { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); padding: 0 8px 6px; display: block; }
|
||||
.nav-item { display: flex; align-items: center; gap: 10px; height: 36px; padding: 0 8px; border-radius: 6px; color: var(--muted); font-size: 13px; cursor: pointer; transition: background 140ms, color 140ms; position: relative; }
|
||||
.nav-item:hover { background: color-mix(in oklab, var(--fg), transparent 94%); color: var(--fg); text-decoration: none; }
|
||||
.nav-item.active { background: color-mix(in oklab, var(--accent), transparent 90%); color: var(--accent); font-weight: 600; }
|
||||
.nav-item.active::before { content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 3px; border-radius: 0 3px 3px 0; background: var(--accent); }
|
||||
.nav-icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.7; }
|
||||
.nav-item.active .nav-icon { opacity: 1; }
|
||||
.sidebar-footer { border-top: 1px solid var(--border); padding: 10px 8px; flex-shrink: 0; display: flex; flex-direction: column; gap: 4px; }
|
||||
.sidebar-user { display: flex; align-items: center; gap: 10px; padding: 8px; border-radius: 6px; cursor: pointer; }
|
||||
.sidebar-user:hover { background: color-mix(in oklab, var(--fg), transparent 94%); }
|
||||
.avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); color: #fff; font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.sidebar-user-info { min-width: 0; }
|
||||
.sidebar-user-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.sidebar-user-role { font-size: 11px; color: var(--muted); font-family: var(--font-mono); }
|
||||
.sidebar-action { display: flex; align-items: center; gap: 10px; height: 34px; padding: 0 8px; border-radius: 6px; color: var(--muted); font-size: 13px; cursor: pointer; border: none; background: transparent; width: 100%; text-align: left; transition: background 140ms, color 140ms; }
|
||||
.sidebar-action:hover { background: color-mix(in oklab, var(--fg), transparent 94%); color: var(--fg); }
|
||||
.content-area { display: flex; flex-direction: column; min-width: 0; min-height: 100vh; }
|
||||
.content-topbar { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 12px; height: 56px; padding: 0 24px; border-bottom: 1px solid var(--border); background: color-mix(in oklab, var(--bg), transparent 4%); backdrop-filter: blur(10px); }
|
||||
.topbar-title { font-weight: 600; font-size: 15px; color: var(--fg); flex: 1; }
|
||||
.footer-status { display: inline-flex; align-items: center; gap: 8px; }
|
||||
.footer-dot { width: 7px; height: 7px; border-radius: 50%; background: #19d3a2; }
|
||||
@media (max-width: 700px) { .app-shell { grid-template-columns: 1fr; } .sidebar { display: none; } }
|
||||
|
||||
/* ── Page content ── */
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: 36px;
|
||||
padding: 0 24px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
background: color-mix(in oklab, var(--bg), var(--surface) 12%);
|
||||
}
|
||||
.page {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
.hero {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
.hero h1 { font-size: clamp(30px, 4vw, 44px); }
|
||||
.hero p { max-width: 72ch; color: var(--muted); }
|
||||
.eyebrow {
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.shell {
|
||||
display: grid;
|
||||
grid-template-columns: 280px minmax(0, 1fr) 320px;
|
||||
gap: 16px;
|
||||
min-height: 760px;
|
||||
}
|
||||
.card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface);
|
||||
padding: 18px;
|
||||
min-width: 0;
|
||||
}
|
||||
.section-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.section-head h2 { font-size: var(--text-xl); }
|
||||
.helper {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.history,
|
||||
.quick-list,
|
||||
.sources,
|
||||
.messages {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.history-item,
|
||||
.quick-item,
|
||||
.source-item,
|
||||
.message {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 14%);
|
||||
padding: 14px;
|
||||
}
|
||||
.history-item.active {
|
||||
background: color-mix(in oklab, var(--accent), white 92%);
|
||||
border-color: color-mix(in oklab, var(--accent), white 70%);
|
||||
}
|
||||
.pill,
|
||||
.status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: fit-content;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
.status::before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: currentColor;
|
||||
}
|
||||
.status.ok { color: var(--success); }
|
||||
.status.warn { color: color-mix(in oklab, var(--warn), black 24%); }
|
||||
.status.risk { color: var(--danger); }
|
||||
.mono {
|
||||
font-family: var(--font-mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.chat-column {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
.messages {
|
||||
align-content: start;
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.message.assistant {
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 8%);
|
||||
}
|
||||
.message.user {
|
||||
background: color-mix(in oklab, var(--accent), white 92%);
|
||||
border-color: color-mix(in oklab, var(--accent), white 70%);
|
||||
}
|
||||
.message-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.composer {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface);
|
||||
padding: 14px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.input {
|
||||
min-height: 52px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 8%);
|
||||
padding: 0 14px;
|
||||
color: var(--fg);
|
||||
width: 100%;
|
||||
}
|
||||
.input:focus-visible,
|
||||
.quick-item:focus-visible,
|
||||
.history-item:focus-visible,
|
||||
.btn:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
.btn-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.btn {
|
||||
min-height: 44px;
|
||||
padding: 0 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
}
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent-on);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
}
|
||||
.source-item strong,
|
||||
.history-item strong,
|
||||
.quick-item strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.citation {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
@media (max-width: 1320px) {
|
||||
.shell { grid-template-columns: 1fr; }
|
||||
.hero { flex-direction: column; align-items: start; }
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.page { padding: 12px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page="chat">
|
||||
<div class="app-shell">
|
||||
<!-- ── Sidebar ── -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-brand">
|
||||
<div class="brand-logo">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M3 5.5h18v2H3zm8 2h2v11h-2zm-5 3h3v2H6zm9 0h3v2h-3zm-5 4h2v2h-2z" fill="currentColor" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sidebar-brand-name">T-Systems</div>
|
||||
<div class="sidebar-brand-sub">Regulation Hub</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav" aria-label="Primary">
|
||||
<!-- 主导航 -->
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">主导航</span>
|
||||
<a class="nav-item" href="index.html">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M3 9.5L12 3l9 6.5V20a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9.5z"/><path d="M9 21V12h6v9"/>
|
||||
</svg>
|
||||
概览
|
||||
</a>
|
||||
<a class="nav-item" href="dashboard.html">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
|
||||
</svg>
|
||||
系统状态
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 工作台 -->
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">工作台</span>
|
||||
<a class="nav-item" href="document-management.html">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="16" y2="17"/>
|
||||
</svg>
|
||||
文档管理
|
||||
</a>
|
||||
<a class="nav-item" href="compliance-analysis.html">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
|
||||
</svg>
|
||||
合规分析
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 对话 -->
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">对话</span>
|
||||
<a class="nav-item active" href="regulation-chat.html">
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
||||
</svg>
|
||||
法规对话
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="avatar">张</div>
|
||||
<div class="sidebar-user-info">
|
||||
<div class="sidebar-user-name">张工程师</div>
|
||||
<div class="sidebar-user-role">compliance.eng</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="sidebar-action" type="button" data-od-theme aria-label="Toggle color theme">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
||||
</svg>
|
||||
主题: 自动
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ── Content area ── -->
|
||||
<div class="content-area">
|
||||
<header class="content-topbar">
|
||||
<span class="topbar-title">法规对话</span>
|
||||
<div class="footer-status">
|
||||
<span class="footer-dot" aria-hidden="true"></span>
|
||||
<span style="font-size:12px; color:var(--muted); font-family:var(--font-mono);">Session synced</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="page">
|
||||
<section class="hero" data-od-id="chat-hero">
|
||||
<div>
|
||||
<div class="eyebrow">Regulation chat</div>
|
||||
<h1>Ask clause-level questions with citations intact.</h1>
|
||||
<p>The chat surface is tuned for compliance follow-up after analysis. Conversation history stays visible, quick prompts speed common queries, and the right rail keeps cited standards inspectable while the answer streams.</p>
|
||||
</div>
|
||||
<div style="display:flex; gap:12px; flex-wrap:wrap;">
|
||||
<a class="pill" href="compliance-analysis.html">From analysis workspace</a>
|
||||
<span class="status ok">Session synced</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="shell" data-od-id="chat-shell">
|
||||
<aside class="card">
|
||||
<div class="section-head">
|
||||
<h2>History</h2>
|
||||
<span class="helper">3 recent threads</span>
|
||||
</div>
|
||||
<div class="history">
|
||||
<div class="history-item active" tabindex="0">
|
||||
<strong>Roof crush resistance wording</strong>
|
||||
<div class="helper">Started 10:14 · 5 replies · linked to GB 26112-2010</div>
|
||||
</div>
|
||||
<div class="history-item" tabindex="0">
|
||||
<strong>R155 incident log retention</strong>
|
||||
<div class="helper">Started yesterday · 8 replies · cybersecurity watch</div>
|
||||
</div>
|
||||
<div class="history-item" tabindex="0">
|
||||
<strong>Thermal runaway evidence checklist</strong>
|
||||
<div class="helper">Started Monday · 4 replies · supplier dossier</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-head" style="margin-top:20px;">
|
||||
<h2>Quick questions</h2>
|
||||
<span class="helper">Common prompts</span>
|
||||
</div>
|
||||
<div class="quick-list">
|
||||
<button class="quick-item" type="button"><strong>What exact numeric threshold is required here?</strong><span class="helper">Turn vague compliance language into a requirement statement.</span></button>
|
||||
<button class="quick-item" type="button"><strong>Which citations are mandatory versus contextual?</strong><span class="helper">Separate legal requirement from scoring or best practice references.</span></button>
|
||||
<button class="quick-item" type="button"><strong>Draft replacement wording for the dossier.</strong><span class="helper">Generate an auditable sentence that cites the source clause.</span></button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section class="chat-column">
|
||||
<div class="card">
|
||||
<div class="section-head">
|
||||
<h2>Active thread</h2>
|
||||
<span class="helper">Chunk 148 · GB 26112-2010 context</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="messages">
|
||||
<article class="message user">
|
||||
<div class="message-top">
|
||||
<strong>Reviewer</strong>
|
||||
<span class="helper mono">10:14</span>
|
||||
</div>
|
||||
<p>Does this paragraph need a numeric load statement, or is the current "meets national crush-resistance requirements" wording enough?</p>
|
||||
</article>
|
||||
|
||||
<article class="message assistant">
|
||||
<div class="message-top">
|
||||
<strong>Compliance assistant</strong>
|
||||
<span class="helper mono">10:14</span>
|
||||
</div>
|
||||
<p>The current wording is not sufficient on its own. The retrieved primary citation, <strong>GB 26112-2010 §4.2</strong>, defines a measurable resistance requirement. To make the dossier auditable, the paragraph should state the tested load threshold and link the supporting report.</p>
|
||||
<div class="citation">
|
||||
Primary citation: GB 26112-2010 §4.2 · lead match for the current paragraph · linked body engineering report candidate
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="message user">
|
||||
<div class="message-top">
|
||||
<strong>Reviewer</strong>
|
||||
<span class="helper mono">10:16</span>
|
||||
</div>
|
||||
<p>Draft the exact replacement sentence I can hand back to the body structure team.</p>
|
||||
</article>
|
||||
|
||||
<article class="message assistant">
|
||||
<div class="message-top">
|
||||
<strong>Compliance assistant</strong>
|
||||
<span class="helper mono">10:16</span>
|
||||
</div>
|
||||
<p>Use: "The roof support structure was validated in accordance with <strong>GB 26112-2010 §4.2</strong>; supporting evidence is documented in the linked body engineering report."</p>
|
||||
<div class="citation">
|
||||
Evidence used: GB 26112-2010 §4.2, internal engineering report reference, related C-NCAP context kept as secondary only.
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<form class="composer">
|
||||
<input class="input" type="text" value="Which part of this answer is mandatory compliance language and which part is supporting evidence?" aria-label="Chat input" />
|
||||
<div class="btn-row">
|
||||
<div class="helper">Citations stay attached to each answer and are exportable with the thread.</div>
|
||||
<button class="btn btn-primary" type="submit">Send question</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<aside class="card">
|
||||
<div class="section-head">
|
||||
<h2>Citation rail</h2>
|
||||
<span class="helper">Sources in current answer</span>
|
||||
</div>
|
||||
<div class="sources">
|
||||
<div class="source-item">
|
||||
<strong>GB 26112-2010 §4.2</strong>
|
||||
<div class="helper">Primary mandatory requirement used to justify the numeric threshold.</div>
|
||||
<div class="citation">Reason surfaced: direct match on roof crush resistance wording and the validation requirement.</div>
|
||||
</div>
|
||||
<div class="source-item">
|
||||
<strong>Linked body engineering report</strong>
|
||||
<div class="helper">Internal evidence artifact proposed for linking into the final dossier.</div>
|
||||
<div class="citation">The relevant static load summary should be attached before final sign-off.</div>
|
||||
</div>
|
||||
<div class="source-item">
|
||||
<strong>C-NCAP rulebook §3.1</strong>
|
||||
<div class="helper">Contextual safety framing only, not the basis of the compliance statement.</div>
|
||||
<div class="citation">Kept in thread for reviewer context but not recommended as the lead citation.</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<span>T-Systems Regulation</span>
|
||||
<div class="footer-status">
|
||||
<span>Desktop Web</span>
|
||||
<span class="footer-dot" aria-hidden="true"></span>
|
||||
<span>Online</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<script src="ui-preferences.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
582
Prototype/cc29bcb0-df2d-4d50-9428-7caa406ecb29/upload-modal.html
Normal file
582
Prototype/cc29bcb0-df2d-4d50-9428-7caa406ecb29/upload-modal.html
Normal file
@@ -0,0 +1,582 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>AI + Compliance Hub - Upload Documents</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #fafafa;
|
||||
--surface: #ffffff;
|
||||
--surface-warm: var(--surface);
|
||||
--fg: #111111;
|
||||
--fg-2: var(--fg);
|
||||
--muted: #6b6b6b;
|
||||
--meta: var(--muted);
|
||||
--border: #e5e5e5;
|
||||
--border-soft: var(--border);
|
||||
--primary: #e20074;
|
||||
--accent: var(--primary);
|
||||
--accent-on: #ffffff;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), black 8%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 14%);
|
||||
--success: #17a34a;
|
||||
--warn: #eab308;
|
||||
--danger: #dc2626;
|
||||
--font-display: "TeleNeoWeb-Bold", "TeleNeoWeb-Medium", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-body: "TeleNeoWeb-Regular", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
||||
--text-xs: 12px;
|
||||
--text-sm: 14px;
|
||||
--text-base: 16px;
|
||||
--text-lg: 20px;
|
||||
--text-xl: 24px;
|
||||
--text-2xl: 32px;
|
||||
--text-3xl: 48px;
|
||||
--text-4xl: 64px;
|
||||
--leading-body: 1.5;
|
||||
--leading-tight: 1.2;
|
||||
--tracking-display: -0.01em;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-12: 48px;
|
||||
--space-20: 80px;
|
||||
--section-y-desktop: 80px;
|
||||
--section-y-tablet: 48px;
|
||||
--section-y-phone: 32px;
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-pill: 9999px;
|
||||
--elev-flat: none;
|
||||
--elev-ring: 0 0 0 1px var(--border);
|
||||
--elev-raised: 0 2px 8px color-mix(in oklab, var(--fg), transparent 92%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 70%);
|
||||
--motion-fast: 150ms;
|
||||
--motion-base: 200ms;
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
--container-max: 1200px;
|
||||
--container-gutter-desktop: 24px;
|
||||
--container-gutter-tablet: 16px;
|
||||
--container-gutter-phone: 12px;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #0f1014;
|
||||
--surface: #17181d;
|
||||
--surface-warm: #1d1f26;
|
||||
--fg: #f5f7fb;
|
||||
--fg-2: #e5e8ef;
|
||||
--muted: #a2a9b8;
|
||||
--meta: #858d9c;
|
||||
--border: #2a2d35;
|
||||
--border-soft: #21242c;
|
||||
--accent-hover: color-mix(in oklab, var(--accent), white 12%);
|
||||
--accent-active: color-mix(in oklab, var(--accent), black 6%);
|
||||
--success: #22c55e;
|
||||
--warn: #facc15;
|
||||
--danger: #f87171;
|
||||
--elev-raised: 0 14px 36px color-mix(in oklab, black, transparent 74%);
|
||||
--focus-ring: 0 0 0 3px color-mix(in oklab, var(--accent), transparent 56%);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background:
|
||||
linear-gradient(180deg, color-mix(in oklab, var(--bg), var(--fg) 4%), var(--bg)),
|
||||
var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--leading-body);
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
margin: 0;
|
||||
font-family: var(--font-display);
|
||||
line-height: var(--leading-tight);
|
||||
letter-spacing: var(--tracking-display);
|
||||
}
|
||||
p { margin: 0; text-wrap: pretty; }
|
||||
button, input, select, textarea { font: inherit; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
a:hover { color: var(--fg); text-decoration: underline; }
|
||||
.od-theme-toggle {
|
||||
cursor: pointer;
|
||||
transition: background var(--motion-fast) var(--ease-standard), border-color var(--motion-fast) var(--ease-standard), color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
.od-theme-toggle:hover {
|
||||
color: var(--fg);
|
||||
border-color: var(--fg);
|
||||
text-decoration: none;
|
||||
}
|
||||
.od-theme-toggle:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: 36px;
|
||||
padding: 0 24px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
background: color-mix(in oklab, var(--bg), var(--surface) 12%);
|
||||
}
|
||||
.footer-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.footer-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #19d3a2;
|
||||
box-shadow: 0 0 0 4px color-mix(in oklab, #19d3a2, transparent 84%);
|
||||
}
|
||||
.frame {
|
||||
width: min(1160px, 100%);
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
.topline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
.back {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.modal {
|
||||
display: grid;
|
||||
grid-template-columns: 1.15fr 0.85fr;
|
||||
gap: 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
background: var(--surface);
|
||||
box-shadow: var(--elev-raised);
|
||||
}
|
||||
.panel {
|
||||
padding: 28px;
|
||||
min-width: 0;
|
||||
}
|
||||
.panel + .panel {
|
||||
border-left: 1px solid var(--border);
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 24%);
|
||||
}
|
||||
.eyebrow {
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.lead {
|
||||
color: var(--muted);
|
||||
margin-top: 12px;
|
||||
max-width: 56ch;
|
||||
}
|
||||
.dropzone {
|
||||
margin-top: 24px;
|
||||
border: 1px dashed color-mix(in oklab, var(--accent), white 54%);
|
||||
border-radius: var(--radius-lg);
|
||||
background: color-mix(in oklab, var(--accent), white 94%);
|
||||
padding: 30px;
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
min-height: 220px;
|
||||
align-content: center;
|
||||
justify-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
.drop-visual {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid color-mix(in oklab, var(--accent), white 48%);
|
||||
background: var(--surface);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--accent);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.btn-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.btn {
|
||||
min-height: 44px;
|
||||
padding: 0 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
transition: background var(--motion-fast) var(--ease-standard), border-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
.btn:hover { border-color: var(--fg); }
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent-on);
|
||||
}
|
||||
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
||||
.theme-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
min-height: 40px;
|
||||
padding: 0 12px;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
transition: background var(--motion-fast) var(--ease-standard), border-color var(--motion-fast) var(--ease-standard), color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
.theme-toggle:hover {
|
||||
color: var(--fg);
|
||||
border-color: var(--fg);
|
||||
text-decoration: none;
|
||||
}
|
||||
.theme-toggle:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
.btn:focus-visible,
|
||||
.field input:focus-visible,
|
||||
.field select:focus-visible,
|
||||
.field textarea:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
}
|
||||
.selected-files {
|
||||
margin-top: 22px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.file-row,
|
||||
.queue-row {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface);
|
||||
padding: 14px 16px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.file-top,
|
||||
.queue-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.meta,
|
||||
.hint {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.mono {
|
||||
font-family: var(--font-mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-top: 22px;
|
||||
}
|
||||
.field {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
.field label {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.field input,
|
||||
.field select,
|
||||
.field textarea {
|
||||
min-height: 44px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface);
|
||||
padding: 0 12px;
|
||||
color: var(--fg);
|
||||
}
|
||||
.field textarea {
|
||||
min-height: 104px;
|
||||
padding: 12px;
|
||||
resize: vertical;
|
||||
}
|
||||
.status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: fit-content;
|
||||
padding: 4px 10px;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
.status::before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: currentColor;
|
||||
}
|
||||
.status.ok { color: var(--success); }
|
||||
.status.warn { color: color-mix(in oklab, var(--warn), black 24%); }
|
||||
.status.risk { color: var(--danger); }
|
||||
.progress {
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in oklab, var(--fg), transparent 95%);
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress > span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: color-mix(in oklab, var(--accent), white 26%);
|
||||
border-radius: inherit;
|
||||
}
|
||||
.queue {
|
||||
margin-top: 18px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
.summary {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 22px;
|
||||
}
|
||||
.summary-card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in oklab, var(--surface), var(--bg) 12%);
|
||||
padding: 14px 16px;
|
||||
}
|
||||
@media (max-width: 980px) {
|
||||
.modal,
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
.panel + .panel { border-left: 0; border-top: 1px solid var(--border); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page="upload">
|
||||
<div class="frame">
|
||||
<div class="topline">
|
||||
<a class="back" href="document-management.html">← Back to document management</a>
|
||||
<span class="hint">Modal review surface for intake, metadata, and queue behavior</span>
|
||||
</div>
|
||||
|
||||
<main class="modal">
|
||||
<section class="panel" data-od-id="upload-form">
|
||||
<div class="eyebrow">Upload documents</div>
|
||||
<h1>Stage files for parsing and indexing.</h1>
|
||||
<p class="lead">This intake flow supports PDF, DOCX, and supplier evidence bundles. Metadata is captured up front so the downstream parser and retrieval pipeline stay normalized.</p>
|
||||
|
||||
<div class="dropzone">
|
||||
<div class="drop-visual">PDF</div>
|
||||
<h2 style="font-size:28px;">Drop files here or browse your local archive.</h2>
|
||||
<p class="hint">Recommended per batch: up to 20 files, 200 MB combined, one regulation family per batch.</p>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-primary">Choose files</button>
|
||||
<button class="btn">Paste object storage link</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="selected-files">
|
||||
<div class="file-row">
|
||||
<div class="file-top">
|
||||
<div>
|
||||
<strong>GBT31484_revision_notes.pdf</strong>
|
||||
<div class="meta mono">size pending validation · scanned appendix included</div>
|
||||
</div>
|
||||
<span class="status ok">Ready</span>
|
||||
</div>
|
||||
<div class="progress"><span style="width:100%"></span></div>
|
||||
</div>
|
||||
<div class="file-row">
|
||||
<div class="file-top">
|
||||
<div>
|
||||
<strong>supplier_thermal_test_report.docx</strong>
|
||||
<div class="meta mono">size pending validation · confidential evidence supplement</div>
|
||||
</div>
|
||||
<span class="status warn">Metadata</span>
|
||||
</div>
|
||||
<div class="progress"><span style="width:58%"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<label for="regulation-type">Regulation type</label>
|
||||
<select id="regulation-type">
|
||||
<option selected>Battery safety</option>
|
||||
<option>Vehicle safety</option>
|
||||
<option>Cybersecurity</option>
|
||||
<option>Charging</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="version">Version / release</label>
|
||||
<input id="version" type="text" value="current revision" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="owner">Owning team</label>
|
||||
<select id="owner">
|
||||
<option selected>Battery Safety Team</option>
|
||||
<option>Connected Fleet</option>
|
||||
<option>Homologation Office</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="parser">Parser backend</label>
|
||||
<select id="parser">
|
||||
<option selected>Aliyun parser</option>
|
||||
<option>Legacy local parser</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field" style="grid-column:1 / -1;">
|
||||
<label for="notes">Reviewer note</label>
|
||||
<textarea id="notes">Re-run this batch against the latest battery energy density requirements and keep supplier evidence attached to the same review thread.</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-row" style="margin-top:24px;">
|
||||
<button class="btn">Save draft batch</button>
|
||||
<button class="btn btn-primary">Start import queue</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="panel" data-od-id="upload-queue">
|
||||
<div class="eyebrow">Import queue</div>
|
||||
<h2 style="font-size:28px;">Batch progress and validation feedback</h2>
|
||||
<p class="lead" style="font-size:16px;">Reviewers can monitor preflight checks before the parser job is submitted, then watch queue depth and failure mode without leaving the modal.</p>
|
||||
|
||||
<div class="summary">
|
||||
<div class="summary-card">
|
||||
<strong class="mono">2 files validated</strong>
|
||||
<div class="hint">Files already validated for metadata completeness</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<strong class="mono">live queue</strong>
|
||||
<div class="hint">Current Aliyun parser queue depth populates here</div>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<strong class="mono">text-embedding-v3</strong>
|
||||
<div class="hint">Embedding target applied after semantic block extraction</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="queue">
|
||||
<div class="queue-row">
|
||||
<div class="queue-top">
|
||||
<div>
|
||||
<strong>Preflight validation</strong>
|
||||
<div class="meta">Duplicate ID scan, file-type validation, metadata completeness</div>
|
||||
</div>
|
||||
<span class="status ok">Passed</span>
|
||||
</div>
|
||||
<div class="progress"><span style="width:100%"></span></div>
|
||||
</div>
|
||||
|
||||
<div class="queue-row">
|
||||
<div class="queue-top">
|
||||
<div>
|
||||
<strong>Object storage upload</strong>
|
||||
<div class="meta">Bucket `upload-files` · transient object names attached to batch</div>
|
||||
</div>
|
||||
<span class="status ok">Completed</span>
|
||||
</div>
|
||||
<div class="progress"><span style="width:100%"></span></div>
|
||||
</div>
|
||||
|
||||
<div class="queue-row">
|
||||
<div class="queue-top">
|
||||
<div>
|
||||
<strong>Aliyun parse submission</strong>
|
||||
<div class="meta">Polling every <span class="mono">5s</span> · timeout <span class="mono">900s</span></div>
|
||||
</div>
|
||||
<span class="status warn">Queued</span>
|
||||
</div>
|
||||
<div class="progress"><span style="width:42%"></span></div>
|
||||
</div>
|
||||
|
||||
<div class="queue-row">
|
||||
<div class="queue-top">
|
||||
<div>
|
||||
<strong>Chunking + embedding</strong>
|
||||
<div class="meta">Build semantic blocks, overlapping vector chunks, then 1024-d embeddings</div>
|
||||
</div>
|
||||
<span class="status warn">Waiting</span>
|
||||
</div>
|
||||
<div class="progress"><span style="width:12%"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-card" style="margin-top:18px;">
|
||||
<strong>Potential issue to resolve</strong>
|
||||
<p class="hint" style="margin-top:8px;">`supplier_thermal_test_report.docx` lacks a formal standard number. The batch can continue, but retrieval quality improves if you assign an evidence relationship before submit.</p>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
</div>
|
||||
<script src="ui-preferences.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
709
Prototype/dashboard-sidebar.html
Normal file
709
Prototype/dashboard-sidebar.html
Normal file
@@ -0,0 +1,709 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>System Status — T-Systems Regulation Hub</title>
|
||||
<style>
|
||||
/* ─── Design tokens ─────────────────────────────────────────────── */
|
||||
:root {
|
||||
/* Sidebar (light rail) */
|
||||
--rail-bg: #ffffff;
|
||||
--rail-surface: #f7f8fa;
|
||||
--rail-fg: #111827;
|
||||
--rail-muted: #8b929e;
|
||||
--rail-border: #e8eaed;
|
||||
--rail-hover: rgba(0,0,0,.04);
|
||||
--rail-active: rgba(226,0,116,.07);
|
||||
|
||||
/* Canvas (content area) */
|
||||
--bg: #f2f4f7;
|
||||
--surface: #ffffff;
|
||||
--fg: #111827;
|
||||
--muted: #6b7280;
|
||||
--border: #e5e7eb;
|
||||
|
||||
/* Brand */
|
||||
--accent: #e20074;
|
||||
--accent-dim: rgba(226,0,116,.10);
|
||||
--accent-hover: #c8006a;
|
||||
--success: #16a34a;
|
||||
--warn: #d97706;
|
||||
--danger: #dc2626;
|
||||
|
||||
/* Type */
|
||||
--font-display: "TeleNeoWeb-Bold","Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
||||
--font-body: "TeleNeoWeb-Regular","Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
||||
--font-mono: ui-monospace,"JetBrains Mono",Menlo,monospace;
|
||||
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-pill: 9999px;
|
||||
--sidebar-w: 232px;
|
||||
--shadow-card: 0 1px 4px rgba(0,0,0,.06), 0 0 0 1px rgba(0,0,0,.04);
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,.05);
|
||||
}
|
||||
|
||||
/* ─── Reset ────────────────────────────────────────────────────── */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; }
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
h1,h2,h3,h4 { font-family: var(--font-display); line-height: 1.2; letter-spacing: -0.015em; text-wrap: balance; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
button, input, select { font: inherit; cursor: pointer; }
|
||||
|
||||
/* ─── App shell ─────────────────────────────────────────────────── */
|
||||
.app-shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
|
||||
|
||||
/* ─── Sidebar ───────────────────────────────────────────────────── */
|
||||
.sidebar {
|
||||
position: sticky; top: 0; height: 100vh;
|
||||
overflow-y: auto; overflow-x: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
background: var(--rail-bg);
|
||||
border-right: 1px solid var(--rail-border);
|
||||
z-index: 20;
|
||||
}
|
||||
.sidebar-brand {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
height: 54px; padding: 0 16px;
|
||||
border-bottom: 1px solid var(--rail-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.brand-logo {
|
||||
width: 28px; height: 28px;
|
||||
background: var(--accent);
|
||||
border-radius: 7px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.brand-logo svg { color: #fff; }
|
||||
.brand-name { font-family: var(--font-display); font-size: 13px; color: var(--rail-fg); line-height: 1.2; font-weight: 700; }
|
||||
.brand-sub { font-size: 10px; color: var(--rail-muted); font-family: var(--font-mono); letter-spacing: .04em; margin-top: 2px; }
|
||||
|
||||
.sidebar-nav { flex: 1; padding: 10px 0; }
|
||||
.nav-group { padding: 0 8px 4px; }
|
||||
.nav-group + .nav-group { margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--rail-border); }
|
||||
.nav-group-label {
|
||||
display: block;
|
||||
font-family: var(--font-mono); font-size: 10px;
|
||||
text-transform: uppercase; letter-spacing: .12em;
|
||||
color: var(--rail-muted);
|
||||
padding: 0 8px 6px;
|
||||
}
|
||||
.nav-item {
|
||||
display: flex; align-items: center; gap: 9px;
|
||||
height: 34px; padding: 0 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
color: #4b5563;
|
||||
font-size: 13px;
|
||||
transition: background 120ms, color 120ms;
|
||||
position: relative;
|
||||
}
|
||||
.nav-item:hover { background: var(--rail-hover); color: var(--rail-fg); text-decoration: none; }
|
||||
.nav-item.active {
|
||||
background: var(--rail-active);
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
.nav-item.active::before {
|
||||
content: "";
|
||||
position: absolute; left: 0; top: 6px; bottom: 6px;
|
||||
width: 3px; border-radius: 0 3px 3px 0;
|
||||
background: var(--accent);
|
||||
}
|
||||
.nav-icon { width: 15px; height: 15px; flex-shrink: 0; opacity: .55; }
|
||||
.nav-item:hover .nav-icon,
|
||||
.nav-item.active .nav-icon { opacity: 1; }
|
||||
.nav-badge {
|
||||
margin-left: auto;
|
||||
min-width: 18px; height: 17px; padding: 0 5px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--accent-dim); color: var(--accent);
|
||||
font-size: 10px; font-family: var(--font-mono); font-weight: 700;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
border-top: 1px solid var(--rail-border);
|
||||
padding: 10px 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar-user {
|
||||
display: flex; align-items: center; gap: 9px;
|
||||
padding: 8px; border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: background 120ms;
|
||||
}
|
||||
.sidebar-user:hover { background: var(--rail-hover); }
|
||||
.avatar {
|
||||
width: 28px; height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent); color: #fff;
|
||||
font-size: 11px; font-weight: 700;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.user-name { font-size: 12px; font-weight: 600; color: var(--rail-fg); }
|
||||
.user-role { font-size: 10px; color: var(--rail-muted); font-family: var(--font-mono); margin-top: 1px; }
|
||||
.sidebar-action {
|
||||
display: flex; align-items: center; gap: 9px;
|
||||
height: 32px; padding: 0 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--rail-muted); font-size: 12px;
|
||||
border: none; background: transparent; width: 100%;
|
||||
text-align: left;
|
||||
transition: background 120ms, color 120ms;
|
||||
}
|
||||
.sidebar-action:hover { background: var(--rail-hover); color: var(--rail-fg); }
|
||||
|
||||
/* ─── Content area ──────────────────────────────────────────────── */
|
||||
.content-area { display: flex; flex-direction: column; min-width: 0; min-height: 100vh; }
|
||||
|
||||
.topbar {
|
||||
position: sticky; top: 0; z-index: 10;
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
height: 54px; padding: 0 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: rgba(242,244,247,.9);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.topbar-title { font-weight: 600; font-size: 14px; flex: 1; color: var(--fg); }
|
||||
.search {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
background: var(--surface);
|
||||
padding: 0 10px; height: 32px; width: 240px;
|
||||
transition: border-color 140ms;
|
||||
}
|
||||
.search:focus-within { border-color: color-mix(in oklab, var(--accent), transparent 60%); }
|
||||
.search input { border: 0; outline: none; background: transparent; width: 100%; color: var(--fg); font-size: 13px; }
|
||||
.search input::placeholder { color: var(--muted); }
|
||||
|
||||
.btn {
|
||||
height: 32px; border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0 12px;
|
||||
background: var(--surface); color: var(--fg);
|
||||
font-size: 13px; font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 120ms, border-color 120ms;
|
||||
white-space: nowrap;
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
}
|
||||
.btn:hover { background: var(--bg); border-color: #b0b7c0; }
|
||||
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
||||
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
||||
|
||||
/* ─── Page ──────────────────────────────────────────────────────── */
|
||||
.page { padding: 20px; display: grid; gap: 18px; flex: 1; }
|
||||
|
||||
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; }
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono); font-size: 10px;
|
||||
text-transform: uppercase; letter-spacing: .1em;
|
||||
color: var(--accent); margin-bottom: 6px;
|
||||
}
|
||||
.page-head h1 { font-size: clamp(20px, 2.4vw, 28px); }
|
||||
.page-head-desc { margin-top: 4px; font-size: 13px; color: var(--muted); max-width: 520px; line-height: 1.55; }
|
||||
|
||||
/* ─── Cards ─────────────────────────────────────────────────────── */
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-card);
|
||||
padding: 16px 18px;
|
||||
}
|
||||
|
||||
/* ─── Stats row ─────────────────────────────────────────────────── */
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 12px; }
|
||||
.stat-card {
|
||||
border-top: 2px solid var(--border);
|
||||
transition: border-color 200ms;
|
||||
}
|
||||
.stat-card:hover { border-top-color: var(--accent); }
|
||||
.stat-card .s-label {
|
||||
font-family: var(--font-mono); font-size: 10px;
|
||||
text-transform: uppercase; letter-spacing: .1em; color: var(--muted);
|
||||
}
|
||||
.stat-card .s-value {
|
||||
margin-top: 10px;
|
||||
font-size: 32px; line-height: 1;
|
||||
font-family: var(--font-display);
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--fg);
|
||||
}
|
||||
.stat-card .s-sub { margin-top: 8px; font-size: 11px; color: var(--muted); line-height: 1.5; }
|
||||
|
||||
/* ─── Two-column panel grid ─────────────────────────────────────── */
|
||||
.panel-grid { display: grid; grid-template-columns: 1.4fr 0.9fr; gap: 18px; }
|
||||
.stack { display: grid; gap: 18px; }
|
||||
|
||||
/* ─── Section heads ─────────────────────────────────────────────── */
|
||||
.section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
||||
.section-head h2 { font-size: 16px; }
|
||||
.ghost-link {
|
||||
color: var(--muted); font-size: 12px;
|
||||
padding: 2px 0; border-radius: 4px;
|
||||
transition: color 120ms;
|
||||
}
|
||||
.ghost-link:hover { color: var(--fg); text-decoration: none; }
|
||||
|
||||
/* ─── Task rows ─────────────────────────────────────────────────── */
|
||||
.task-list, .program-list, .event-list { display: grid; gap: 8px; }
|
||||
.task-row, .program-row, .event-row {
|
||||
display: grid; gap: 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px 12px;
|
||||
background: var(--surface);
|
||||
transition: border-color 120ms, box-shadow 120ms;
|
||||
}
|
||||
.task-row:hover, .program-row:hover {
|
||||
border-color: #c8cdd8;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.task-row { grid-template-columns: 1.6fr 0.8fr 0.8fr 0.6fr; align-items: center; }
|
||||
.program-row{ grid-template-columns: 1fr auto; align-items: start; }
|
||||
.event-row { grid-template-columns: 76px 1fr; align-items: start; }
|
||||
|
||||
/* ─── KPI strip ─────────────────────────────────────────────────── */
|
||||
.kpi-strip { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; margin-top: 12px; }
|
||||
.kpi {
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
padding: 10px 12px; background: #f8f9fb;
|
||||
}
|
||||
.kpi strong { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 17px; color: var(--fg); }
|
||||
.kpi-label { font-size: 11px; color: var(--muted); margin-bottom: 4px; }
|
||||
.meter { height: 3px; border-radius: 999px; background: #e5e7eb; overflow: hidden; margin-top: 8px; }
|
||||
.meter > span { display: block; height: 100%; background: var(--accent); border-radius: inherit; }
|
||||
|
||||
/* ─── Dark mode ────────────────────────────────────────────────── */
|
||||
[data-theme="dark"] {
|
||||
--rail-bg: #1a1c22;
|
||||
--rail-surface: #22242c;
|
||||
--rail-fg: #f0f2f5;
|
||||
--rail-muted: #7a8390;
|
||||
--rail-border: #2d3038;
|
||||
--rail-hover: rgba(255,255,255,.05);
|
||||
--rail-active: rgba(226,0,116,.12);
|
||||
--bg: #111318;
|
||||
--surface: #1a1c22;
|
||||
--fg: #f0f2f5;
|
||||
--muted: #7a8390;
|
||||
--border: #2d3038;
|
||||
}
|
||||
[data-theme="dark"] body { color-scheme: dark; }
|
||||
[data-theme="dark"] .topbar { background: rgba(17,19,24,.9); }
|
||||
[data-theme="dark"] .kpi { background: #1e2028; }
|
||||
[data-theme="dark"] .task-row, [data-theme="dark"] .program-row, [data-theme="dark"] .event-row { background: #1e2028; }
|
||||
|
||||
/* ─── Status pills ──────────────────────────────────────────────── */
|
||||
.status {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
padding: 2px 8px; border-radius: var(--radius-pill);
|
||||
font-size: 11px; font-family: var(--font-mono); font-weight: 600;
|
||||
width: fit-content; white-space: nowrap;
|
||||
}
|
||||
.status::before {
|
||||
content: ""; width: 5px; height: 5px;
|
||||
border-radius: 50%; background: currentColor; flex-shrink: 0;
|
||||
}
|
||||
.status.ok { color: var(--success); background: color-mix(in oklab,var(--success),transparent 90%); }
|
||||
.status.warn { color: var(--warn); background: color-mix(in oklab,var(--warn),transparent 90%); }
|
||||
.status.risk { color: var(--danger); background: color-mix(in oklab,var(--danger),transparent 90%); }
|
||||
|
||||
/* ─── Pill (version tag) ────────────────────────────────────────── */
|
||||
.pill {
|
||||
display: inline-flex; align-items: center;
|
||||
height: 20px; padding: 0 8px;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--muted); font-size: 10px;
|
||||
font-family: var(--font-mono); font-weight: 500;
|
||||
}
|
||||
|
||||
/* ─── Task CTA buttons ──────────────────────────────────────────── */
|
||||
.task-cta {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
height: 26px; padding: 0 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
background: transparent; color: var(--muted);
|
||||
font-size: 11px; font-family: var(--font-body);
|
||||
white-space: nowrap; cursor: pointer;
|
||||
transition: border-color 120ms, color 120ms, background 120ms;
|
||||
}
|
||||
.task-cta:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); text-decoration: none; }
|
||||
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 12px; }
|
||||
.note { font-size: 12px; color: var(--muted); line-height: 1.5; }
|
||||
|
||||
/* ─── Footer ────────────────────────────────────────────────────── */
|
||||
.footer {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 16px;
|
||||
min-height: 34px; padding: 0 20px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted);
|
||||
font-size: 10px; font-family: var(--font-mono);
|
||||
letter-spacing: .1em; text-transform: uppercase;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.footer-live { display: inline-flex; align-items: center; gap: 7px; }
|
||||
.footer-dot {
|
||||
width: 6px; height: 6px; border-radius: 50%;
|
||||
background: #22c55e;
|
||||
box-shadow: 0 0 0 3px rgba(34,197,94,.2);
|
||||
}
|
||||
|
||||
/* ─── Responsive ────────────────────────────────────────────────── */
|
||||
@media (max-width: 1200px) {
|
||||
.stats-grid { grid-template-columns: repeat(2,1fr); }
|
||||
.panel-grid { grid-template-columns: 1fr; }
|
||||
.kpi-strip { grid-template-columns: 1fr 1fr; }
|
||||
.task-row { grid-template-columns: 1fr auto; }
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.app-shell { grid-template-columns: 1fr; }
|
||||
.sidebar { display: none; }
|
||||
.stats-grid, .kpi-strip { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
|
||||
<!-- ─── Sidebar ─────────────────────────────────────────────────────── -->
|
||||
<aside class="sidebar" aria-label="Primary navigation">
|
||||
<div class="sidebar-brand">
|
||||
<div class="brand-logo">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M2 3.5h12v1.5H2zm5.25 1.5h1.5v8h-1.5zm-3 2h2.25v1.5H4.25zm5.25 0h2.25v1.5H9.5zm-3 3h2.5v1.5H6.5z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="brand-name">T-Systems</div>
|
||||
<div class="brand-sub">Regulation Hub</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav" aria-label="Primary">
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">Main</span>
|
||||
<a class="nav-item" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/index.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M2 2h5v5H2zm7 0h5v5H9zM2 9h5v5H2zm7 0h5v5H9z" fill="currentColor" opacity=".6"/>
|
||||
</svg>
|
||||
Overview
|
||||
</a>
|
||||
<a class="nav-item" href="perception.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none">
|
||||
<circle cx="8" cy="8" r="2.5" fill="currentColor"/>
|
||||
<path d="M8 2.5C4.91 2.5 2.5 5.42 2.5 8S4.91 13.5 8 13.5 13.5 10.58 13.5 8 11.09 2.5 8 2.5zm0 9.5C5.52 12 3.5 10.24 3.5 8S5.52 4 8 4s4.5 1.76 4.5 4-2.02 4-4.5 4z" fill="currentColor" opacity=".45"/>
|
||||
</svg>
|
||||
Regulatory Signals
|
||||
<span class="nav-badge">6</span>
|
||||
</a>
|
||||
<a class="nav-item active" href="dashboard-sidebar.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M1.5 2.5h13v1H1.5zm0 3h13v1H1.5zm0 3h8v1h-8zm0 3h6v1h-6z" fill="currentColor"/>
|
||||
</svg>
|
||||
System Status
|
||||
<span class="nav-badge">3</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">Workbench</span>
|
||||
<a class="nav-item" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/document-management.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M3 1h7l3 3v11H3V1zm1 1v12h8V5h-3V2H4zm5 .5V4h1.5L9 1.5zM6 7h4v1H6zm0 2h4v1H6zm0 2h3v1H6z" fill="currentColor"/>
|
||||
</svg>
|
||||
Documents
|
||||
</a>
|
||||
<a class="nav-item" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/compliance-analysis.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M8 1l7 3-1 6a7 7 0 01-6 5A7 7 0 011 10L0 4l8-3zm0 1.2L1.3 4.8l.8 5.1A6 6 0 008 14.8a6 6 0 005.9-4.9l.8-5.1L8 2.2zM7.5 5h1v4.5h-1V5zm0 5.5h1v1h-1v-1z" fill="currentColor"/>
|
||||
</svg>
|
||||
Compliance Analysis
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">Chat</span>
|
||||
<a class="nav-item" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/regulation-chat.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M2 2h12a1 1 0 011 1v8a1 1 0 01-1 1H5l-3 2.5V3a1 1 0 011-1zm0 1v9.5L4.5 11H14V3H2zm2 2h8v1H4zm0 2h6v1H4z" fill="currentColor"/>
|
||||
</svg>
|
||||
Regulation Q&A
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="avatar">TS</div>
|
||||
<div>
|
||||
<div class="user-name">T-Systems User</div>
|
||||
<div class="user-role">Compliance Analyst</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="sidebar-action" type="button" onclick="toggleTheme()">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M8 3a5 5 0 100 10A5 5 0 008 3zM2 8a6 6 0 1112 0A6 6 0 012 8z" fill="currentColor"/>
|
||||
</svg>
|
||||
<span>Dark mode</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ─── Content ──────────────────────────────────────────────────────── -->
|
||||
<div class="content-area">
|
||||
<header class="topbar">
|
||||
<span class="topbar-title">System Status</span>
|
||||
<div class="search">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M6.5 1a5.5 5.5 0 014.23 9.02l3.62 3.62-.7.71-3.63-3.63A5.5 5.5 0 116.5 1zm0 1a4.5 4.5 0 100 9 4.5 4.5 0 000-9z" fill="currentColor" opacity=".4"/>
|
||||
</svg>
|
||||
<input type="search" placeholder="Search regulations, documents…" aria-label="Search" />
|
||||
</div>
|
||||
<button class="btn">Export status</button>
|
||||
<a class="btn btn-primary" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/upload-modal.html">New upload</a>
|
||||
</header>
|
||||
|
||||
<main class="page">
|
||||
<!-- Page head -->
|
||||
<section class="page-head">
|
||||
<div>
|
||||
<div class="eyebrow">System Status</div>
|
||||
<h1>System Status</h1>
|
||||
<p class="page-head-desc">Ingestion pipeline, active compliance programs, and regulatory watch — all in one place.</p>
|
||||
</div>
|
||||
<span class="pill">v1.0.0</span>
|
||||
</section>
|
||||
|
||||
<!-- Stats -->
|
||||
<section class="stats-grid">
|
||||
<article class="card stat-card">
|
||||
<div class="s-label">Documents total</div>
|
||||
<div class="s-value mono" id="ds-docs">—</div>
|
||||
<div class="s-sub">Ingested into the knowledge base</div>
|
||||
</article>
|
||||
<article class="card stat-card">
|
||||
<div class="s-label">Vector chunks</div>
|
||||
<div class="s-value mono" id="ds-chunks">—</div>
|
||||
<div class="s-sub">regulations_dense_1024_v2 serving retrieval</div>
|
||||
</article>
|
||||
<article class="card stat-card">
|
||||
<div class="s-label">High-impact signals</div>
|
||||
<div class="s-value mono" id="ds-high">—</div>
|
||||
<div class="s-sub">Regulatory signals requiring immediate review</div>
|
||||
</article>
|
||||
<article class="card stat-card">
|
||||
<div class="s-label">Last 90 days</div>
|
||||
<div class="s-value mono" id="ds-90d">—</div>
|
||||
<div class="s-sub">Recent regulatory publications</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- Two-column panels -->
|
||||
<section class="panel-grid">
|
||||
<!-- Left column -->
|
||||
<div class="stack">
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Workflow queue</h2>
|
||||
<a class="ghost-link" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/document-management.html">Open documents →</a>
|
||||
</div>
|
||||
<div class="task-list">
|
||||
<div class="task-row">
|
||||
<div>
|
||||
<strong>GB/T 31484-2015 battery density revision</strong>
|
||||
<div class="note">Uploaded by EV Safety Team · version 2026-04 addendum</div>
|
||||
</div>
|
||||
<span class="status warn">Embedding</span>
|
||||
<span class="mono">chunk build active</span>
|
||||
<a class="task-cta" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/document-detail.html">Inspect →</a>
|
||||
</div>
|
||||
<div class="task-row">
|
||||
<div>
|
||||
<strong>UNECE R155 annex interpretation note</strong>
|
||||
<div class="note">Parser artifacts ready · waiting for analyst assignment</div>
|
||||
</div>
|
||||
<span class="status ok">Ready</span>
|
||||
<span class="mono">19 clauses linked</span>
|
||||
<a class="task-cta" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/compliance-analysis.html">Analyze →</a>
|
||||
</div>
|
||||
<div class="task-row">
|
||||
<div>
|
||||
<strong>GB 26112-2010 roof strength scan</strong>
|
||||
<div class="note">OCR confidence dropped below threshold on 6 pages</div>
|
||||
</div>
|
||||
<span class="status risk">Failed</span>
|
||||
<span class="mono">Retry #2</span>
|
||||
<a class="task-cta" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/document-management.html">Resolve →</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Active compliance programs</h2>
|
||||
<a class="ghost-link" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/compliance-analysis.html">Review findings →</a>
|
||||
</div>
|
||||
<div class="program-list">
|
||||
<div class="program-row">
|
||||
<div>
|
||||
<strong>Intelligent cockpit homologation</strong>
|
||||
<p class="note">42 related standards across driver monitoring, EMC, and child safety. Four findings still open for MY27 platform.</p>
|
||||
</div>
|
||||
<span class="status risk">High risk</span>
|
||||
</div>
|
||||
<div class="program-row">
|
||||
<div>
|
||||
<strong>Battery swap certification dossier</strong>
|
||||
<p class="note">Clause mapping complete. Thermal event test evidence package awaiting supplier document refresh.</p>
|
||||
</div>
|
||||
<span class="status warn">Pending</span>
|
||||
</div>
|
||||
<div class="program-row">
|
||||
<div>
|
||||
<strong>Connected fleet cybersecurity</strong>
|
||||
<p class="note">RAG checks aligned with UNECE R155. Chat follow-up requested on remote key rotation obligations.</p>
|
||||
</div>
|
||||
<span class="status ok">On track</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-strip">
|
||||
<div class="kpi">
|
||||
<div class="kpi-label">Retrieval hit rate</div>
|
||||
<strong>87%</strong>
|
||||
<div class="meter"><span style="width:87%"></span></div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="kpi-label">Evidence coverage</div>
|
||||
<strong>72%</strong>
|
||||
<div class="meter"><span style="width:72%"></span></div>
|
||||
</div>
|
||||
<div class="kpi">
|
||||
<div class="kpi-label">Reviewer SLA</div>
|
||||
<strong>18h</strong>
|
||||
<div class="meter"><span style="width:64%"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- Right column -->
|
||||
<div class="stack">
|
||||
<article class="card">
|
||||
<div class="section-head"><h2>System health</h2><a class="ghost-link" href="#">Refresh</a></div>
|
||||
<div class="task-list">
|
||||
<div class="task-row" style="grid-template-columns:1fr auto">
|
||||
<div>
|
||||
<strong>Aliyun parser backend</strong>
|
||||
<div class="note">Poll interval 5 s · timeout 900 s</div>
|
||||
</div>
|
||||
<span class="status warn">Queue depth 7</span>
|
||||
</div>
|
||||
<div class="task-row" style="grid-template-columns:1fr auto">
|
||||
<div>
|
||||
<strong>Embedding model</strong>
|
||||
<div class="note">text-embedding-v3 · dimension 1024</div>
|
||||
</div>
|
||||
<span class="status ok">Healthy</span>
|
||||
</div>
|
||||
<div class="task-row" style="grid-template-columns:1fr auto">
|
||||
<div>
|
||||
<strong>Vector store</strong>
|
||||
<div class="note">Milvus regulations_dense_1024_v2</div>
|
||||
</div>
|
||||
<span class="status ok">Serving</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="section-head">
|
||||
<h2>Regulatory watch</h2>
|
||||
<a class="ghost-link" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/regulation-chat.html">Ask chat →</a>
|
||||
</div>
|
||||
<div class="event-list">
|
||||
<div class="event-row">
|
||||
<span class="mono note">2d ago</span>
|
||||
<div>
|
||||
<strong>GB 38031 thermal propagation draft updated</strong>
|
||||
<p class="note" style="-webkit-line-clamp:2;display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden">Potential impact on current battery enclosure narrative. Evidence gap flagged in two supplier submissions.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-row">
|
||||
<span class="mono note">5d ago</span>
|
||||
<div>
|
||||
<strong>UNECE R155 Q&A added note on incident response logs</strong>
|
||||
<p class="note" style="-webkit-line-clamp:2;display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden">Connected fleet program must confirm retention windows and ownership controls.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-row">
|
||||
<span class="mono note">12d ago</span>
|
||||
<div>
|
||||
<strong>GB/T 18487 charging interface interpretation circulated</strong>
|
||||
<p class="note" style="-webkit-line-clamp:2;display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden">No blocker yet, but three documents should be re-run against the new clause wording.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<span>T-Systems Regulation Hub</span>
|
||||
<div class="footer-live">
|
||||
<span class="footer-dot" aria-hidden="true"></span>
|
||||
<span>Online</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ─── Theme toggle (P0 fix: reads data-theme attribute) ───────────────
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement;
|
||||
const next = html.dataset.theme === 'dark' ? 'light' : 'dark';
|
||||
html.dataset.theme = next;
|
||||
localStorage.setItem('theme', next);
|
||||
const btn = document.querySelector('.sidebar-action');
|
||||
if (btn) btn.querySelector('span') && (btn.querySelector('span').textContent = next === 'dark' ? 'Light mode' : 'Dark mode');
|
||||
}
|
||||
// Restore saved theme
|
||||
(function() {
|
||||
const saved = localStorage.getItem('theme');
|
||||
if (saved) document.documentElement.dataset.theme = saved;
|
||||
})();
|
||||
|
||||
// ─── Live stats from perception API ──────────────────────────────────
|
||||
async function loadDashboardStats() {
|
||||
try {
|
||||
const r = await fetch('http://6.86.80.9:5173/api/v1/perception/stats');
|
||||
if (!r.ok) return;
|
||||
const s = await r.json();
|
||||
const set = (id, val) => { const el = document.getElementById(id); if (el && val != null) el.textContent = val; };
|
||||
set('ds-high', s.high_impact);
|
||||
set('ds-90d', s.recent_90d);
|
||||
set('ds-docs', s.total);
|
||||
} catch(e) { /* silent — fallback to — */ }
|
||||
}
|
||||
loadDashboardStats();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
11
Prototype/index.html
Normal file
11
Prototype/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="refresh" content="0; url=dashboard-sidebar.html" />
|
||||
<title>TSI Regulation Hub</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>location.replace("dashboard-sidebar.html");</script>
|
||||
</body>
|
||||
</html>
|
||||
915
Prototype/perception.html
Normal file
915
Prototype/perception.html
Normal file
@@ -0,0 +1,915 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Regulatory Signals — T-Systems Regulation Hub</title>
|
||||
<style>
|
||||
/* ─── Design tokens (identical to dashboard-sidebar.html) ────────── */
|
||||
:root {
|
||||
--rail-bg: #ffffff;
|
||||
--rail-surface: #f7f8fa;
|
||||
--rail-fg: #111827;
|
||||
--rail-muted: #8b929e;
|
||||
--rail-border: #e8eaed;
|
||||
--rail-hover: rgba(0,0,0,.04);
|
||||
--rail-active: rgba(226,0,116,.07);
|
||||
|
||||
--bg: #f2f4f7;
|
||||
--surface: #ffffff;
|
||||
--fg: #111827;
|
||||
--muted: #6b7280;
|
||||
--border: #e5e7eb;
|
||||
|
||||
--accent: #e20074;
|
||||
--accent-dim: rgba(226,0,116,.10);
|
||||
--accent-hover: #c8006a;
|
||||
--success: #16a34a;
|
||||
--warn: #d97706;
|
||||
--danger: #dc2626;
|
||||
|
||||
--font-display: "TeleNeoWeb-Bold","Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
||||
--font-body: "TeleNeoWeb-Regular","Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
||||
--font-mono: ui-monospace,"JetBrains Mono",Menlo,monospace;
|
||||
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-pill: 9999px;
|
||||
--sidebar-w: 232px;
|
||||
--shadow-card: 0 1px 4px rgba(0,0,0,.06), 0 0 0 1px rgba(0,0,0,.04);
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,.05);
|
||||
}
|
||||
|
||||
/* ─── Reset ─────────────────────────────────────────────────────── */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; }
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
h1,h2,h3,h4 { font-family: var(--font-display); line-height: 1.2; letter-spacing: -0.015em; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
button, input, select { font: inherit; cursor: pointer; }
|
||||
|
||||
/* ─── App shell ──────────────────────────────────────────────────── */
|
||||
.app-shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
|
||||
|
||||
/* ─── Sidebar (light rail — identical to dashboard) ───────────────── */
|
||||
.sidebar {
|
||||
position: sticky; top: 0; height: 100vh;
|
||||
overflow-y: auto; overflow-x: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
background: var(--rail-bg);
|
||||
border-right: 1px solid var(--rail-border);
|
||||
z-index: 20;
|
||||
}
|
||||
.sidebar-brand {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
height: 54px; padding: 0 16px;
|
||||
border-bottom: 1px solid var(--rail-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.brand-logo {
|
||||
width: 28px; height: 28px;
|
||||
background: var(--accent);
|
||||
border-radius: 7px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.brand-logo svg { color: #fff; }
|
||||
.brand-name { font-family: var(--font-display); font-size: 13px; color: var(--rail-fg); line-height: 1.2; font-weight: 700; }
|
||||
.brand-sub { font-size: 10px; color: var(--rail-muted); font-family: var(--font-mono); letter-spacing: .04em; margin-top: 2px; }
|
||||
|
||||
.sidebar-nav { flex: 1; padding: 10px 0; }
|
||||
.nav-group { padding: 0 8px 4px; }
|
||||
.nav-group + .nav-group { margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--rail-border); }
|
||||
.nav-group-label {
|
||||
display: block;
|
||||
font-family: var(--font-mono); font-size: 10px;
|
||||
text-transform: uppercase; letter-spacing: .12em;
|
||||
color: var(--rail-muted);
|
||||
padding: 0 8px 6px;
|
||||
}
|
||||
.nav-item {
|
||||
display: flex; align-items: center; gap: 9px;
|
||||
height: 34px; padding: 0 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
color: #4b5563;
|
||||
font-size: 13px;
|
||||
transition: background 120ms, color 120ms;
|
||||
position: relative;
|
||||
}
|
||||
.nav-item:hover { background: var(--rail-hover); color: var(--rail-fg); text-decoration: none; }
|
||||
.nav-item.active { background: var(--rail-active); color: var(--accent); font-weight: 600; }
|
||||
.nav-item.active::before {
|
||||
content: "";
|
||||
position: absolute; left: 0; top: 6px; bottom: 6px;
|
||||
width: 3px; border-radius: 0 3px 3px 0;
|
||||
background: var(--accent);
|
||||
}
|
||||
.nav-icon { width: 15px; height: 15px; flex-shrink: 0; opacity: .55; }
|
||||
.nav-item:hover .nav-icon,
|
||||
.nav-item.active .nav-icon { opacity: 1; }
|
||||
.nav-badge {
|
||||
margin-left: auto;
|
||||
min-width: 18px; height: 17px; padding: 0 5px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--accent-dim); color: var(--accent);
|
||||
font-size: 10px; font-family: var(--font-mono); font-weight: 700;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
border-top: 1px solid var(--rail-border);
|
||||
padding: 10px 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar-user {
|
||||
display: flex; align-items: center; gap: 9px;
|
||||
padding: 8px; border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: background 120ms;
|
||||
}
|
||||
.sidebar-user:hover { background: var(--rail-hover); }
|
||||
.avatar {
|
||||
width: 28px; height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent); color: #fff;
|
||||
font-size: 11px; font-weight: 700;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.user-name { font-size: 12px; font-weight: 600; color: var(--rail-fg); }
|
||||
.user-role { font-size: 10px; color: var(--rail-muted); font-family: var(--font-mono); margin-top: 1px; }
|
||||
|
||||
/* ─── Content area ───────────────────────────────────────────────── */
|
||||
.content-area { display: flex; flex-direction: column; min-width: 0; min-height: 100vh; }
|
||||
|
||||
.topbar {
|
||||
position: sticky; top: 0; z-index: 10;
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
height: 54px; padding: 0 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: rgba(242,244,247,.9);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.topbar-title { font-weight: 600; font-size: 14px; }
|
||||
.topbar-sub { color: var(--muted); font-family: var(--font-mono); font-size: 10px; margin-left: 4px; font-weight: 400; }
|
||||
|
||||
.btn {
|
||||
height: 32px; border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0 12px;
|
||||
background: var(--surface); color: var(--fg);
|
||||
font-size: 13px; font-weight: 500;
|
||||
transition: background 120ms, border-color 120ms;
|
||||
white-space: nowrap;
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
}
|
||||
.btn:hover { background: var(--bg); border-color: #b0b7c0; }
|
||||
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
||||
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
||||
.btn-primary:disabled { opacity: .45; cursor: not-allowed; }
|
||||
.btn-sm { height: 28px; padding: 0 10px; font-size: 12px; }
|
||||
|
||||
.search {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
background: var(--surface);
|
||||
padding: 0 10px; height: 32px; width: 220px;
|
||||
transition: border-color 140ms;
|
||||
}
|
||||
.search:focus-within { border-color: color-mix(in oklab, var(--accent), transparent 60%); }
|
||||
.search input { border: 0; outline: none; background: transparent; width: 100%; color: var(--fg); font-size: 13px; }
|
||||
.search input::placeholder { color: var(--muted); }
|
||||
|
||||
/* ─── Stats bar ──────────────────────────────────────────────────── */
|
||||
.stats-bar {
|
||||
display: grid; grid-template-columns: repeat(4,1fr);
|
||||
gap: 1px;
|
||||
background: var(--border);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.stat-cell {
|
||||
background: var(--surface);
|
||||
padding: 14px 20px;
|
||||
}
|
||||
.stat-cell .s-label {
|
||||
font-family: var(--font-mono); font-size: 10px;
|
||||
text-transform: uppercase; letter-spacing: .1em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.stat-cell .s-value {
|
||||
margin-top: 6px;
|
||||
font-size: 28px; line-height: 1;
|
||||
font-family: var(--font-display);
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.stat-cell .s-sub { margin-top: 4px; font-size: 11px; color: var(--muted); }
|
||||
|
||||
/* ─── Work area (filter bar + split) ────────────────────────────── */
|
||||
.work-area { flex: 1; display: flex; flex-direction: column; min-height: 0; }
|
||||
|
||||
.filter-bar {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 10px 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
flex-shrink: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.filter-label {
|
||||
font-family: var(--font-mono); font-size: 10px;
|
||||
text-transform: uppercase; letter-spacing: .08em;
|
||||
color: var(--muted);
|
||||
}
|
||||
.chip {
|
||||
padding: 3px 10px;
|
||||
border-radius: var(--radius-pill);
|
||||
border: 1px solid var(--border);
|
||||
background: transparent; color: var(--muted);
|
||||
font-size: 11px; font-family: var(--font-mono);
|
||||
transition: all 100ms;
|
||||
}
|
||||
.chip:hover { border-color: var(--fg); color: var(--fg); }
|
||||
.chip.on { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); font-weight: 600; }
|
||||
.sep { width: 1px; height: 16px; background: var(--border); flex-shrink: 0; margin: 0 2px; }
|
||||
|
||||
/* ─── Two-pane split ─────────────────────────────────────────────── */
|
||||
.perception-split {
|
||||
flex: 1; display: grid;
|
||||
grid-template-columns: 360px 1fr;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* ─── Feed pane ─────────────────────────────────────────────────── */
|
||||
.feed-pane {
|
||||
display: flex; flex-direction: column;
|
||||
border-right: 1px solid var(--border);
|
||||
min-height: 0;
|
||||
background: var(--bg);
|
||||
}
|
||||
.feed-pane-head {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 12px 16px 10px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.feed-pane-head h2 { font-size: 13px; font-weight: 700; }
|
||||
.feed-count { font-family: var(--font-mono); font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .08em; }
|
||||
.feed-scroll {
|
||||
flex: 1; overflow-y: auto;
|
||||
padding: 10px 10px;
|
||||
display: flex; flex-direction: column; gap: 6px;
|
||||
}
|
||||
.feed-scroll::-webkit-scrollbar { width: 4px; }
|
||||
.feed-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
||||
|
||||
/* Event card */
|
||||
.ev-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 11px 13px;
|
||||
cursor: pointer;
|
||||
transition: border-color 100ms, box-shadow 100ms;
|
||||
}
|
||||
.ev-card:hover { border-color: #c4c9d4; }
|
||||
.ev-card.selected {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--accent-dim);
|
||||
}
|
||||
.ev-head { display: flex; align-items: center; gap: 6px; margin-bottom: 7px; }
|
||||
.src-tag {
|
||||
font-size: 10px; font-weight: 700; padding: 2px 6px;
|
||||
border-radius: 4px; font-family: var(--font-mono);
|
||||
}
|
||||
.std-code { font-family: var(--font-mono); font-size: 10px; color: var(--muted); }
|
||||
.ev-title { font-weight: 600; font-size: 12px; line-height: 1.4; margin-bottom: 4px; }
|
||||
.ev-summary {
|
||||
font-size: 11px; color: var(--muted); line-height: 1.5;
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
||||
}
|
||||
.ev-foot { display: flex; align-items: center; gap: 6px; margin-top: 7px; }
|
||||
.ev-date { font-family: var(--font-mono); font-size: 10px; color: var(--muted); }
|
||||
.ev-tag {
|
||||
font-size: 10px; font-family: var(--font-mono);
|
||||
padding: 1px 5px; border-radius: 3px;
|
||||
border: 1px solid var(--border); color: var(--muted);
|
||||
}
|
||||
.imp-dot {
|
||||
margin-left: auto;
|
||||
font-size: 10px; font-family: var(--font-mono);
|
||||
}
|
||||
.loading-msg { font-family: var(--font-mono); font-size: 12px; color: var(--muted); text-align: center; padding: 40px 0; }
|
||||
|
||||
/* ─── Analysis pane ─────────────────────────────────────────────── */
|
||||
.analysis-pane {
|
||||
display: flex; flex-direction: column;
|
||||
min-height: 0; overflow-y: auto;
|
||||
padding: 16px 20px;
|
||||
gap: 12px;
|
||||
}
|
||||
.analysis-pane::-webkit-scrollbar { width: 4px; }
|
||||
.analysis-pane::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
|
||||
|
||||
.analysis-empty {
|
||||
flex: 1; display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
min-height: 300px;
|
||||
}
|
||||
.analysis-empty-ring {
|
||||
width: 40px; height: 40px; border-radius: 50%;
|
||||
border: 1.5px solid var(--border);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.analysis-empty-label { font-size: 13px; }
|
||||
.analysis-empty-hint { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase; letter-spacing: .08em; }
|
||||
|
||||
/* Detail card */
|
||||
.detail-card {
|
||||
background: var(--surface);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-card);
|
||||
padding: 14px 16px;
|
||||
}
|
||||
.detail-head { display: flex; align-items: center; gap: 7px; margin-bottom: 8px; }
|
||||
.detail-title { font-weight: 700; font-size: 14px; line-height: 1.3; margin-bottom: 5px; }
|
||||
.detail-summary { font-size: 13px; color: var(--muted); line-height: 1.6; }
|
||||
.detail-meta { display: flex; gap: 14px; margin-top: 10px; flex-wrap: wrap; }
|
||||
.meta-item { font-family: var(--font-mono); font-size: 10px; color: var(--muted); }
|
||||
.meta-item strong { color: var(--fg); }
|
||||
|
||||
.action-row { display: flex; gap: 8px; align-items: center; flex-shrink: 0; }
|
||||
|
||||
/* Output card */
|
||||
.output-card {
|
||||
background: var(--surface);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-card);
|
||||
padding: 14px 16px;
|
||||
display: none;
|
||||
}
|
||||
.output-head {
|
||||
font-family: var(--font-mono); font-size: 10px;
|
||||
text-transform: uppercase; letter-spacing: .08em;
|
||||
color: var(--muted); margin-bottom: 10px;
|
||||
display: flex; align-items: center; gap: 7px;
|
||||
}
|
||||
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
||||
.blink { animation: blink 1s step-end infinite; }
|
||||
|
||||
.md-h2 { font-size: 13px; font-weight: 700; color: var(--accent); margin: 14px 0 5px; font-family: var(--font-display); }
|
||||
.md-h3 { font-size: 12px; font-weight: 700; margin: 10px 0 3px; }
|
||||
.md-li { display: flex; gap: 7px; margin-bottom: 3px; padding-left: 3px; font-size: 12px; line-height: 1.6; }
|
||||
.md-li-dot { color: var(--accent); flex-shrink: 0; }
|
||||
.md-p { font-size: 12px; line-height: 1.7; margin-bottom: 3px; }
|
||||
.md-empty { height: 5px; }
|
||||
|
||||
/* Docs card */
|
||||
.docs-card {
|
||||
background: var(--surface);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-card);
|
||||
padding: 12px 16px;
|
||||
display: none;
|
||||
}
|
||||
.docs-head {
|
||||
font-family: var(--font-mono); font-size: 10px;
|
||||
text-transform: uppercase; letter-spacing: .08em;
|
||||
color: var(--muted); margin-bottom: 8px;
|
||||
}
|
||||
.doc-row { display: flex; align-items: flex-start; gap: 10px; padding: 7px 0; border-top: 1px solid var(--border); }
|
||||
.doc-row:first-of-type { border-top: none; }
|
||||
.doc-score { font-family: var(--font-mono); font-size: 11px; color: var(--accent); font-weight: 700; flex-shrink: 0; width: 32px; }
|
||||
.doc-name { font-size: 12px; font-weight: 600; line-height: 1.4; }
|
||||
.doc-clause { font-family: var(--font-mono); font-size: 10px; color: var(--muted); }
|
||||
.doc-snippet { font-size: 11px; color: var(--muted); line-height: 1.5; margin-top: 2px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
|
||||
/* Status pills */
|
||||
.status {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
padding: 2px 8px; border-radius: var(--radius-pill);
|
||||
font-size: 10px; font-family: var(--font-mono); font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.status::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
|
||||
.status.ok { color: var(--success); background: color-mix(in oklab,var(--success),transparent 90%); }
|
||||
.status.warn { color: var(--warn); background: color-mix(in oklab,var(--warn),transparent 90%); }
|
||||
.status.risk { color: var(--danger); background: color-mix(in oklab,var(--danger),transparent 90%); }
|
||||
.status.info { color: #3b82f6; background: color-mix(in oklab,#3b82f6,transparent 90%); }
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 16px;
|
||||
min-height: 34px; padding: 0 20px;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--muted); font-size: 10px;
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: .1em; text-transform: uppercase;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.footer-live { display: inline-flex; align-items: center; gap: 7px; }
|
||||
.footer-dot {
|
||||
width: 6px; height: 6px; border-radius: 50%;
|
||||
background: #22c55e;
|
||||
box-shadow: 0 0 0 3px rgba(34,197,94,.2);
|
||||
}
|
||||
|
||||
/* Dark mode */
|
||||
[data-theme="dark"] {
|
||||
--rail-bg: #1a1c22;
|
||||
--rail-surface: #22242c;
|
||||
--rail-fg: #f0f2f5;
|
||||
--rail-muted: #7a8390;
|
||||
--rail-border: #2d3038;
|
||||
--rail-hover: rgba(255,255,255,.05);
|
||||
--rail-active: rgba(226,0,116,.12);
|
||||
--bg: #111318;
|
||||
--surface: #1a1c22;
|
||||
--fg: #f0f2f5;
|
||||
--muted: #7a8390;
|
||||
--border: #2d3038;
|
||||
}
|
||||
[data-theme="dark"] body { color-scheme: dark; }
|
||||
[data-theme="dark"] .topbar { background: rgba(17,19,24,.9); }
|
||||
[data-theme="dark"] .stats-bar { background: var(--border); }
|
||||
[data-theme="dark"] .stat-cell { background: var(--surface); }
|
||||
[data-theme="dark"] .filter-bar { background: var(--surface); }
|
||||
[data-theme="dark"] .ev-card { background: var(--surface); }
|
||||
[data-theme="dark"] .detail-card,
|
||||
[data-theme="dark"] .output-card,
|
||||
[data-theme="dark"] .docs-card { background: var(--surface); }
|
||||
|
||||
/* Sidebar action */
|
||||
.sidebar-action {
|
||||
display: flex; align-items: center; gap: 9px;
|
||||
height: 32px; padding: 0 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--rail-muted); font-size: 12px;
|
||||
border: none; background: transparent; width: 100%;
|
||||
text-align: left; cursor: pointer;
|
||||
transition: background 120ms, color 120ms;
|
||||
}
|
||||
.sidebar-action:hover { background: var(--rail-hover); color: var(--rail-fg); }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1100px) {
|
||||
.stats-bar { grid-template-columns: 1fr 1fr; }
|
||||
.perception-split { grid-template-columns: 300px 1fr; }
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
.app-shell { grid-template-columns: 1fr; }
|
||||
.sidebar { display: none; }
|
||||
.stats-bar { grid-template-columns: 1fr 1fr; }
|
||||
.perception-split { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
|
||||
<!-- ─── Sidebar (dark rail) ─────────────────────────────────────────── -->
|
||||
<aside class="sidebar" aria-label="Primary navigation">
|
||||
<div class="sidebar-brand">
|
||||
<div class="brand-logo">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M2 3.5h12v1.5H2zm5.25 1.5h1.5v8h-1.5zm-3 2h2.25v1.5H4.25zm5.25 0h2.25v1.5H9.5zm-3 3h2.5v1.5H6.5z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="brand-name">T-Systems</div>
|
||||
<div class="brand-sub">Regulation Hub</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav" aria-label="Primary">
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">Main</span>
|
||||
<a class="nav-item" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/index.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M2 2h5v5H2zm7 0h5v5H9zM2 9h5v5H2zm7 0h5v5H9z" fill="currentColor" opacity=".6"/></svg>
|
||||
Overview
|
||||
</a>
|
||||
<a class="nav-item active" href="perception.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none">
|
||||
<circle cx="8" cy="8" r="2.5" fill="currentColor"/>
|
||||
<path d="M8 2.5C4.91 2.5 2.5 5.42 2.5 8S4.91 13.5 8 13.5 13.5 10.58 13.5 8 11.09 2.5 8 2.5zm0 9.5C5.52 12 3.5 10.24 3.5 8S5.52 4 8 4s4.5 1.76 4.5 4-2.02 4-4.5 4z" fill="currentColor" opacity=".45"/>
|
||||
</svg>
|
||||
Regulatory Signals
|
||||
<span class="nav-badge" id="badge-high">—</span>
|
||||
</a>
|
||||
<a class="nav-item" href="dashboard-sidebar.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M1.5 2.5h13v1H1.5zm0 3h13v1H1.5zm0 3h8v1h-8zm0 3h6v1h-6z" fill="currentColor"/></svg>
|
||||
System Status
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">Workbench</span>
|
||||
<a class="nav-item" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/document-management.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M3 1h7l3 3v11H3V1zm1 1v12h8V5h-3V2H4zm5 .5V4h1.5L9 1.5zM6 7h4v1H6zm0 2h4v1H6zm0 2h3v1H6z" fill="currentColor"/></svg>
|
||||
Documents
|
||||
</a>
|
||||
<a class="nav-item" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/compliance-analysis.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M8 1l7 3-1 6a7 7 0 01-6 5A7 7 0 011 10L0 4l8-3zm0 1.2L1.3 4.8l.8 5.1A6 6 0 008 14.8a6 6 0 005.9-4.9l.8-5.1L8 2.2zM7.5 5h1v4.5h-1V5zm0 5.5h1v1h-1v-1z" fill="currentColor"/></svg>
|
||||
Compliance Analysis
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<span class="nav-group-label">Chat</span>
|
||||
<a class="nav-item" href="cc29bcb0-df2d-4d50-9428-7caa406ecb29/regulation-chat.html">
|
||||
<svg class="nav-icon" viewBox="0 0 16 16" fill="none"><path d="M2 2h12a1 1 0 011 1v8a1 1 0 01-1 1H5l-3 2.5V3a1 1 0 011-1zm0 1v9.5L4.5 11H14V3H2zm2 2h8v1H4zm0 2h6v1H4z" fill="currentColor"/></svg>
|
||||
Regulation Q&A
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-user">
|
||||
<div class="avatar">TS</div>
|
||||
<div>
|
||||
<div class="user-name">T-Systems User</div>
|
||||
<div class="user-role">Compliance Analyst</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="sidebar-action" type="button" onclick="toggleTheme()">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M8 3a5 5 0 100 10A5 5 0 008 3zM2 8a6 6 0 1112 0A6 6 0 012 8z" fill="currentColor"/>
|
||||
</svg>
|
||||
<span>Dark mode</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ─── Content ───────────────────────────────────────────────────────── -->
|
||||
<div class="content-area">
|
||||
<!-- Topbar -->
|
||||
<div class="topbar">
|
||||
<span class="topbar-title">
|
||||
Regulatory Signals
|
||||
<span class="topbar-sub">Real-time monitoring · Knowledge-base impact analysis</span>
|
||||
</span>
|
||||
<div class="search">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M6.5 1a5.5 5.5 0 014.23 9.02l3.62 3.62-.7.71-3.63-3.63A5.5 5.5 0 116.5 1zm0 1a4.5 4.5 0 100 9 4.5 4.5 0 000-9z" fill="currentColor" opacity=".4"/>
|
||||
</svg>
|
||||
<input type="search" placeholder="Search signals…" aria-label="Search signals" />
|
||||
</div>
|
||||
<button class="btn btn-sm" onclick="loadFeed()">
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M13.5 8a5.5 5.5 0 11-1.1-3.3" stroke="currentColor" stroke-width="1.4"/><path d="M10 4.5l2.5.2.3-2.7" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Stats bar — inlined (no card borders, flush panel style) -->
|
||||
<div class="stats-bar">
|
||||
<div class="stat-cell">
|
||||
<div class="s-label">Total signals</div>
|
||||
<div class="s-value" id="stat-total">—</div>
|
||||
<div class="s-sub">All regulatory events in feed</div>
|
||||
</div>
|
||||
<div class="stat-cell">
|
||||
<div class="s-label">High impact</div>
|
||||
<div class="s-value" id="stat-high" style="color:var(--danger)">—</div>
|
||||
<div class="s-sub">Requires immediate review</div>
|
||||
</div>
|
||||
<div class="stat-cell">
|
||||
<div class="s-label">Medium impact</div>
|
||||
<div class="s-value" id="stat-med" style="color:var(--warn)">—</div>
|
||||
<div class="s-sub">Scheduled for assessment</div>
|
||||
</div>
|
||||
<div class="stat-cell">
|
||||
<div class="s-label">Last 90 days</div>
|
||||
<div class="s-value" id="stat-90d" style="color:var(--accent)">—</div>
|
||||
<div class="s-sub">Recent publications</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter bar -->
|
||||
<div class="filter-bar">
|
||||
<span class="filter-label">Source</span>
|
||||
<button class="chip on" data-src="" onclick="setSource(this,'')">All</button>
|
||||
<button class="chip" data-src="MIIT" onclick="setSource(this,'MIIT')">MIIT</button>
|
||||
<button class="chip" data-src="UN-ECE" onclick="setSource(this,'UN-ECE')">UN-ECE</button>
|
||||
<button class="chip" data-src="ISO" onclick="setSource(this,'ISO')">ISO</button>
|
||||
<button class="chip" data-src="国标委" onclick="setSource(this,'国标委')">GB Comm.</button>
|
||||
<button class="chip" data-src="EUR-Lex" onclick="setSource(this,'EUR-Lex')">EUR-Lex</button>
|
||||
<button class="chip" data-src="IATF" onclick="setSource(this,'IATF')">IATF</button>
|
||||
<div class="sep"></div>
|
||||
<span class="filter-label">Impact</span>
|
||||
<button class="chip on" data-imp="" onclick="setImpact(this,'')">All</button>
|
||||
<button class="chip" data-imp="high" onclick="setImpact(this,'high')">High</button>
|
||||
<button class="chip" data-imp="medium" onclick="setImpact(this,'medium')">Medium</button>
|
||||
<button class="chip" data-imp="low" onclick="setImpact(this,'low')">Low</button>
|
||||
</div>
|
||||
|
||||
<!-- Two-pane split -->
|
||||
<div class="perception-split work-area">
|
||||
<!-- Feed pane -->
|
||||
<div class="feed-pane">
|
||||
<div class="feed-pane-head">
|
||||
<h2>Signal feed</h2>
|
||||
<span class="feed-count" id="feed-count"></span>
|
||||
</div>
|
||||
<div class="feed-scroll" id="feed-scroll">
|
||||
<div class="loading-msg">Loading…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Analysis pane -->
|
||||
<div class="analysis-pane" id="analysis-pane">
|
||||
<div class="analysis-empty" id="analysis-empty">
|
||||
<div class="analysis-empty-ring">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M8 2a6 6 0 100 12A6 6 0 008 2zm0 2v4l3 1.5-.5 1-3.5-1.75V4H8z" fill="currentColor" opacity=".3"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="analysis-empty-label">Select a signal to view impact analysis</div>
|
||||
<div class="analysis-empty-hint">← Choose from the signal feed</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="footer">
|
||||
<div class="footer-live"><span class="footer-dot"></span><span>Live feed</span></div>
|
||||
<span>T-Systems Regulation Hub</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ─── Theme toggle ────────────────────────────────────────────────────
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement;
|
||||
const next = html.dataset.theme === 'dark' ? 'light' : 'dark';
|
||||
html.dataset.theme = next;
|
||||
localStorage.setItem('theme', next);
|
||||
const spans = document.querySelectorAll('.sidebar-action span');
|
||||
spans.forEach(s => s.textContent = next === 'dark' ? 'Light mode' : 'Dark mode');
|
||||
}
|
||||
(function() {
|
||||
const saved = localStorage.getItem('theme');
|
||||
if (saved) document.documentElement.dataset.theme = saved;
|
||||
})();
|
||||
|
||||
const API = 'http://6.86.80.9:5173/api/v1';
|
||||
|
||||
const SRC_COLOR = {
|
||||
MIIT: '#e20074', 'UN-ECE': '#3b82f6', ISO: '#7c3aed',
|
||||
'国标委': '#059669', 'EUR-Lex': '#d97706', IATF: '#7c3aed'
|
||||
};
|
||||
const IMP_COLOR = { high: 'var(--danger)', medium: 'var(--warn)', low: 'var(--success)' };
|
||||
const IMP_LABEL = { high: 'High', medium: 'Medium', low: 'Low' };
|
||||
const STA_LABEL = { enacted: 'Enacted', draft: 'Draft', consultation: 'Consultation' };
|
||||
const STA_CLASS = { enacted: 'ok', draft: 'warn', consultation: 'info' };
|
||||
|
||||
let currentSource = '', currentImpact = '', selectedId = null, abortCtrl = null;
|
||||
let allEvents = [];
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const r = await fetch(`${API}/perception/stats`);
|
||||
if (!r.ok) return;
|
||||
const s = await r.json();
|
||||
document.getElementById('stat-total').textContent = s.total ?? '—';
|
||||
document.getElementById('stat-high').textContent = s.high_impact ?? '—';
|
||||
document.getElementById('stat-med').textContent = s.medium_impact ?? '—';
|
||||
document.getElementById('stat-90d').textContent = s.recent_90d ?? '—';
|
||||
const badge = document.getElementById('badge-high');
|
||||
if (badge) badge.textContent = s.high_impact ?? '—';
|
||||
} catch(e) { console.warn('stats:', e); }
|
||||
}
|
||||
|
||||
async function loadFeed() {
|
||||
const scroll = document.getElementById('feed-scroll');
|
||||
scroll.innerHTML = '<div class="loading-msg">Loading…</div>';
|
||||
const params = new URLSearchParams();
|
||||
if (currentSource) params.set('source', currentSource);
|
||||
if (currentImpact) params.set('impact_level', currentImpact);
|
||||
try {
|
||||
const r = await fetch(`${API}/perception/events?${params}`);
|
||||
if (!r.ok) throw new Error(r.status);
|
||||
const data = await r.json();
|
||||
allEvents = data.events || [];
|
||||
const countEl = document.getElementById('feed-count');
|
||||
if (countEl) countEl.textContent = `${allEvents.length} / ${data.total}`;
|
||||
renderFeed(allEvents);
|
||||
} catch(e) {
|
||||
scroll.innerHTML = `<div class="loading-msg" style="color:var(--danger)">Failed to load: ${e.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderFeed(events) {
|
||||
const scroll = document.getElementById('feed-scroll');
|
||||
if (!events.length) {
|
||||
scroll.innerHTML = '<div class="loading-msg">No signals match the current filters.</div>';
|
||||
return;
|
||||
}
|
||||
scroll.innerHTML = events.map(ev => {
|
||||
const sc = SRC_COLOR[ev.source] || '#888';
|
||||
const stc = STA_CLASS[ev.status] || 'info';
|
||||
const sel = ev.id === selectedId;
|
||||
return `<div class="ev-card${sel ? ' selected' : ''}" onclick="selectEvent('${ev.id}')" id="card-${ev.id}">
|
||||
<div class="ev-head">
|
||||
<span class="src-tag" style="color:${sc};background:${sc}18">${ev.source}</span>
|
||||
<span class="std-code">${ev.standard_code}</span>
|
||||
<span class="status ${stc}" style="margin-left:auto">${STA_LABEL[ev.status] || ev.status}</span>
|
||||
</div>
|
||||
<div class="ev-title">${ev.title}</div>
|
||||
<div class="ev-summary">${ev.summary}</div>
|
||||
<div class="ev-foot">
|
||||
<span class="ev-date">${ev.published_at}</span>
|
||||
${(ev.tags||[]).slice(0,2).map(t=>`<span class="ev-tag">${t}</span>`).join('')}
|
||||
<span class="imp-dot" style="color:${IMP_COLOR[ev.impact_level]||'var(--muted)'}">● ${IMP_LABEL[ev.impact_level]||ev.impact_level}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function selectEvent(id) {
|
||||
if (id === selectedId) return;
|
||||
selectedId = id;
|
||||
document.querySelectorAll('.ev-card').forEach(c => c.classList.remove('selected'));
|
||||
const card = document.getElementById(`card-${id}`);
|
||||
if (card) card.classList.add('selected');
|
||||
const ev = allEvents.find(e => e.id === id);
|
||||
if (ev) renderDetail(ev);
|
||||
}
|
||||
|
||||
function renderDetail(ev) {
|
||||
const sc = SRC_COLOR[ev.source] || '#888';
|
||||
const stc = STA_CLASS[ev.status] || 'info';
|
||||
const pane = document.getElementById('analysis-pane');
|
||||
pane.innerHTML = `
|
||||
<div class="detail-card">
|
||||
<div class="detail-head">
|
||||
<span class="src-tag" style="color:${sc};background:${sc}18;font-size:10px;padding:2px 7px;border-radius:4px">${ev.source}</span>
|
||||
<span style="font-family:var(--font-mono);font-size:10px;color:var(--muted)">${ev.standard_code}</span>
|
||||
<span class="status ${stc}" style="margin-left:auto">${STA_LABEL[ev.status]||ev.status}</span>
|
||||
</div>
|
||||
<div class="detail-title">${ev.title}</div>
|
||||
<div class="detail-summary">${ev.summary}</div>
|
||||
<div class="detail-meta">
|
||||
<span class="meta-item">Published <strong>${ev.published_at}</strong></span>
|
||||
${ev.effective_at ? `<span class="meta-item">Effective <strong>${ev.effective_at}</strong></span>` : ''}
|
||||
<span class="meta-item">Category <strong>${ev.category}</strong></span>
|
||||
<span class="meta-item">Impact <strong style="color:${IMP_COLOR[ev.impact_level]||'var(--muted)'}">${IMP_LABEL[ev.impact_level]||ev.impact_level}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<button class="btn btn-primary" id="btn-analyze" onclick="startAnalysis('${ev.id}')">
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M8 1l1.8 5.5H15l-4.8 3.5 1.8 5.5L8 12l-4 3.5 1.8-5.5L1 6.5h5.2z" fill="currentColor"/></svg>
|
||||
Run impact analysis
|
||||
</button>
|
||||
<button class="btn" id="btn-abort" style="display:none" onclick="stopAnalysis()">Stop</button>
|
||||
${ev.source_url ? `<a href="${ev.source_url}" target="_blank" class="btn">
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M9 2h5v5M9.5 6.5L14 2M3 4h4M3 8h8M3 12h8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
||||
Source
|
||||
</a>` : ''}
|
||||
</div>
|
||||
<div class="docs-card" id="docs-card">
|
||||
<div class="docs-head">Affected documents</div>
|
||||
<div id="docs-list"></div>
|
||||
</div>
|
||||
<div class="output-card" id="output-card">
|
||||
<div class="output-head" id="output-head">
|
||||
<svg width="11" height="11" viewBox="0 0 16 16" fill="none"><path d="M8 1l1.8 5.5H15l-4.8 3.5 1.8 5.5L8 12l-4 3.5 1.8-5.5L1 6.5h5.2z" fill="currentColor"/></svg>
|
||||
AI impact analysis
|
||||
</div>
|
||||
<div id="output-body"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function startAnalysis(eventId) {
|
||||
if (abortCtrl) abortCtrl.abort();
|
||||
abortCtrl = new AbortController();
|
||||
const btnA = document.getElementById('btn-analyze');
|
||||
const btnX = document.getElementById('btn-abort');
|
||||
btnA.disabled = true;
|
||||
btnX.style.display = '';
|
||||
const outputCard = document.getElementById('output-card');
|
||||
const outputHead = document.getElementById('output-head');
|
||||
const outputBody = document.getElementById('output-body');
|
||||
const docsCard = document.getElementById('docs-card');
|
||||
outputCard.style.display = '';
|
||||
outputBody.innerHTML = '';
|
||||
outputHead.innerHTML = `<svg width="11" height="11" viewBox="0 0 16 16" fill="none"><path d="M8 1l1.8 5.5H15l-4.8 3.5 1.8 5.5L8 12l-4 3.5 1.8-5.5L1 6.5h5.2z" fill="currentColor"/></svg> AI impact analysis <span class="blink" style="color:var(--accent)">▋</span>`;
|
||||
try {
|
||||
const res = await fetch(`${API}/perception/events/${eventId}/analyze`, {
|
||||
method: 'POST', headers: { Accept: 'text/event-stream' }, signal: abortCtrl.signal
|
||||
});
|
||||
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
|
||||
const reader = res.body.getReader();
|
||||
const dec = new TextDecoder();
|
||||
let buf = '', rawText = '';
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buf += dec.decode(value, { stream: true });
|
||||
const parts = buf.split('\n\n');
|
||||
buf = parts.pop() ?? '';
|
||||
for (const block of parts) {
|
||||
if (!block.trim()) continue;
|
||||
let evtName = 'message';
|
||||
const dataLines = [];
|
||||
for (const line of block.split('\n')) {
|
||||
if (line.startsWith('event:')) evtName = line.slice(6).trim();
|
||||
else if (line.startsWith('data:')) dataLines.push(line.slice(5).trim());
|
||||
}
|
||||
const payload = dataLines.join('\n');
|
||||
if (!payload) continue;
|
||||
if (evtName === 'sources') {
|
||||
try { renderDocs(JSON.parse(payload), docsCard); } catch {}
|
||||
} else if (evtName === 'content') {
|
||||
rawText += payload;
|
||||
outputBody.innerHTML = renderMarkdown(rawText);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
if (e.name !== 'AbortError') {
|
||||
const ob = document.getElementById('output-body');
|
||||
if (ob) ob.innerHTML += `<div style="color:var(--danger);font-size:12px;margin-top:8px">Analysis error: ${e.message}</div>`;
|
||||
}
|
||||
} finally {
|
||||
const blink = document.querySelector('#output-head .blink');
|
||||
if (blink) blink.remove();
|
||||
const ab = document.getElementById('btn-abort');
|
||||
if (ab) ab.style.display = 'none';
|
||||
const ba = document.getElementById('btn-analyze');
|
||||
if (ba) { ba.disabled = false; ba.innerHTML = `<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M8 1l1.8 5.5H15l-4.8 3.5 1.8 5.5L8 12l-4 3.5 1.8-5.5L1 6.5h5.2z" fill="currentColor"/></svg> Re-analyse`; }
|
||||
}
|
||||
}
|
||||
|
||||
function stopAnalysis() {
|
||||
if (abortCtrl) { abortCtrl.abort(); abortCtrl = null; }
|
||||
const ab = document.getElementById('btn-abort');
|
||||
if (ab) ab.style.display = 'none';
|
||||
const ba = document.getElementById('btn-analyze');
|
||||
if (ba) ba.disabled = false;
|
||||
const h = document.getElementById('output-head');
|
||||
if (h) { const b = h.querySelector('.blink'); if (b) b.remove(); }
|
||||
}
|
||||
|
||||
function renderDocs(docs, card) {
|
||||
if (!docs || !docs.length) return;
|
||||
card.style.display = '';
|
||||
document.getElementById('docs-list').innerHTML = docs.map(d => `
|
||||
<div class="doc-row">
|
||||
<div class="doc-score">${Math.round(d.score * 100)}%</div>
|
||||
<div>
|
||||
<div class="doc-name">${d.doc_name}</div>
|
||||
<div class="doc-clause">${d.clause || ''}</div>
|
||||
<div class="doc-snippet">${d.snippet || ''}</div>
|
||||
</div>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
function renderMarkdown(text) {
|
||||
return text.split('\n').map(line => {
|
||||
if (line.startsWith('## ')) return `<div class="md-h2">${line.slice(3)}</div>`;
|
||||
if (line.startsWith('### ')) return `<div class="md-h3">${line.slice(4)}</div>`;
|
||||
if (line.startsWith('- ') || line.startsWith('* ')) {
|
||||
const c = line.slice(2).replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>');
|
||||
return `<div class="md-li"><span class="md-li-dot">·</span><span>${c}</span></div>`;
|
||||
}
|
||||
if (/^\d+\./.test(line)) {
|
||||
const c = line.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>');
|
||||
return `<div class="md-p" style="padding-left:8px">${c}</div>`;
|
||||
}
|
||||
if (!line.trim()) return '<div class="md-empty"></div>';
|
||||
return `<div class="md-p">${line.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>')}</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function setSource(btn, src) {
|
||||
currentSource = src;
|
||||
document.querySelectorAll('[data-src]').forEach(c => c.classList.toggle('on', c.dataset.src === src));
|
||||
loadFeed();
|
||||
}
|
||||
|
||||
function setImpact(btn, imp) {
|
||||
currentImpact = imp;
|
||||
document.querySelectorAll('[data-imp]').forEach(c => c.classList.toggle('on', c.dataset.imp === imp));
|
||||
loadFeed();
|
||||
}
|
||||
|
||||
// Boot
|
||||
loadStats();
|
||||
loadFeed();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1059
Prototype/regulation-hub-export.html
Normal file
1059
Prototype/regulation-hub-export.html
Normal file
File diff suppressed because it is too large
Load Diff
475
aliyun_parser/parse_pdf.py
Normal file
475
aliyun_parser/parse_pdf.py
Normal file
@@ -0,0 +1,475 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
阿里云文档智能 API 解析 PDF,输出三层结构 chunks
|
||||
- structure_nodes: 目录树结构
|
||||
- semantic_blocks: 语义块(章节文本、表格、图片)
|
||||
- vector_chunks: 检索块(带 overlap 切分)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
from alibabacloud_docmind_api20220711.client import Client as DocmindClient
|
||||
from alibabacloud_tea_openapi import models as open_api_models
|
||||
from alibabacloud_docmind_api20220711 import models as docmind_models
|
||||
from alibabacloud_tea_util import models as util_models
|
||||
|
||||
# ===================== 阿里云配置 =====================
|
||||
ALIBABA_ACCESS_KEY_ID = "LTAI5t6fWvAsvZkoF9WTbtys"
|
||||
ALIBABA_ACCESS_KEY_SECRET = "WX4oaE4FLYRa5L85TMQkqRPHeTJAF0"
|
||||
ALIBABA_ENDPOINT = "docmind-api.cn-hangzhou.aliyuncs.com"
|
||||
|
||||
# ===================== 切分参数 =====================
|
||||
MAX_CHARS = 600
|
||||
OVERLAP_CHARS = 80
|
||||
|
||||
# ===================== 布局类型常量 =====================
|
||||
TOC_TITLES = {"目次", "目录"}
|
||||
TITLE_SUBTYPES = {"doc_title", "para_title"}
|
||||
TEXT_SUBTYPES = {"para", "none"}
|
||||
FIGURE_TYPES = {"figure", "figure_name", "figure_note"}
|
||||
FIGURE_SUBTYPES = {"picture", "pic_title", "pic_caption"}
|
||||
|
||||
|
||||
# ===================== 阿里云 API 客户端 =====================
|
||||
def init_client() -> DocmindClient:
|
||||
config = open_api_models.Config(
|
||||
access_key_id=ALIBABA_ACCESS_KEY_ID,
|
||||
access_key_secret=ALIBABA_ACCESS_KEY_SECRET,
|
||||
)
|
||||
config.endpoint = ALIBABA_ENDPOINT
|
||||
return DocmindClient(config)
|
||||
|
||||
|
||||
def submit_job(client: DocmindClient, file_path: str) -> str:
|
||||
"""提交文档解析任务"""
|
||||
file_name = Path(file_path).name
|
||||
request = docmind_models.SubmitDocParserJobAdvanceRequest(
|
||||
file_url_object=open(file_path, "rb"),
|
||||
file_name=file_name,
|
||||
file_name_extension=Path(file_path).suffix.lstrip("."),
|
||||
llm_enhancement=True,
|
||||
enhancement_mode="VLM",
|
||||
)
|
||||
runtime = util_models.RuntimeOptions()
|
||||
response = client.submit_doc_parser_job_advance(request, runtime)
|
||||
return response.body.data.id
|
||||
|
||||
|
||||
def query_status(client: DocmindClient, task_id: str) -> Dict:
|
||||
"""查询任务状态"""
|
||||
request = docmind_models.QueryDocParserStatusRequest(id=task_id)
|
||||
response = client.query_doc_parser_status(request)
|
||||
return response.body.data.to_map() if response.body.data else None
|
||||
|
||||
|
||||
def wait_for_completion(client: DocmindClient, task_id: str, poll_interval: int = 5) -> bool:
|
||||
"""等待任务完成"""
|
||||
while True:
|
||||
status_data = query_status(client, task_id)
|
||||
if not status_data:
|
||||
return False
|
||||
status = status_data.get("Status", "").lower()
|
||||
if status == "success":
|
||||
return True
|
||||
elif status == "failed":
|
||||
print(f"任务失败: {status_data}")
|
||||
return False
|
||||
print(f"任务状态: {status}, 等待中...")
|
||||
time.sleep(poll_interval)
|
||||
|
||||
|
||||
def get_result(client: DocmindClient, task_id: str, layout_num: int = 0, layout_step_size: int = 50) -> Dict:
|
||||
"""获取解析结果"""
|
||||
request = docmind_models.GetDocParserResultRequest(
|
||||
id=task_id,
|
||||
layout_step_size=layout_step_size,
|
||||
layout_num=layout_num,
|
||||
)
|
||||
response = client.get_doc_parser_result(request)
|
||||
return response.body.data if response.body.data else None
|
||||
|
||||
|
||||
def collect_all_results(client: DocmindClient, task_id: str, layout_step_size: int = 50) -> List[Dict]:
|
||||
"""收集所有解析结果"""
|
||||
all_layouts = []
|
||||
layout_num = 0
|
||||
while True:
|
||||
result_data = get_result(client, task_id, layout_num, layout_step_size)
|
||||
if not result_data:
|
||||
break
|
||||
layouts = result_data.get("layouts", [])
|
||||
if not layouts:
|
||||
break
|
||||
all_layouts.extend(layouts)
|
||||
layout_num += len(layouts)
|
||||
if len(layouts) < layout_step_size:
|
||||
break
|
||||
return all_layouts
|
||||
|
||||
|
||||
# ===================== 文本处理 =====================
|
||||
def normalize_text(text: str) -> str:
|
||||
text = text.replace("\r", "\n")
|
||||
text = text.replace(" ", " ")
|
||||
text = re.sub(r"\n+", "\n", text)
|
||||
text = re.sub(r"[ \t]+", " ", text)
|
||||
return text.strip()
|
||||
|
||||
|
||||
def get_page(layout: Dict) -> int:
|
||||
return layout.get("pageNum", layout.get("pageNumber", 0))
|
||||
|
||||
|
||||
def get_text(layout: Dict) -> str:
|
||||
text = normalize_text(layout.get("text", ""))
|
||||
if text:
|
||||
return text
|
||||
return normalize_text(layout.get("markdownContent", ""))
|
||||
|
||||
|
||||
# ===================== 布局类型判断 =====================
|
||||
def is_title(layout: Dict) -> bool:
|
||||
return layout.get("type") == "title" or layout.get("subType") in TITLE_SUBTYPES
|
||||
|
||||
|
||||
def is_text(layout: Dict) -> bool:
|
||||
return layout.get("type") == "text" and layout.get("subType", "none") in TEXT_SUBTYPES
|
||||
|
||||
|
||||
def is_figure(layout: Dict) -> bool:
|
||||
return layout.get("type") in FIGURE_TYPES or layout.get("subType") in FIGURE_SUBTYPES
|
||||
|
||||
|
||||
def is_table(layout: Dict) -> bool:
|
||||
return layout.get("type") == "table"
|
||||
|
||||
|
||||
def is_toc_layout(layout: Dict) -> bool:
|
||||
text = get_text(layout)
|
||||
if text in TOC_TITLES:
|
||||
return True
|
||||
if get_page(layout) == 1 and re.match(r"^\d+(\.\d+)*\s+.+[.。…]{2,}\s*\d+$", text):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def extract_table_text(layout: Dict) -> str:
|
||||
rows = []
|
||||
for cell in layout.get("cells", []):
|
||||
texts = []
|
||||
for cell_layout in cell.get("layouts", []):
|
||||
cell_text = normalize_text(cell_layout.get("text", ""))
|
||||
if cell_text:
|
||||
texts.append(cell_text)
|
||||
if texts:
|
||||
rows.append(" ".join(texts))
|
||||
return "\n".join(rows).strip()
|
||||
|
||||
|
||||
# ===================== 结构层:目录树 =====================
|
||||
def build_structure_nodes(layouts: List[Dict]) -> List[Dict]:
|
||||
nodes = []
|
||||
for layout in layouts:
|
||||
if not is_title(layout):
|
||||
continue
|
||||
text = get_text(layout)
|
||||
if not text or text in TOC_TITLES:
|
||||
continue
|
||||
nodes.append(
|
||||
{
|
||||
"unique_id": layout.get("uniqueId"),
|
||||
"page": get_page(layout),
|
||||
"index": layout.get("index", 0),
|
||||
"level": layout.get("level", 0),
|
||||
"title": text,
|
||||
"type": layout.get("type"),
|
||||
"sub_type": layout.get("subType"),
|
||||
}
|
||||
)
|
||||
return nodes
|
||||
|
||||
|
||||
# ===================== 语义层:章节内容 =====================
|
||||
def update_section_path(section_stack: List[Dict], layout: Dict) -> List[Dict]:
|
||||
level = layout.get("level", 0)
|
||||
title = get_text(layout)
|
||||
while section_stack and section_stack[-1]["level"] >= level:
|
||||
section_stack.pop()
|
||||
section_stack.append(
|
||||
{
|
||||
"level": level,
|
||||
"title": title,
|
||||
"page": get_page(layout),
|
||||
"unique_id": layout.get("uniqueId"),
|
||||
}
|
||||
)
|
||||
return section_stack
|
||||
|
||||
|
||||
def section_path_titles(section_stack: List[Dict]) -> List[str]:
|
||||
return [item["title"] for item in section_stack]
|
||||
|
||||
|
||||
def flush_text_block(blocks: List[Dict], semantic_blocks: List[Dict], block_id: int) -> int:
|
||||
if not blocks:
|
||||
return block_id
|
||||
|
||||
texts = [item["text"] for item in blocks if item["text"]]
|
||||
merged_text = "\n".join(texts).strip()
|
||||
if not merged_text:
|
||||
return block_id
|
||||
|
||||
semantic_blocks.append(
|
||||
{
|
||||
"semantic_id": f"semantic-{block_id}",
|
||||
"block_type": "section_text",
|
||||
"page_start": min(item["page"] for item in blocks),
|
||||
"page_end": max(item["page"] for item in blocks),
|
||||
"section_path": blocks[0]["section_path"],
|
||||
"section_level": blocks[0]["section_level"],
|
||||
"section_title": blocks[0]["section_title"],
|
||||
"source_ids": [item["unique_id"] for item in blocks if item.get("unique_id")],
|
||||
"text": merged_text,
|
||||
}
|
||||
)
|
||||
return block_id + 1
|
||||
|
||||
|
||||
def build_semantic_blocks(layouts: List[Dict]) -> List[Dict]:
|
||||
semantic_blocks = []
|
||||
section_stack = []
|
||||
pending_text_blocks = []
|
||||
block_id = 1
|
||||
skip_toc_page = False
|
||||
|
||||
for layout in layouts:
|
||||
text = get_text(layout)
|
||||
page = get_page(layout)
|
||||
|
||||
if is_toc_layout(layout):
|
||||
skip_toc_page = True
|
||||
continue
|
||||
if skip_toc_page and page == 1:
|
||||
continue
|
||||
if skip_toc_page and page != 1:
|
||||
skip_toc_page = False
|
||||
|
||||
if is_title(layout):
|
||||
block_id = flush_text_block(pending_text_blocks, semantic_blocks, block_id)
|
||||
pending_text_blocks = []
|
||||
section_stack = update_section_path(section_stack, layout)
|
||||
continue
|
||||
|
||||
section_path = section_path_titles(section_stack)
|
||||
section_title = section_path[-1] if section_path else "未分类"
|
||||
section_level = len(section_path)
|
||||
|
||||
if is_table(layout):
|
||||
block_id = flush_text_block(pending_text_blocks, semantic_blocks, block_id)
|
||||
pending_text_blocks = []
|
||||
table_text = extract_table_text(layout)
|
||||
if table_text:
|
||||
semantic_blocks.append(
|
||||
{
|
||||
"semantic_id": f"semantic-{block_id}",
|
||||
"block_type": "table",
|
||||
"page_start": page,
|
||||
"page_end": page,
|
||||
"section_path": section_path,
|
||||
"section_level": section_level,
|
||||
"section_title": section_title,
|
||||
"source_ids": [layout.get("uniqueId")],
|
||||
"text": table_text,
|
||||
}
|
||||
)
|
||||
block_id += 1
|
||||
continue
|
||||
|
||||
if is_figure(layout):
|
||||
block_id = flush_text_block(pending_text_blocks, semantic_blocks, block_id)
|
||||
pending_text_blocks = []
|
||||
if text:
|
||||
semantic_blocks.append(
|
||||
{
|
||||
"semantic_id": f"semantic-{block_id}",
|
||||
"block_type": "figure",
|
||||
"page_start": page,
|
||||
"page_end": page,
|
||||
"section_path": section_path,
|
||||
"section_level": section_level,
|
||||
"section_title": section_title,
|
||||
"source_ids": [layout.get("uniqueId")],
|
||||
"text": text,
|
||||
}
|
||||
)
|
||||
block_id += 1
|
||||
continue
|
||||
|
||||
if is_text(layout) and text:
|
||||
pending_text_blocks.append(
|
||||
{
|
||||
"page": page,
|
||||
"text": text,
|
||||
"unique_id": layout.get("uniqueId"),
|
||||
"section_path": section_path,
|
||||
"section_level": section_level,
|
||||
"section_title": section_title,
|
||||
}
|
||||
)
|
||||
|
||||
flush_text_block(pending_text_blocks, semantic_blocks, block_id)
|
||||
return semantic_blocks
|
||||
|
||||
|
||||
# ===================== 检索层:向量 chunks =====================
|
||||
def split_text_with_overlap(text: str, max_chars: int, overlap_chars: int) -> List[str]:
|
||||
text = text.strip()
|
||||
if len(text) <= max_chars:
|
||||
return [text] if text else []
|
||||
|
||||
parts = []
|
||||
start = 0
|
||||
while start < len(text):
|
||||
end = min(len(text), start + max_chars)
|
||||
parts.append(text[start:end].strip())
|
||||
if end >= len(text):
|
||||
break
|
||||
start = max(0, end - overlap_chars)
|
||||
return [part for part in parts if part]
|
||||
|
||||
|
||||
def build_vector_chunks(
|
||||
semantic_blocks: List[Dict],
|
||||
doc_id: str,
|
||||
doc_title: str,
|
||||
max_chars: int,
|
||||
overlap_chars: int,
|
||||
) -> List[Dict]:
|
||||
vector_chunks = []
|
||||
chunk_index = 1
|
||||
|
||||
for block in semantic_blocks:
|
||||
pieces = split_text_with_overlap(block["text"], max_chars, overlap_chars)
|
||||
for piece_index, piece in enumerate(pieces, start=1):
|
||||
if block["section_path"]:
|
||||
header = f"标准:{doc_title}\n章节:{' > '.join(block['section_path'])}\n\n"
|
||||
else:
|
||||
header = f"标准:{doc_title}\n\n"
|
||||
vector_chunks.append(
|
||||
{
|
||||
"doc_id": doc_id,
|
||||
"doc_title": doc_title,
|
||||
"chunk_id": f"chunk-{chunk_index}",
|
||||
"chunk_index": chunk_index,
|
||||
"semantic_id": block["semantic_id"],
|
||||
"chunk_type": block["block_type"],
|
||||
"piece_index": piece_index,
|
||||
"page_start": block["page_start"],
|
||||
"page_end": block["page_end"],
|
||||
"section_path": block["section_path"],
|
||||
"section_level": block["section_level"],
|
||||
"section_title": block["section_title"],
|
||||
"source_ids": block["source_ids"],
|
||||
"text": piece,
|
||||
"embedding_text": header + piece,
|
||||
}
|
||||
)
|
||||
chunk_index += 1
|
||||
|
||||
return vector_chunks
|
||||
|
||||
|
||||
# ===================== 主转换函数 =====================
|
||||
def convert_layouts(
|
||||
layouts: List[Dict],
|
||||
doc_id: str,
|
||||
doc_title: str,
|
||||
max_chars: int,
|
||||
overlap_chars: int,
|
||||
) -> Dict:
|
||||
structure_nodes = build_structure_nodes(layouts)
|
||||
semantic_blocks = build_semantic_blocks(layouts)
|
||||
vector_chunks = build_vector_chunks(
|
||||
semantic_blocks,
|
||||
doc_id=doc_id,
|
||||
doc_title=doc_title,
|
||||
max_chars=max_chars,
|
||||
overlap_chars=overlap_chars,
|
||||
)
|
||||
return {
|
||||
"doc_id": doc_id,
|
||||
"doc_title": doc_title,
|
||||
"structure_nodes": structure_nodes,
|
||||
"semantic_blocks": semantic_blocks,
|
||||
"vector_chunks": vector_chunks,
|
||||
}
|
||||
|
||||
|
||||
# ===================== CLI 入口 =====================
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="阿里云文档智能解析 PDF,输出三层结构 chunks")
|
||||
parser.add_argument("pdf_path", help="PDF 文件路径")
|
||||
parser.add_argument("--out", default="vector_chunks.json", help="输出 JSON 文件路径")
|
||||
parser.add_argument("--layouts-out", dest="layouts_output", help="输出原始 layouts JSON")
|
||||
parser.add_argument("--doc-id", default="GB14747-2006", help="文档 ID")
|
||||
parser.add_argument("--doc-title", default="GB 14747—2006 儿童三轮车安全要求", help="文档标题")
|
||||
parser.add_argument("--max-chars", type=int, default=MAX_CHARS, help="单个检索 chunk 最大字符数")
|
||||
parser.add_argument("--overlap-chars", type=int, default=OVERLAP_CHARS, help="相邻检索 chunk 重叠字符数")
|
||||
parser.add_argument("--poll-interval", type=int, default=5, help="轮询间隔(秒)")
|
||||
args = parser.parse_args()
|
||||
|
||||
pdf_path = Path(args.pdf_path).expanduser().resolve()
|
||||
if not pdf_path.exists():
|
||||
raise FileNotFoundError(f"PDF 文件不存在: {pdf_path}")
|
||||
|
||||
# 1. 提交阿里云任务
|
||||
client = init_client()
|
||||
print(f"提交任务: {pdf_path}")
|
||||
task_id = submit_job(client, str(pdf_path))
|
||||
print(f"任务 ID: {task_id}")
|
||||
|
||||
# 2. 等待完成
|
||||
print("等待任务完成...")
|
||||
if not wait_for_completion(client, task_id, args.poll_interval):
|
||||
print("任务失败,退出")
|
||||
return
|
||||
|
||||
# 3. 获取 layouts
|
||||
print("获取解析结果...")
|
||||
layouts = collect_all_results(client, task_id)
|
||||
print(f"获取到 {len(layouts)} 个布局块")
|
||||
|
||||
# 4. 输出原始 layouts(可选)
|
||||
if args.layouts_output:
|
||||
layouts_path = Path(args.layouts_output).expanduser().resolve()
|
||||
layouts_path.write_text(json.dumps(layouts, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(f"原始 layouts 已写入: {layouts_path}")
|
||||
|
||||
# 5. 转换为三层结构
|
||||
print("转换为三层结构...")
|
||||
data = convert_layouts(
|
||||
layouts,
|
||||
doc_id=args.doc_id,
|
||||
doc_title=args.doc_title,
|
||||
max_chars=args.max_chars,
|
||||
overlap_chars=args.overlap_chars,
|
||||
)
|
||||
|
||||
# 6. 输出结果
|
||||
output_path = Path(args.out).expanduser().resolve()
|
||||
output_path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
print(f"结构层节点数: {len(data['structure_nodes'])}")
|
||||
print(f"语义层块数: {len(data['semantic_blocks'])}")
|
||||
print(f"检索层块数: {len(data['vector_chunks'])}")
|
||||
print(f"输出文件: {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
122
aliyun_parser/schema.sql
Normal file
122
aliyun_parser/schema.sql
Normal file
@@ -0,0 +1,122 @@
|
||||
-- 法规文档向量检索系统数据库表结构
|
||||
-- PostgreSQL
|
||||
|
||||
-- ==================== 文档表 ====================
|
||||
CREATE TABLE documents (
|
||||
id SERIAL PRIMARY KEY,
|
||||
doc_id VARCHAR(128) UNIQUE NOT NULL, -- 文档唯一标识,如 "GB14747-2006"
|
||||
title VARCHAR(512) NOT NULL, -- 文档标题
|
||||
doc_type VARCHAR(32), -- 文档类型:标准/法规/规范
|
||||
standard_number VARCHAR(64), -- 标准编号:如 "GB 14747-2006"
|
||||
publish_date DATE, -- 发布日期
|
||||
implement_date DATE, -- 实施日期
|
||||
status VARCHAR(32), -- 状态:现行/废止/修订
|
||||
source_url VARCHAR(512), -- 来源 URL
|
||||
file_path VARCHAR(512), -- 本地 PDF 文件路径
|
||||
file_size INT, -- 文件大小(字节)
|
||||
upload_time TIMESTAMP DEFAULT NOW(), -- 上传时间
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE documents IS '文档元数据表';
|
||||
COMMENT ON COLUMN documents.doc_id IS '文档唯一标识,用于关联 Milvus 和其他表';
|
||||
COMMENT ON COLUMN documents.standard_number IS '标准编号,如 GB 14747-2006';
|
||||
|
||||
-- ==================== 章节结构表 ====================
|
||||
CREATE TABLE sections (
|
||||
id SERIAL PRIMARY KEY,
|
||||
doc_id VARCHAR(128) NOT NULL,
|
||||
unique_id VARCHAR(64) NOT NULL, -- 阿里云返回的唯一标识
|
||||
level INT NOT NULL, -- 层级:1, 2, 3...
|
||||
title VARCHAR(512) NOT NULL, -- 章节标题
|
||||
page INT, -- 所在页码
|
||||
index INT, -- 页内顺序
|
||||
parent_id INT, -- 父章节 ID(树形结构)
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT fk_sections_doc_id FOREIGN KEY (doc_id) REFERENCES documents(doc_id),
|
||||
CONSTRAINT fk_sections_parent_id FOREIGN KEY (parent_id) REFERENCES sections(id),
|
||||
CONSTRAINT uq_sections_doc_unique UNIQUE (doc_id, unique_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE sections IS '章节结构表,用于目录导航';
|
||||
COMMENT ON COLUMN sections.parent_id IS '父章节 ID,构建树形结构';
|
||||
COMMENT ON COLUMN sections.level IS '层级深度,1 为最顶层';
|
||||
|
||||
-- ==================== 语义块表 ====================
|
||||
CREATE TABLE semantic_blocks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
doc_id VARCHAR(128) NOT NULL,
|
||||
semantic_id VARCHAR(64) NOT NULL, -- 语义块唯一标识
|
||||
block_type VARCHAR(32) NOT NULL, -- 类型:section_text/table/figure
|
||||
page_start INT NOT NULL, -- 起始页码
|
||||
page_end INT NOT NULL, -- 结束页码
|
||||
section_id INT, -- 所属章节
|
||||
section_title VARCHAR(512), -- 章节标题(冗余,方便查询)
|
||||
section_level INT, -- 章节层级
|
||||
source_ids JSONB, -- 原始 layout IDs(JSON 数组)
|
||||
text TEXT NOT NULL, -- 完整内容(未被切分)
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT fk_semantic_blocks_doc_id FOREIGN KEY (doc_id) REFERENCES documents(doc_id),
|
||||
CONSTRAINT fk_semantic_blocks_section_id FOREIGN KEY (section_id) REFERENCES sections(id),
|
||||
CONSTRAINT uq_semantic_blocks_doc_semantic UNIQUE (doc_id, semantic_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE semantic_blocks IS '语义块表,用于邻域扩展,恢复完整内容';
|
||||
COMMENT ON COLUMN semantic_blocks.block_type IS '类型:section_text(正文)、table(表格)、figure(图示)';
|
||||
COMMENT ON COLUMN semantic_blocks.source_ids IS '原始阿里云 layout 的 uniqueId 数组';
|
||||
COMMENT ON COLUMN semantic_blocks.text IS '完整语义内容,未被切分';
|
||||
|
||||
-- ==================== 向量块元数据表 ====================
|
||||
CREATE TABLE vector_chunks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
doc_id VARCHAR(128) NOT NULL,
|
||||
chunk_id VARCHAR(64) NOT NULL, -- Milvus 主键
|
||||
semantic_id VARCHAR(64) NOT NULL, -- 关联语义块
|
||||
chunk_index INT NOT NULL, -- 切片序号(全局)
|
||||
piece_index INT, -- 同语义块内的切片序号
|
||||
page_start INT,
|
||||
page_end INT,
|
||||
section_title VARCHAR(512),
|
||||
text VARCHAR(2048), -- 切片文本(可选,缩短版用于展示)
|
||||
source_ids JSONB, -- 原始 layout IDs(JSON 数组)
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT fk_vector_chunks_doc_id FOREIGN KEY (doc_id) REFERENCES documents(doc_id),
|
||||
CONSTRAINT fk_vector_chunks_semantic_id FOREIGN KEY (doc_id, semantic_id)
|
||||
REFERENCES semantic_blocks(doc_id, semantic_id),
|
||||
CONSTRAINT uq_vector_chunks_doc_chunk UNIQUE (doc_id, chunk_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE vector_chunks IS '向量块元数据表,用于快速关联查询';
|
||||
COMMENT ON COLUMN vector_chunks.chunk_id IS 'Milvus 向量库主键';
|
||||
COMMENT ON COLUMN vector_chunks.piece_index IS '同语义块内的切片序号,用于按序拼接';
|
||||
|
||||
-- ==================== 索引 ====================
|
||||
CREATE INDEX idx_sections_doc_id ON sections(doc_id);
|
||||
CREATE INDEX idx_sections_parent_id ON sections(parent_id);
|
||||
CREATE INDEX idx_sections_level ON sections(level);
|
||||
|
||||
CREATE INDEX idx_semantic_blocks_doc_id ON semantic_blocks(doc_id);
|
||||
CREATE INDEX idx_semantic_blocks_section_id ON semantic_blocks(section_id);
|
||||
CREATE INDEX idx_semantic_blocks_block_type ON semantic_blocks(block_type);
|
||||
CREATE INDEX idx_semantic_blocks_semantic_id ON semantic_blocks(semantic_id);
|
||||
|
||||
CREATE INDEX idx_vector_chunks_doc_id ON vector_chunks(doc_id);
|
||||
CREATE INDEX idx_vector_chunks_semantic_id ON vector_chunks(semantic_id);
|
||||
CREATE INDEX idx_vector_chunks_chunk_id ON vector_chunks(chunk_id);
|
||||
|
||||
-- ==================== 触发器:自动更新 updated_at ====================
|
||||
CREATE OR REPLACE FUNCTION update_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER tr_documents_updated_at
|
||||
BEFORE UPDATE ON documents
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
327
aliyun_parser/upload_to_milvus.py
Normal file
327
aliyun_parser/upload_to_milvus.py
Normal file
@@ -0,0 +1,327 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
将 vector_chunks.json 向量化并上传到 Milvus 和 PostgreSQL
|
||||
使用中转站的 OpenAI 兼容 API
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List, Dict
|
||||
|
||||
import psycopg2
|
||||
from psycopg2.extras import execute_values
|
||||
from pymilvus import (
|
||||
connections,
|
||||
Collection,
|
||||
FieldSchema,
|
||||
CollectionSchema,
|
||||
DataType,
|
||||
utility,
|
||||
)
|
||||
from openai import OpenAI
|
||||
|
||||
# ===================== 配置 =====================
|
||||
# 中转站配置
|
||||
RELAY_BASE_URL = "http://6.86.80.4:30080/v1"
|
||||
RELAY_API_KEY = "sk-5HeY7gfSIlyZMacfuXOf5cphpymsNqufEu1ou4U3avbULcyY"
|
||||
EMBEDDING_MODEL = "text-embedding-v3" # 中转站支持的 embedding 模型
|
||||
|
||||
# Milvus 配置
|
||||
MILVUS_HOST = "localhost"
|
||||
MILVUS_PORT = "19530"
|
||||
COLLECTION_NAME = "regulation_chunks"
|
||||
|
||||
# PostgreSQL 配置
|
||||
PG_HOST = "6.86.80.10"
|
||||
PG_PORT = 5432
|
||||
PG_USER = "postgresql"
|
||||
PG_PASSWORD = "postgresql123456"
|
||||
PG_DATABASE = "postgres"
|
||||
|
||||
|
||||
# ===================== Embedding =====================
|
||||
def get_openai_client(api_key: str, base_url: str) -> OpenAI:
|
||||
"""创建 OpenAI 客户端连接到中转站"""
|
||||
return OpenAI(api_key=api_key, base_url=base_url)
|
||||
|
||||
|
||||
def get_embeddings_batch(client: OpenAI, texts: List[str], batch_size: int = 10) -> List[List[float]]:
|
||||
"""批量获取文本向量"""
|
||||
all_embeddings = []
|
||||
|
||||
for i in range(0, len(texts), batch_size):
|
||||
batch = texts[i:i + batch_size]
|
||||
print(f"Embedding batch {i // batch_size + 1}/{(len(texts) - 1) // batch_size + 1}...")
|
||||
|
||||
response = client.embeddings.create(
|
||||
model=EMBEDDING_MODEL,
|
||||
input=batch,
|
||||
)
|
||||
|
||||
embeddings = [item.embedding for item in response.data]
|
||||
all_embeddings.extend(embeddings)
|
||||
|
||||
return all_embeddings
|
||||
|
||||
|
||||
# ===================== Milvus =====================
|
||||
def init_milvus(host: str, port: str):
|
||||
connections.connect("default", host=host, port=port)
|
||||
print(f"已连接 Milvus: {host}:{port}")
|
||||
|
||||
|
||||
def create_collection(name: str, dim: int) -> Collection:
|
||||
"""创建或获取 collection"""
|
||||
if utility.has_collection(name):
|
||||
print(f"Collection '{name}' 已存在,删除重建")
|
||||
utility.drop_collection(name)
|
||||
|
||||
fields = [
|
||||
FieldSchema(name="chunk_id", dtype=DataType.VARCHAR, max_length=64, is_primary=True),
|
||||
FieldSchema(name="doc_id", dtype=DataType.VARCHAR, max_length=128),
|
||||
FieldSchema(name="doc_title", dtype=DataType.VARCHAR, max_length=512),
|
||||
FieldSchema(name="chunk_index", dtype=DataType.INT64),
|
||||
FieldSchema(name="semantic_id", dtype=DataType.VARCHAR, max_length=64),
|
||||
FieldSchema(name="chunk_type", dtype=DataType.VARCHAR, max_length=32),
|
||||
FieldSchema(name="page_start", dtype=DataType.INT64),
|
||||
FieldSchema(name="page_end", dtype=DataType.INT64),
|
||||
FieldSchema(name="section_title", dtype=DataType.VARCHAR, max_length=512),
|
||||
FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=2048),
|
||||
FieldSchema(name="source_ids", dtype=DataType.VARCHAR, max_length=4096), # JSON 字符串
|
||||
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=dim),
|
||||
]
|
||||
|
||||
schema = CollectionSchema(fields, description="法规文档检索 chunks")
|
||||
collection = Collection(name, schema)
|
||||
|
||||
# 创建向量索引(IVF_FLAT,适合中小规模)
|
||||
index_params = {
|
||||
"metric_type": "COSINE",
|
||||
"index_type": "IVF_FLAT",
|
||||
"params": {"nlist": 128},
|
||||
}
|
||||
collection.create_index("embedding", index_params)
|
||||
print(f"Collection '{name}' 创建完成,索引已建立")
|
||||
|
||||
return collection
|
||||
|
||||
|
||||
def insert_chunks(collection: Collection, chunks: List[Dict], embeddings: List[List[float]]):
|
||||
"""插入 chunks 到 Milvus"""
|
||||
data = [
|
||||
[c["chunk_id"] for c in chunks],
|
||||
[c["doc_id"] for c in chunks],
|
||||
[c["doc_title"] for c in chunks],
|
||||
[c["chunk_index"] for c in chunks],
|
||||
[c["semantic_id"] for c in chunks],
|
||||
[c["chunk_type"] for c in chunks],
|
||||
[c["page_start"] for c in chunks],
|
||||
[c["page_end"] for c in chunks],
|
||||
[c["section_title"] for c in chunks],
|
||||
[c["text"] for c in chunks],
|
||||
[json.dumps(c.get("source_ids", [])) for c in chunks], # JSON 字符串
|
||||
embeddings,
|
||||
]
|
||||
|
||||
collection.insert(data)
|
||||
collection.flush()
|
||||
print(f"已插入 {len(chunks)} 个 chunks")
|
||||
|
||||
|
||||
def load_collection(collection: Collection):
|
||||
"""加载 collection 到内存(搜索前必须)"""
|
||||
collection.load()
|
||||
print(f"Collection 已加载到内存")
|
||||
|
||||
|
||||
# ===================== PostgreSQL =====================
|
||||
def get_pg_connection(host: str, port: int, user: str, password: str, database: str):
|
||||
"""获取 PostgreSQL 连接"""
|
||||
conn = psycopg2.connect(
|
||||
host=host,
|
||||
port=port,
|
||||
user=user,
|
||||
password=password,
|
||||
database=database,
|
||||
)
|
||||
print(f"已连接 PostgreSQL: {host}:{port}/{database}")
|
||||
return conn
|
||||
|
||||
|
||||
def insert_chunks_to_pg(conn, chunks: List[Dict], doc_data: Dict):
|
||||
"""插入 chunks 和相关数据到 PostgreSQL"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
# 1. 插入文档
|
||||
cursor.execute("""
|
||||
INSERT INTO documents (doc_id, title, standard_number, upload_time)
|
||||
VALUES (%s, %s, %s, NOW())
|
||||
ON CONFLICT (doc_id) DO UPDATE SET title = EXCLUDED.title, updated_at = NOW()
|
||||
""", (doc_data["doc_id"], doc_data["doc_title"], doc_data.get("standard_number")))
|
||||
|
||||
# 2. 插入语义块
|
||||
semantic_blocks = doc_data.get("semantic_blocks", [])
|
||||
if semantic_blocks:
|
||||
block_rows = [
|
||||
(
|
||||
doc_data["doc_id"],
|
||||
block["semantic_id"],
|
||||
block["block_type"],
|
||||
block["page_start"],
|
||||
block["page_end"],
|
||||
block.get("section_title"),
|
||||
block.get("section_level"),
|
||||
json.dumps(block.get("source_ids", [])),
|
||||
block["text"],
|
||||
)
|
||||
for block in semantic_blocks
|
||||
]
|
||||
execute_values(
|
||||
cursor,
|
||||
"""
|
||||
INSERT INTO semantic_blocks
|
||||
(doc_id, semantic_id, block_type, page_start, page_end, section_title, section_level, source_ids, text)
|
||||
VALUES %s
|
||||
ON CONFLICT (doc_id, semantic_id) DO UPDATE SET text = EXCLUDED.text
|
||||
""",
|
||||
block_rows,
|
||||
)
|
||||
print(f"已插入 {len(semantic_blocks)} 个语义块")
|
||||
|
||||
# 3. 插入向量块元数据
|
||||
chunk_rows = [
|
||||
(
|
||||
doc_data["doc_id"],
|
||||
chunk["chunk_id"],
|
||||
chunk["semantic_id"],
|
||||
chunk["chunk_index"],
|
||||
chunk.get("piece_index"),
|
||||
chunk["page_start"],
|
||||
chunk["page_end"],
|
||||
chunk.get("section_title"),
|
||||
chunk["text"],
|
||||
json.dumps(chunk.get("source_ids", [])),
|
||||
)
|
||||
for chunk in chunks
|
||||
]
|
||||
execute_values(
|
||||
cursor,
|
||||
"""
|
||||
INSERT INTO vector_chunks
|
||||
(doc_id, chunk_id, semantic_id, chunk_index, piece_index, page_start, page_end, section_title, text, source_ids)
|
||||
VALUES %s
|
||||
ON CONFLICT (doc_id, chunk_id) DO UPDATE SET text = EXCLUDED.text
|
||||
""",
|
||||
chunk_rows,
|
||||
)
|
||||
print(f"已插入 {len(chunks)} 个向量块元数据")
|
||||
|
||||
conn.commit()
|
||||
print("PostgreSQL 数据插入完成")
|
||||
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
raise e
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
|
||||
# ===================== 主流程 =====================
|
||||
def load_data(file_path: Path) -> Dict:
|
||||
"""加载 vector_chunks.json,返回完整数据"""
|
||||
data = json.loads(file_path.read_text(encoding="utf-8"))
|
||||
return data
|
||||
|
||||
|
||||
def upload_to_milvus_and_pg(
|
||||
chunks_file: str,
|
||||
api_key: str,
|
||||
base_url: str,
|
||||
milvus_host: str,
|
||||
milvus_port: str,
|
||||
collection_name: str,
|
||||
batch_size: int,
|
||||
pg_host: str,
|
||||
pg_port: int,
|
||||
pg_user: str,
|
||||
pg_password: str,
|
||||
pg_database: str,
|
||||
):
|
||||
# 1. 加载完整数据
|
||||
chunks_path = Path(chunks_file).expanduser().resolve()
|
||||
if not chunks_path.exists():
|
||||
raise FileNotFoundError(f"文件不存在: {chunks_path}")
|
||||
|
||||
data = load_data(chunks_path)
|
||||
chunks = data.get("vector_chunks", [])
|
||||
if not chunks:
|
||||
raise ValueError("vector_chunks 为空")
|
||||
print(f"加载 {len(chunks)} 个 chunks")
|
||||
|
||||
# 2. 初始化连接
|
||||
client = get_openai_client(api_key, base_url)
|
||||
init_milvus(milvus_host, milvus_port)
|
||||
pg_conn = get_pg_connection(pg_host, pg_port, pg_user, pg_password, pg_database)
|
||||
|
||||
# 3. 获取 embeddings
|
||||
texts = [c["embedding_text"] for c in chunks]
|
||||
embeddings = get_embeddings_batch(client, texts, batch_size)
|
||||
print(f"生成 {len(embeddings)} 个向量")
|
||||
|
||||
# 4. 获取 embedding 维度
|
||||
embedding_dim = len(embeddings[0])
|
||||
print(f"Embedding 维度: {embedding_dim}")
|
||||
|
||||
# 5. 创建 collection 并插入 Milvus
|
||||
collection = create_collection(collection_name, embedding_dim)
|
||||
insert_chunks(collection, chunks, embeddings)
|
||||
load_collection(collection)
|
||||
|
||||
# 6. 插入 PostgreSQL
|
||||
insert_chunks_to_pg(pg_conn, chunks, data)
|
||||
|
||||
# 7. 关闭连接
|
||||
pg_conn.close()
|
||||
|
||||
print("上传完成!")
|
||||
|
||||
|
||||
# ===================== CLI =====================
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="将 vector_chunks 向量化并上传到 Milvus 和 PostgreSQL")
|
||||
parser.add_argument("chunks_file", help="vector_chunks.json 文件路径")
|
||||
parser.add_argument("--api-key", default=RELAY_API_KEY, help="中转站 API Key")
|
||||
parser.add_argument("--base-url", default=RELAY_BASE_URL, help="中转站 Base URL")
|
||||
parser.add_argument("--milvus-host", default=MILVUS_HOST, help="Milvus host")
|
||||
parser.add_argument("--milvus-port", default=MILVUS_PORT, help="Milvus port")
|
||||
parser.add_argument("--collection", default=COLLECTION_NAME, help="Milvus collection 名称")
|
||||
parser.add_argument("--batch-size", type=int, default=10, help="Embedding 批量大小(中转站限制最大10)")
|
||||
parser.add_argument("--pg-host", default=PG_HOST, help="PostgreSQL host")
|
||||
parser.add_argument("--pg-port", type=int, default=PG_PORT, help="PostgreSQL port")
|
||||
parser.add_argument("--pg-user", default=PG_USER, help="PostgreSQL user")
|
||||
parser.add_argument("--pg-password", default=PG_PASSWORD, help="PostgreSQL password")
|
||||
parser.add_argument("--pg-database", default=PG_DATABASE, help="PostgreSQL database")
|
||||
args = parser.parse_args()
|
||||
|
||||
upload_to_milvus_and_pg(
|
||||
chunks_file=args.chunks_file,
|
||||
api_key=args.api_key,
|
||||
base_url=args.base_url,
|
||||
milvus_host=args.milvus_host,
|
||||
milvus_port=args.milvus_port,
|
||||
collection_name=args.collection,
|
||||
batch_size=args.batch_size,
|
||||
pg_host=args.pg_host,
|
||||
pg_port=args.pg_port,
|
||||
pg_user=args.pg_user,
|
||||
pg_password=args.pg_password,
|
||||
pg_database=args.pg_database,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
5212
aliyun_parser/vector_chunks.json
Normal file
5212
aliyun_parser/vector_chunks.json
Normal file
File diff suppressed because it is too large
Load Diff
263
aliyun_parser/嵌入和å¬å›ž.md
Normal file
263
aliyun_parser/嵌入和å¬å›ž.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# 文档解析与向量检索说明
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `aliyun_doc_parser.py`:调用阿里云文档智能解析 PDF,生成原始 `layouts.json`
|
||||
- `layouts_to_vector_chunks.py`:把 `layouts.json` 转成适合向量数据库入库的三层结构
|
||||
- `layouts.json`:阿里云返回的原始布局结果
|
||||
- `vector_chunks.json`:转换后的结构化输出
|
||||
|
||||
## 一、`layouts.json` 的结构
|
||||
|
||||
`layouts.json` 顶层是一个数组,每个元素代表一个布局块(layout)。常见字段如下:
|
||||
|
||||
- `type`:主类型,例如 `title`、`text`、`table`、`figure`
|
||||
- `subType`:更细的语义类型,例如 `doc_title`、`para_title`、`para`、`picture`、`pic_title`、`pic_caption`
|
||||
- `text`:当前布局块的纯文本
|
||||
- `markdownContent`:带 markdown 标记的文本
|
||||
- `pageNum`:页码
|
||||
- `index`:页内顺序
|
||||
- `level`:标题层级
|
||||
- `uniqueId`:布局块唯一标识
|
||||
- `blocks`:更细粒度的文本与样式信息
|
||||
- `cells`:表格单元格,仅 `table` 类型存在
|
||||
|
||||
这个结构不是简单 OCR 文本流,而是已经带有版面理解和语义分类的结构化数据。
|
||||
|
||||
## 二、推荐的三层转换结构
|
||||
|
||||
### 1. 结构层 `structure_nodes`
|
||||
|
||||
结构层用于恢复文档标题树,不直接作为最终向量检索单元。
|
||||
|
||||
示例:
|
||||
|
||||
- `1 范围`
|
||||
- `2 规范性引用文件`
|
||||
- `3 术语和定义`
|
||||
- `3.1 儿童三轮车`
|
||||
- `3.2 轮距`
|
||||
|
||||
结构层主要用于给下游 chunk 绑定 `section_path`。
|
||||
|
||||
### 2. 语义层 `semantic_blocks`
|
||||
|
||||
语义层是按文档意义聚合后的内容块,主要分为三类:
|
||||
|
||||
- `section_text`:同一章节下连续正文聚合而成
|
||||
- `table`:表格内容单独成块
|
||||
- `figure`:图、图名、图注等单独成块
|
||||
|
||||
这一层比单 layout 更适合做语义理解,也适合后续做上下文扩展。
|
||||
|
||||
### 3. 检索层 `vector_chunks`
|
||||
|
||||
检索层是最终写进向量数据库的 chunk。
|
||||
|
||||
处理方式:
|
||||
|
||||
- 对 `semantic_blocks` 中较短的块直接入库
|
||||
- 对较长的块按 `max_chars` 再切分
|
||||
- 相邻切片保留 `overlap_chars` 重叠
|
||||
- 每个 chunk 都带完整 metadata,便于后续过滤、重排和邻域扩展
|
||||
|
||||
## 三、当前转换脚本做了什么
|
||||
|
||||
`layouts_to_vector_chunks.py` 当前已经实现:
|
||||
|
||||
1. 过滤目录页噪声(如 `目次`)
|
||||
2. 根据标题层级维护章节路径
|
||||
3. 将正文聚合成 `section_text`
|
||||
4. 将表格单独转成 `table`
|
||||
5. 将图相关内容单独转成 `figure`
|
||||
6. 对长文本继续切分为最终 `vector_chunks`
|
||||
7. 为每个检索 chunk 生成 `embedding_text`
|
||||
|
||||
## 四、为什么不要直接按 layout 入库
|
||||
|
||||
如果把 `layouts.json` 的每条 layout 直接做向量:
|
||||
|
||||
- 颗粒度太碎
|
||||
- 标题和正文容易分离
|
||||
- 表格会丢失结构上下文
|
||||
- 图示信息无法完整表达
|
||||
- 检索命中结果噪声较大
|
||||
|
||||
对于标准文档,最合适的单位通常不是“句子”,而是“条款语义块”。
|
||||
|
||||
## 五、建议的入库字段
|
||||
|
||||
建议向量数据库每条记录至少保存:
|
||||
|
||||
- `embedding_text`:用于生成向量
|
||||
- `text`:原始 chunk 文本
|
||||
- `chunk_id`
|
||||
- `semantic_id`
|
||||
- `chunk_type`:`section_text` / `table` / `figure`
|
||||
- `section_path`
|
||||
- `section_title`
|
||||
- `section_level`
|
||||
- `page_start`
|
||||
- `page_end`
|
||||
- `doc_id`
|
||||
- `doc_title`
|
||||
- `source_ids`
|
||||
|
||||
其中:
|
||||
|
||||
- 向量化字段:`embedding_text`
|
||||
- 展示字段:`text`
|
||||
- 检索增强字段:其余 metadata
|
||||
|
||||
## 六、推荐的检索方式
|
||||
|
||||
不要只做最简单的 top-k 向量搜索,建议采用:
|
||||
|
||||
**向量召回 + metadata 重排 + 邻域扩展**
|
||||
|
||||
### 1. 向量召回
|
||||
|
||||
使用 `vector_chunks[*].embedding_text` 做 embedding,并在向量数据库中检索 top 10 ~ 15 条。
|
||||
|
||||
查询时可以对用户问题做轻微改写,例如:
|
||||
|
||||
原问题:
|
||||
|
||||
`儿童三轮车的定义是什么?`
|
||||
|
||||
可改写为:
|
||||
|
||||
`请检索 GB 14747—2006 儿童三轮车安全要求 中关于“儿童三轮车定义”的条款、术语、表格或图示说明。`
|
||||
|
||||
这样更适合标准文档检索。
|
||||
|
||||
### 2. metadata 重排
|
||||
|
||||
向量召回后,根据 metadata 做轻量规则重排。
|
||||
|
||||
常见规则:
|
||||
|
||||
- `chunk_type == section_text`:对定义类、要求类问题优先级更高
|
||||
- `section_path` 命中查询关键词:例如查询“定义”时,`术语和定义` 章节优先
|
||||
- `chunk_type == table`:对“尺寸 / 参数 / 数值 / 对照 / 要求”类问题加权
|
||||
- `chunk_type == figure`:对“图 / 结构 / 状态 / 示意”类问题加权
|
||||
|
||||
### 3. 邻域扩展
|
||||
|
||||
检索命中的是最终切片,但回答往往需要更完整上下文。
|
||||
|
||||
建议命中某个 `vector_chunk` 后:
|
||||
|
||||
1. 优先回捞同一个 `semantic_id` 下的所有 chunk
|
||||
2. 如果还不够,再补充同 `section_path`、相邻页码或相邻 `chunk_index` 的内容
|
||||
|
||||
这样可以恢复完整条款,而不是只给模型一小段碎片。
|
||||
|
||||
## 七、不同问题的检索重点
|
||||
|
||||
### 1. 定义类问题
|
||||
|
||||
例如:
|
||||
|
||||
- `儿童三轮车的定义是什么?`
|
||||
- `轮距是什么意思?`
|
||||
|
||||
优先检索:
|
||||
|
||||
- `section_text`
|
||||
- `section_path` 中包含 `术语和定义` 的内容
|
||||
|
||||
### 2. 要求类问题
|
||||
|
||||
例如:
|
||||
|
||||
- `外露突出物有什么要求?`
|
||||
- `辅助推杆有哪些安全要求?`
|
||||
|
||||
优先检索:
|
||||
|
||||
- `section_text`
|
||||
- `table`
|
||||
|
||||
### 3. 数值 / 尺寸 / 对照类问题
|
||||
|
||||
例如:
|
||||
|
||||
- `鞍座到脚蹬距离要求是什么?`
|
||||
- `哪些项目需要满足规定尺寸?`
|
||||
|
||||
优先检索:
|
||||
|
||||
- `table`
|
||||
- `section_text`
|
||||
|
||||
### 4. 图示说明类问题
|
||||
|
||||
例如:
|
||||
|
||||
- `正常乘骑状态是什么意思?`
|
||||
- `图1表示什么?`
|
||||
|
||||
优先检索:
|
||||
|
||||
- `figure`
|
||||
- 同章节相邻 `section_text`
|
||||
|
||||
## 八、推荐的最终检索流程
|
||||
|
||||
建议采用以下固定流程:
|
||||
|
||||
1. 用 `vector_chunks.embedding_text` 做 embedding 检索
|
||||
2. 取 top 10 ~ 15 条候选
|
||||
3. 按 `chunk_type + section_path` 做规则重排
|
||||
4. 以 `semantic_id` 为中心回捞完整语义块
|
||||
5. 选 3 ~ 5 组上下文提供给大模型回答
|
||||
|
||||
## 九、给大模型的上下文组织方式
|
||||
|
||||
最终不要直接把原始 JSON 扔给模型,建议整理成如下格式:
|
||||
|
||||
```text
|
||||
[命中片段 1]
|
||||
章节:3 术语和定义 > 3.1 儿童三轮车
|
||||
页码:1-2
|
||||
类型:section_text
|
||||
内容:
|
||||
......
|
||||
|
||||
[命中片段 2]
|
||||
章节:4 要求 > 4.3 外露突出物
|
||||
页码:5
|
||||
类型:section_text
|
||||
内容:
|
||||
......
|
||||
|
||||
[命中片段 3]
|
||||
章节:5 试验方法
|
||||
页码:8
|
||||
类型:table
|
||||
内容:
|
||||
......
|
||||
```
|
||||
|
||||
这种格式更利于模型稳定回答并引用出处。
|
||||
|
||||
## 十、转换命令
|
||||
|
||||
生成三层结构:
|
||||
|
||||
```bash
|
||||
python3 /home/huaci/dev/ai/SuperMew/tests/layouts_to_vector_chunks.py \
|
||||
--layouts /home/huaci/dev/ai/SuperMew/tests/layouts.json \
|
||||
--out /home/huaci/dev/ai/SuperMew/tests/vector_chunks.json
|
||||
```
|
||||
|
||||
自定义切片大小:
|
||||
|
||||
```bash
|
||||
python3 /home/huaci/dev/ai/SuperMew/tests/layouts_to_vector_chunks.py \
|
||||
--layouts /home/huaci/dev/ai/SuperMew/tests/layouts.json \
|
||||
--out /home/huaci/dev/ai/SuperMew/tests/vector_chunks.json \
|
||||
--max-chars 500 \
|
||||
--overlap-chars 80
|
||||
```
|
||||
636
docs/superpowers/plans/2026-05-21-system-status-optimizations.md
Normal file
636
docs/superpowers/plans/2026-05-21-system-status-optimizations.md
Normal file
@@ -0,0 +1,636 @@
|
||||
# System Status Module Optimization Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Upgrade the 系统状态 page from a static one-shot snapshot into a reliable observability dashboard with loading states, service health checks, auto-refresh, BM25/Reranker visibility, and config display fixes.
|
||||
|
||||
**Architecture:** Backend adds a unified `/status/health` aggregate endpoint (Milvus + MinIO + BM25 + Reranker + Sessions) and a TTL cache on `/status/stats`. Frontend adds loading/error states, a refresh button, auto-polling while documents are processing, a health services panel, and config value truncation fixes.
|
||||
|
||||
**Tech Stack:** FastAPI, Python 3.11, React 18, TypeScript, CSS-in-JS (inline styles with theme context)
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| File | Action | Purpose |
|
||||
|------|--------|---------|
|
||||
| `backend/app/api/routes/status.py` | Modify | Add `/health` endpoint, TTL cache on `/stats`, import session store |
|
||||
| `frontend/src/api/status.ts` | Modify | Add `SystemHealth` type + `getSystemHealth()` |
|
||||
| `frontend/src/api/index.ts` | Modify | Export `SystemHealth` interface |
|
||||
| `frontend/src/pages/Status/StatusPage.tsx` | Modify | Loading/error states, refresh, auto-poll, health panel, config fix |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Backend — Add `/status/health` Endpoint + TTL Cache on `/status/stats`
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/api/routes/status.py`
|
||||
|
||||
- [ ] **Step 1: Read the current `status.py`**
|
||||
|
||||
```
|
||||
File: backend/app/api/routes/status.py
|
||||
```
|
||||
|
||||
Note existing imports and route signatures before editing.
|
||||
|
||||
- [ ] **Step 2: Replace `status.py` with the new version**
|
||||
|
||||
Replace the entire content of `backend/app/api/routes/status.py` with:
|
||||
|
||||
```python
|
||||
"""Define API routes for status."""
|
||||
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.config.settings import settings
|
||||
from app.shared.bootstrap import (
|
||||
get_bm25_retriever,
|
||||
get_binary_store,
|
||||
get_conversation_store,
|
||||
get_document_query_service,
|
||||
get_vector_index,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/status", tags=["系统状态"])
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Simple TTL cache for /stats (avoids O(N) doc scan on every request)
|
||||
# ---------------------------------------------------------------------------
|
||||
_stats_cache: dict[str, Any] = {}
|
||||
_stats_cache_time: float = 0.0
|
||||
_STATS_TTL_SECONDS: float = 10.0
|
||||
|
||||
|
||||
def _invalidate_stats_cache() -> None:
|
||||
global _stats_cache_time
|
||||
_stats_cache_time = 0.0
|
||||
|
||||
|
||||
@router.get("/stats")
|
||||
async def get_stats():
|
||||
"""Return document statistics (cached for 10 s)."""
|
||||
global _stats_cache, _stats_cache_time
|
||||
now = time.time()
|
||||
if _stats_cache and (now - _stats_cache_time) < _STATS_TTL_SECONDS:
|
||||
return _stats_cache
|
||||
|
||||
documents = get_document_query_service().list_documents()
|
||||
indexed = sum(1 for d in documents if d.status.value == "indexed")
|
||||
failed = sum(1 for d in documents if d.status.value == "failed")
|
||||
_stats_cache = {
|
||||
"documents_total": len(documents),
|
||||
"documents_indexed": indexed,
|
||||
"documents_failed": failed,
|
||||
"chunks_total": sum(d.chunk_count for d in documents),
|
||||
}
|
||||
_stats_cache_time = now
|
||||
return _stats_cache
|
||||
|
||||
|
||||
@router.get("/config")
|
||||
async def get_config():
|
||||
"""Return system configuration."""
|
||||
return {
|
||||
"embedding_model": settings.embedding_model,
|
||||
"embedding_dim": settings.embedding_dim,
|
||||
"embedding_base_url": settings.embedding_base_url,
|
||||
"milvus_collection": settings.milvus_collection,
|
||||
"parser_backend": settings.parser_backend,
|
||||
"chunk_backend": settings.chunk_backend,
|
||||
"artifact_prefix": settings.document_parse_artifact_prefix,
|
||||
"parser_failure_mode": settings.parser_failure_mode,
|
||||
"llm_provider": settings.llm_provider,
|
||||
"llm_model": settings.llm_model,
|
||||
"document_metadata_path": settings.document_metadata_path,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/milvus/health")
|
||||
async def milvus_health():
|
||||
"""Return Milvus health (kept for backwards compat)."""
|
||||
return get_vector_index().health()
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def get_health():
|
||||
"""Return aggregate health of all backend services."""
|
||||
# --- Milvus ---
|
||||
try:
|
||||
milvus_info = get_vector_index().health()
|
||||
milvus_status = "ok" if milvus_info.get("connected") else "error"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
milvus_info = {}
|
||||
milvus_status = "error"
|
||||
milvus_info["error"] = str(exc)
|
||||
|
||||
# --- MinIO ---
|
||||
try:
|
||||
minio_connected = get_binary_store().client.connected
|
||||
minio_status = "ok" if minio_connected else "error"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
minio_status = "error"
|
||||
minio_connected = False
|
||||
|
||||
# --- BM25 ---
|
||||
bm25 = get_bm25_retriever()
|
||||
|
||||
# --- Sessions ---
|
||||
try:
|
||||
session_count = len(get_conversation_store().list_sessions())
|
||||
except Exception: # noqa: BLE001
|
||||
session_count = 0
|
||||
|
||||
return {
|
||||
"milvus": {"status": milvus_status, **milvus_info},
|
||||
"minio": {"status": minio_status, "connected": minio_connected},
|
||||
"bm25": {"available": bm25 is not None},
|
||||
"reranker": {
|
||||
"enabled": settings.reranker_enabled,
|
||||
"model": settings.reranker_model if settings.reranker_enabled else None,
|
||||
},
|
||||
"sessions": {
|
||||
"active": session_count,
|
||||
"max": settings.session_max_sessions,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify Python syntax**
|
||||
|
||||
```bash
|
||||
cd backend && python -c "from app.api.routes.status import router; print('OK')"
|
||||
```
|
||||
|
||||
Expected output: `OK`
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/api/routes/status.py
|
||||
git commit -m "feat(status): add /health aggregate endpoint and 10s TTL cache on /stats"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Frontend API — Add `SystemHealth` Type + `getSystemHealth()`
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/api/index.ts`
|
||||
- Modify: `frontend/src/api/status.ts`
|
||||
|
||||
- [ ] **Step 1: Add `SystemHealth` interface to `frontend/src/api/index.ts`**
|
||||
|
||||
Open `frontend/src/api/index.ts`. After the `SystemConfig` interface (around line 240), add:
|
||||
|
||||
```typescript
|
||||
export interface ServiceHealth {
|
||||
status: 'ok' | 'error' | 'unknown';
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface SystemHealth {
|
||||
milvus: ServiceHealth & {
|
||||
connected?: boolean;
|
||||
collection_name?: string;
|
||||
num_entities?: number;
|
||||
};
|
||||
minio: ServiceHealth & { connected?: boolean };
|
||||
bm25: { available: boolean };
|
||||
reranker: { enabled: boolean; model?: string | null };
|
||||
sessions: { active: number; max: number };
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add `getSystemHealth()` to `frontend/src/api/status.ts`**
|
||||
|
||||
Open `frontend/src/api/status.ts`. The current content is:
|
||||
|
||||
```typescript
|
||||
import { fetchAPI, type SystemConfig, type SystemStats } from './index';
|
||||
|
||||
export async function getSystemStats(): Promise<SystemStats> {
|
||||
return fetchAPI<SystemStats>('/status/stats');
|
||||
}
|
||||
|
||||
export async function getSystemConfig(): Promise<SystemConfig> {
|
||||
return fetchAPI<SystemConfig>('/status/config');
|
||||
}
|
||||
|
||||
export async function getMilvusHealth(): Promise<{ connected: boolean; collections: string[] }> {
|
||||
return fetchAPI('/status/milvus/health');
|
||||
}
|
||||
|
||||
export type { SystemConfig, SystemStats };
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```typescript
|
||||
import { fetchAPI, type SystemConfig, type SystemHealth, type SystemStats } from './index';
|
||||
|
||||
export async function getSystemStats(): Promise<SystemStats> {
|
||||
return fetchAPI<SystemStats>('/status/stats');
|
||||
}
|
||||
|
||||
export async function getSystemConfig(): Promise<SystemConfig> {
|
||||
return fetchAPI<SystemConfig>('/status/config');
|
||||
}
|
||||
|
||||
export async function getSystemHealth(): Promise<SystemHealth> {
|
||||
return fetchAPI<SystemHealth>('/status/health');
|
||||
}
|
||||
|
||||
export type { SystemConfig, SystemHealth, SystemStats };
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/api/index.ts frontend/src/api/status.ts
|
||||
git commit -m "feat(status): add SystemHealth type and getSystemHealth() API function"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Frontend — Loading/Error States + Refresh Button + Auto-Poll
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/pages/Status/StatusPage.tsx`
|
||||
|
||||
This task replaces the `useEffect` + `loadData` pattern and the top of the component with loading/error/refresh support. Auto-poll fires every 5 s while any document is `parsing` or `pending`.
|
||||
|
||||
- [ ] **Step 1: Read current `StatusPage.tsx`**
|
||||
|
||||
```
|
||||
File: frontend/src/pages/Status/StatusPage.tsx
|
||||
```
|
||||
|
||||
Identify: the `useState` block, `loadData` function, and the single `useEffect`.
|
||||
|
||||
- [ ] **Step 2: Replace the import block and state/effect section**
|
||||
|
||||
Find and replace the imports at the top of the file:
|
||||
|
||||
**Old:**
|
||||
```typescript
|
||||
import React, { useEffect, useState } from 'react';
|
||||
```
|
||||
|
||||
**New:**
|
||||
```typescript
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add `SystemHealth` to the API import**
|
||||
|
||||
**Old:**
|
||||
```typescript
|
||||
import { getSystemStats, getSystemConfig, type SystemStats, type SystemConfig } from '../../api/status';
|
||||
```
|
||||
|
||||
**New:**
|
||||
```typescript
|
||||
import { getSystemStats, getSystemConfig, getSystemHealth, type SystemStats, type SystemConfig, type SystemHealth } from '../../api/status';
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Replace the state declarations and loadData + useEffect block inside the component**
|
||||
|
||||
Find the block starting with `const [stats, setStats]` and ending after the closing `}, []);` of the first `useEffect`. Replace it entirely with:
|
||||
|
||||
```typescript
|
||||
const [stats, setStats] = useState<SystemStats>({
|
||||
documents_total: 0,
|
||||
documents_indexed: 0,
|
||||
documents_failed: 0,
|
||||
chunks_total: 0,
|
||||
});
|
||||
const [config, setConfig] = useState<SystemConfig | null>(null);
|
||||
const [docs, setDocs] = useState<DocInfo[]>([]);
|
||||
const [health, setHealth] = useState<SystemHealth | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const [statsRes, configRes, docsRes, healthRes] = await Promise.all([
|
||||
getSystemStats(),
|
||||
getSystemConfig(),
|
||||
getDocumentList(),
|
||||
getSystemHealth(),
|
||||
]);
|
||||
setStats(statsRes);
|
||||
setConfig(configRes);
|
||||
setDocs(docsRes.docs);
|
||||
setHealth(healthRes);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load status data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Initial load
|
||||
useEffect(() => {
|
||||
void loadData();
|
||||
}, [loadData]);
|
||||
|
||||
// Auto-poll every 5 s while any document is still processing
|
||||
useEffect(() => {
|
||||
const hasProcessing = docs.some(d => d.status === 'parsing' || d.status === 'pending');
|
||||
if (!hasProcessing) return;
|
||||
const id = window.setInterval(() => void loadData(), 5000);
|
||||
return () => window.clearInterval(id);
|
||||
}, [docs, loadData]);
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Add loading banner and error banner at the top of the returned JSX**
|
||||
|
||||
Find the `return (` statement in the component. Inside `<Content>`, right after `<TPattern />`, add:
|
||||
|
||||
```typescript
|
||||
{/* Loading overlay */}
|
||||
{loading && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16, color: theme.text3, fontSize: 13 }}>
|
||||
<span style={{ display: 'inline-block', width: 12, height: 12, borderRadius: '50%', border: `2px solid ${theme.accent}`, borderTopColor: 'transparent', animation: 'spin 0.8s linear infinite' }} />
|
||||
<span className="mono">LOADING...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error banner */}
|
||||
{error && (
|
||||
<div style={{
|
||||
marginBottom: 16,
|
||||
padding: '12px 16px',
|
||||
background: '#d6454520',
|
||||
border: '1px solid #d64545',
|
||||
borderRadius: 8,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}>
|
||||
<span style={{ fontSize: 13, color: '#d64545' }}>{error}</span>
|
||||
<button
|
||||
onClick={() => void loadData()}
|
||||
style={{ background: 'none', border: '1px solid #d64545', borderRadius: 6, color: '#d64545', cursor: 'pointer', padding: '4px 10px', fontSize: 12 }}
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Add a refresh button to the stats section header**
|
||||
|
||||
Find the `<section style={{ marginBottom: 48 }}>` that wraps the 4 stats cards. Add a header row with a refresh button just before the grid `<div>`:
|
||||
|
||||
```typescript
|
||||
<section style={{ marginBottom: 48 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
<h2 style={{ fontSize: 14, fontWeight: 600, color: theme.accent, letterSpacing: '1px', margin: 0 }}>
|
||||
DOCUMENT STATISTICS
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => void loadData()}
|
||||
disabled={loading}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: `1px solid ${theme.border}`,
|
||||
borderRadius: 6,
|
||||
color: theme.text3,
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
padding: '4px 12px',
|
||||
fontSize: 11,
|
||||
opacity: loading ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
↻ 刷新
|
||||
</button>
|
||||
</div>
|
||||
{/* existing 4-card grid stays here unchanged */}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Add CSS keyframe for the spinner**
|
||||
|
||||
Find the `<Content>` wrapping element. Add a `<style>` tag as the very first child inside `<Content>`:
|
||||
|
||||
```typescript
|
||||
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/pages/Status/StatusPage.tsx
|
||||
git commit -m "feat(status): add loading/error states, refresh button, auto-poll for processing docs"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Frontend — Service Health Panel
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/pages/Status/StatusPage.tsx`
|
||||
|
||||
Adds a new section after the stats cards that shows a colored status badge for each service (Milvus, MinIO, BM25, Reranker) plus the active session count.
|
||||
|
||||
- [ ] **Step 1: Add the `ServiceBadge` helper component** (add just before `StatsCard`)
|
||||
|
||||
```typescript
|
||||
const ServiceBadge = ({
|
||||
label,
|
||||
status,
|
||||
detail,
|
||||
}: {
|
||||
label: string;
|
||||
status: 'ok' | 'error' | 'unknown' | boolean;
|
||||
detail?: string;
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
const isOk = status === 'ok' || status === true;
|
||||
const color = isOk ? theme.green : '#d64545';
|
||||
const dot = isOk ? '●' : '●';
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '12px 16px',
|
||||
background: theme.bgCard,
|
||||
borderRadius: 10,
|
||||
border: `1px solid ${theme.border}`,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span style={{ color, fontSize: 10 }}>{dot}</span>
|
||||
<span className="mono" style={{ fontSize: 12, color: theme.text2 }}>{label}</span>
|
||||
</div>
|
||||
<span style={{ fontSize: 12, color, fontWeight: 600 }}>
|
||||
{detail ?? (isOk ? 'OK' : 'ERROR')}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Insert the health section between the stats section and the SYSTEM CONFIGURATION section**
|
||||
|
||||
Find `<section style={{ marginBottom: 48 }}>` for SYSTEM CONFIGURATION. Just before it, insert:
|
||||
|
||||
```typescript
|
||||
<section style={{ marginBottom: 48 }}>
|
||||
<h2 style={{ fontSize: 14, fontWeight: 600, color: theme.accent, marginBottom: 20, letterSpacing: '1px' }}>
|
||||
SERVICE HEALTH
|
||||
</h2>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12, marginBottom: 12 }}>
|
||||
<ServiceBadge
|
||||
label="MILVUS"
|
||||
status={health?.milvus.status ?? 'unknown'}
|
||||
detail={health ? (health.milvus.status === 'ok' ? `${health.milvus.num_entities ?? 0} entities` : 'disconnected') : '—'}
|
||||
/>
|
||||
<ServiceBadge
|
||||
label="MINIO"
|
||||
status={health?.minio.status ?? 'unknown'}
|
||||
detail={health ? (health.minio.status === 'ok' ? 'connected' : 'disconnected') : '—'}
|
||||
/>
|
||||
<ServiceBadge
|
||||
label="BM25 HYBRID"
|
||||
status={health?.bm25.available ?? false}
|
||||
detail={health ? (health.bm25.available ? 'enabled' : 'unavailable') : '—'}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12 }}>
|
||||
<ServiceBadge
|
||||
label="RERANKER"
|
||||
status={health?.reranker.enabled ?? false}
|
||||
detail={health ? (health.reranker.enabled ? health.reranker.model ?? 'enabled' : 'disabled') : '—'}
|
||||
/>
|
||||
<ServiceBadge
|
||||
label="SESSIONS"
|
||||
status="ok"
|
||||
detail={health ? `${health.sessions.active} / ${health.sessions.max}` : '—'}
|
||||
/>
|
||||
<ServiceBadge
|
||||
label="LLM"
|
||||
status="ok"
|
||||
detail={config ? `${config.llm_provider} · ${config.llm_model}` : '—'}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/pages/Status/StatusPage.tsx
|
||||
git commit -m "feat(status): add SERVICE HEALTH panel with Milvus/MinIO/BM25/Reranker/Session badges"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Frontend — Config Value Truncation Fix + Failed Docs Highlight
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/src/pages/Status/StatusPage.tsx`
|
||||
|
||||
- [ ] **Step 1: Fix config value truncation**
|
||||
|
||||
In the SYSTEM CONFIGURATION section, find the `<span>` that renders the config value `v` — it currently renders as:
|
||||
|
||||
```typescript
|
||||
<span style={{ fontSize: 14, fontWeight: 500 }}>{v}</span>
|
||||
```
|
||||
|
||||
Replace it with:
|
||||
|
||||
```typescript
|
||||
<span
|
||||
title={v}
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
maxWidth: 240,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
cursor: 'help',
|
||||
}}
|
||||
>
|
||||
{v}
|
||||
</span>
|
||||
```
|
||||
|
||||
This applies to both the MODELS and STORAGE AND PATHS sub-sections since they share the same `.map(([k, v]) => ...)` render pattern.
|
||||
|
||||
- [ ] **Step 2: Highlight failed documents in the document list**
|
||||
|
||||
In the DOCUMENT INDEX section, find the `docs.map(d => ...)` container `<div>`. Change the border to highlight failed docs:
|
||||
|
||||
**Old:**
|
||||
```typescript
|
||||
border: `1px solid ${theme.border}`,
|
||||
```
|
||||
|
||||
**New:**
|
||||
```typescript
|
||||
border: `1px solid ${d.status === 'failed' ? '#d64545' : d.status === 'parsing' || d.status === 'pending' ? theme.accent + '80' : theme.border}`,
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add pulsing indicator for in-progress documents**
|
||||
|
||||
In the same `docs.map` block, the status badge currently always shows a static green background. Update it to show different colors per status:
|
||||
|
||||
**Old:**
|
||||
```typescript
|
||||
<div style={{
|
||||
padding: '4px 12px',
|
||||
background: d.status === 'failed' ? '#d64545' : theme.green,
|
||||
borderRadius: 6,
|
||||
}}>
|
||||
<span className="mono" style={{ fontSize: 10, fontWeight: 600, color: '#fff' }}>
|
||||
{d.status.toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
**New:**
|
||||
```typescript
|
||||
<div style={{
|
||||
padding: '4px 12px',
|
||||
background:
|
||||
d.status === 'failed' ? '#d64545' :
|
||||
d.status === 'parsing' || d.status === 'pending' ? theme.accent :
|
||||
theme.green,
|
||||
borderRadius: 6,
|
||||
opacity: d.status === 'parsing' || d.status === 'pending' ? 0.85 : 1,
|
||||
}}>
|
||||
<span className="mono" style={{ fontSize: 10, fontWeight: 600, color: '#fff' }}>
|
||||
{d.status === 'parsing' ? '⟳ ' : ''}{d.status.toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/src/pages/Status/StatusPage.tsx
|
||||
git commit -m "fix(status): config value ellipsis truncation, failed doc highlight, parsing doc pulse"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review Checklist
|
||||
|
||||
- [x] Task 1 covers P2 (TTL cache) + P1 (service health backend)
|
||||
- [x] Task 2 adds the `SystemHealth` type that Tasks 3–4 depend on
|
||||
- [x] Task 3 covers P0 (loading/error/refresh/auto-poll)
|
||||
- [x] Task 4 covers P1 (BM25/reranker/sessions visibility)
|
||||
- [x] Task 5 covers P1 (config truncation) + P1 (failed doc visual)
|
||||
- [x] All `getSystemHealth` references match the function name defined in Task 2
|
||||
- [x] `health?.milvus.status` type matches `ServiceHealth.status: 'ok' | 'error' | 'unknown'`
|
||||
- [x] Backend `/health` endpoint uses only already-imported bootstrap functions
|
||||
- [x] No TBD or TODO left in plan
|
||||
944
docs/superpowers/plans/2026-05-27-boss-report-html.md
Normal file
944
docs/superpowers/plans/2026-05-27-boss-report-html.md
Normal file
@@ -0,0 +1,944 @@
|
||||
# Boss Progress Report HTML Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Build a single self-contained HTML file (`boss-report.html`) that serves as a phase-1 progress report for the AI+合规智能中枢 project — usable both for in-person projection and async reading by the boss.
|
||||
|
||||
**Architecture:** Single HTML file, zero external dependencies except Google Fonts CDN. All styles inline via `<style>` tag, all interactivity via vanilla JS `<script>` tag. 8 vertical sections with a fixed top nav that highlights the active section on scroll. IntersectionObserver triggers fade-in animations as sections enter the viewport.
|
||||
|
||||
**Tech Stack:** HTML5, CSS3 (custom properties, grid, flexbox), vanilla JS (IntersectionObserver, scroll spy), Google Fonts (Noto Sans SC + JetBrains Mono)
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| File | Action | Purpose |
|
||||
|------|--------|---------|
|
||||
| `boss-report.html` | **CREATE** (project root) | The single deliverable — complete self-contained report |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: HTML skeleton + visual system + fixed nav
|
||||
|
||||
**Files:**
|
||||
- Create: `boss-report.html`
|
||||
|
||||
- [ ] **Step 1: Create the HTML file with the base skeleton**
|
||||
|
||||
Create `C:\Projects\AIProjects\AIRegulations\AIRegulation-DocAnalysis-Demo\boss-report.html` with the following content:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI+合规智能中枢 — 阶段汇报 2026.05</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* ── Reset & base ── */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--bg: #0a0a12;
|
||||
--bg-card: #12121f;
|
||||
--bg-hover: #1a1a2e;
|
||||
--border: #2a2a40;
|
||||
--text: #ffffff;
|
||||
--text2: #c0c0d0;
|
||||
--text3: #9a9aaa;
|
||||
--accent: #e20074;
|
||||
--accent-dk: #be0060;
|
||||
--green: #00d4aa;
|
||||
--orange: #ff8800;
|
||||
--blue: #4a90d9;
|
||||
--radius: 12px;
|
||||
}
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.mono { font-family: 'JetBrains Mono', monospace; }
|
||||
|
||||
/* ── Fixed nav ── */
|
||||
#nav {
|
||||
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
||||
background: rgba(10,10,18,0.92);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0 40px; height: 52px;
|
||||
}
|
||||
#nav .brand {
|
||||
font-size: 13px; font-weight: 700; color: var(--accent);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
#nav ul {
|
||||
display: flex; gap: 4px; list-style: none;
|
||||
}
|
||||
#nav ul li a {
|
||||
display: block; padding: 6px 12px; border-radius: 6px;
|
||||
font-size: 12px; color: var(--text3); text-decoration: none;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
}
|
||||
#nav ul li a:hover,
|
||||
#nav ul li a.active {
|
||||
background: var(--accent); color: #fff;
|
||||
}
|
||||
|
||||
/* ── Section wrapper ── */
|
||||
.section {
|
||||
padding: 100px 40px 80px;
|
||||
max-width: 1200px; margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ── Fade-in animation ── */
|
||||
.fade-in {
|
||||
opacity: 0; transform: translateY(24px);
|
||||
transition: opacity 0.6s ease, transform 0.6s ease;
|
||||
}
|
||||
.fade-in.visible {
|
||||
opacity: 1; transform: translateY(0);
|
||||
}
|
||||
|
||||
/* ── Section label ── */
|
||||
.section-label {
|
||||
font-size: 11px; font-weight: 700; letter-spacing: 2px;
|
||||
color: var(--accent); text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* ── Section title ── */
|
||||
.section-title {
|
||||
font-size: 32px; font-weight: 900; color: var(--text);
|
||||
margin-bottom: 8px; line-height: 1.2;
|
||||
}
|
||||
|
||||
.section-sub {
|
||||
font-size: 15px; color: var(--text3); margin-bottom: 48px;
|
||||
}
|
||||
|
||||
/* ── Divider line ── */
|
||||
.divider {
|
||||
height: 1px; background: var(--border);
|
||||
max-width: 1200px; margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ── Scrollbar ── */
|
||||
::-webkit-scrollbar { width: 6px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: var(--accent); border-radius: 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ── Fixed Nav ── -->
|
||||
<nav id="nav">
|
||||
<div class="brand">AI+合规智能中枢</div>
|
||||
<ul>
|
||||
<li><a href="#hero">概览</a></li>
|
||||
<li><a href="#background">背景</a></li>
|
||||
<li><a href="#progress">进展</a></li>
|
||||
<li><a href="#architecture">架构</a></li>
|
||||
<li><a href="#demo">功能</a></li>
|
||||
<li><a href="#value">价值</a></li>
|
||||
<li><a href="#roadmap">路线图</a></li>
|
||||
<li><a href="#next">下一步</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Sections will be added in subsequent tasks -->
|
||||
|
||||
<script>
|
||||
// Scroll spy
|
||||
const sections = document.querySelectorAll('section[id]');
|
||||
const navLinks = document.querySelectorAll('#nav ul li a');
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
navLinks.forEach(a => a.classList.remove('active'));
|
||||
const active = document.querySelector(`#nav a[href="#${entry.target.id}"]`);
|
||||
if (active) active.classList.add('active');
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.3 });
|
||||
|
||||
sections.forEach(s => observer.observe(s));
|
||||
|
||||
// Fade-in
|
||||
const fadeObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
fadeObserver.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
document.querySelectorAll('.fade-in').forEach(el => fadeObserver.observe(el));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Open in browser to verify nav renders**
|
||||
|
||||
Open `boss-report.html` in a browser. Expected: dark background page with a fixed top nav bar showing "AI+合规智能中枢" on the left and 8 nav links on the right. Page body should be empty below the nav.
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Section 1 — Hero 封面
|
||||
|
||||
**Files:**
|
||||
- Modify: `boss-report.html` — add hero section before `<script>` tag
|
||||
|
||||
- [ ] **Step 1: Add Hero section HTML**
|
||||
|
||||
Insert the following after the `<!-- Sections will be added in subsequent tasks -->` comment:
|
||||
|
||||
```html
|
||||
<!-- ── S1: Hero ── -->
|
||||
<section id="hero" style="min-height:100vh; display:flex; align-items:center; position:relative; overflow:hidden;">
|
||||
<!-- Background glow -->
|
||||
<div style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:600px; height:600px; background:radial-gradient(circle, rgba(226,0,116,0.12) 0%, transparent 70%); pointer-events:none;"></div>
|
||||
<!-- Grid pattern -->
|
||||
<div style="position:absolute; inset:0; background-image:linear-gradient(var(--border) 1px, transparent 1px), linear-gradient(90deg, var(--border) 1px, transparent 1px); background-size:60px 60px; opacity:0.3; pointer-events:none;"></div>
|
||||
|
||||
<div class="section fade-in" style="width:100%; padding-top:52px;">
|
||||
<!-- Tag -->
|
||||
<div style="display:inline-flex; align-items:center; gap:8px; background:rgba(226,0,116,0.12); border:1px solid rgba(226,0,116,0.3); border-radius:20px; padding:6px 16px; margin-bottom:32px;">
|
||||
<span style="width:6px;height:6px;border-radius:50%;background:var(--accent);display:inline-block;animation:pulse 2s infinite;"></span>
|
||||
<span class="mono" style="font-size:11px; color:var(--accent); letter-spacing:1px;">INTERNAL · AI 合规项目组 · 2026.05</span>
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h1 style="font-size:clamp(36px,5vw,64px); font-weight:900; line-height:1.1; margin-bottom:16px;">
|
||||
AI + 合规智能中枢<br>
|
||||
<span style="color:var(--accent);">阶段性进展汇报</span>
|
||||
</h1>
|
||||
<p style="font-size:18px; color:var(--text2); max-width:640px; margin-bottom:60px;">
|
||||
基于 RAG 的多模块法规合规智能平台 — EMS/EHS 合规响应效率提升,降低人工成本
|
||||
</p>
|
||||
|
||||
<!-- KPI cards -->
|
||||
<div style="display:grid; grid-template-columns:repeat(3,1fr); gap:20px; max-width:720px;">
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-top:3px solid var(--accent); border-radius:var(--radius); padding:24px 28px;">
|
||||
<div class="mono" style="font-size:48px; font-weight:700; color:var(--accent); line-height:1;">5</div>
|
||||
<div style="font-size:13px; color:var(--text3); margin-top:6px;">功能模块<br>已完成/开发中</div>
|
||||
</div>
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-top:3px solid var(--green); border-radius:var(--radius); padding:24px 28px;">
|
||||
<div class="mono" style="font-size:48px; font-weight:700; color:var(--green); line-height:1;">17+</div>
|
||||
<div style="font-size:13px; color:var(--text3); margin-top:6px;">API 接口<br>已设计并文档化</div>
|
||||
</div>
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-top:3px solid var(--orange); border-radius:var(--radius); padding:24px 28px;">
|
||||
<div class="mono" style="font-size:48px; font-weight:700; color:var(--orange); line-height:1;">6+</div>
|
||||
<div style="font-size:13px; color:var(--text3); margin-top:6px;">法规来源<br>接入覆盖</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scroll hint -->
|
||||
<div style="margin-top:60px; display:flex; align-items:center; gap:10px; color:var(--text3); font-size:13px;">
|
||||
<div style="width:1px; height:40px; background:linear-gradient(to bottom, transparent, var(--accent));"></div>
|
||||
向下滚动查看详情
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
```
|
||||
|
||||
Add this keyframe to the `<style>` block (before the closing `</style>`):
|
||||
|
||||
```css
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify in browser**
|
||||
|
||||
Refresh `boss-report.html`. Expected: Full-height dark hero section with grid background, pink glow, large title with magenta accent, 3 KPI cards (5 / 17+ / 6+), and a scroll hint at the bottom.
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Section 2 — 项目背景与痛点
|
||||
|
||||
**Files:**
|
||||
- Modify: `boss-report.html` — add background section after the hero divider
|
||||
|
||||
- [ ] **Step 1: Add background section HTML**
|
||||
|
||||
Insert the following after the hero `<div class="divider"></div>`:
|
||||
|
||||
```html
|
||||
<!-- ── S2: Background ── -->
|
||||
<section id="background">
|
||||
<div class="section fade-in">
|
||||
<div class="section-label">项目背景</div>
|
||||
<h2 class="section-title">为什么要做这个系统?</h2>
|
||||
<p class="section-sub">当前汽车行业合规管理面临三大核心痛点,传统人工方式已无法满足需求</p>
|
||||
|
||||
<div style="display:grid; grid-template-columns:repeat(3,1fr); gap:20px;">
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); padding:28px; position:relative; overflow:hidden;">
|
||||
<div style="position:absolute; top:0; left:0; right:0; height:3px; background:linear-gradient(90deg,var(--accent),var(--accent-dk));"></div>
|
||||
<div style="font-size:36px; margin-bottom:16px;">📋</div>
|
||||
<h3 style="font-size:18px; font-weight:700; color:var(--text); margin-bottom:10px;">法规碎片化</h3>
|
||||
<p style="font-size:14px; color:var(--text2); line-height:1.7;">
|
||||
GB、MIIT、UN-ECE、IATF 16949、ISO 45001、EUR-Lex 等多源法规并存,更新频繁,人工跟踪极易遗漏,合规窗口期短。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); padding:28px; position:relative; overflow:hidden;">
|
||||
<div style="position:absolute; top:0; left:0; right:0; height:3px; background:linear-gradient(90deg,var(--orange),#ff5500);"></div>
|
||||
<div style="font-size:36px; margin-bottom:16px;">⏱️</div>
|
||||
<h3 style="font-size:18px; font-weight:700; color:var(--text); margin-bottom:10px;">响应周期长</h3>
|
||||
<p style="font-size:14px; color:var(--text2); line-height:1.7;">
|
||||
从法规发布到内部解读、影响评估、整改计划,人工流程往往需要数周,无法满足快速迭代的合规要求。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); padding:28px; position:relative; overflow:hidden;">
|
||||
<div style="position:absolute; top:0; left:0; right:0; height:3px; background:linear-gradient(90deg,var(--green),#00a080);"></div>
|
||||
<div style="font-size:36px; margin-bottom:16px;">💼</div>
|
||||
<h3 style="font-size:18px; font-weight:700; color:var(--text); margin-bottom:10px;">人工成本高</h3>
|
||||
<p style="font-size:14px; color:var(--text2); line-height:1.7;">
|
||||
合规专家大量时间消耗在文档检索、条款比对、报告撰写等重复性工作上,高价值判断时间被严重压缩。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify in browser**
|
||||
|
||||
Refresh. Expected: Three cards with colored top borders (magenta / orange / green), emoji icons, Chinese titles and descriptions. Each card should animate in on scroll.
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Section 3 — 已完成进展
|
||||
|
||||
**Files:**
|
||||
- Modify: `boss-report.html` — add progress section
|
||||
|
||||
- [ ] **Step 1: Add progress section HTML**
|
||||
|
||||
Insert after the background `<div class="divider"></div>`:
|
||||
|
||||
```html
|
||||
<!-- ── S3: Progress ── -->
|
||||
<section id="progress">
|
||||
<div class="section fade-in">
|
||||
<div class="section-label">阶段成果</div>
|
||||
<h2 class="section-title">已完成的工作</h2>
|
||||
<p class="section-sub">5 个核心功能模块,覆盖从法规感知到报告输出的完整合规链路</p>
|
||||
|
||||
<div style="display:grid; grid-template-columns:repeat(1,1fr); gap:16px;">
|
||||
|
||||
<!-- Module 1 -->
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); padding:24px 28px; display:grid; grid-template-columns:auto 1fr auto; gap:20px; align-items:start;">
|
||||
<div style="width:48px; height:48px; border-radius:10px; background:rgba(226,0,116,0.15); border:1px solid rgba(226,0,116,0.3); display:flex; align-items:center; justify-content:center; font-size:22px;">📡</div>
|
||||
<div>
|
||||
<div style="display:flex; align-items:center; gap:10px; margin-bottom:8px;">
|
||||
<h3 style="font-size:16px; font-weight:700;">法规感知模块</h3>
|
||||
<span class="mono" style="font-size:10px; background:rgba(0,212,170,0.15); color:var(--green); border:1px solid rgba(0,212,170,0.3); padding:2px 8px; border-radius:4px;">✓ 已完成</span>
|
||||
</div>
|
||||
<ul style="font-size:13px; color:var(--text2); line-height:2; list-style:none;">
|
||||
<li>▸ 接入 MIIT、UN-ECE、ISO、国标委、EUR-Lex、IATF 六大法规来源,实时抓取动态</li>
|
||||
<li>▸ 支持按来源/影响等级(高/中/低)过滤,KPI 统计卡(总计/高影响/近90天)</li>
|
||||
<li>▸ 法规详情侧边面板,关联 AI 影响分析与条款摘要</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mono" style="font-size:11px; color:var(--text3); white-space:nowrap;">模块 01/05</div>
|
||||
</div>
|
||||
|
||||
<!-- Module 2 -->
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); padding:24px 28px; display:grid; grid-template-columns:auto 1fr auto; gap:20px; align-items:start;">
|
||||
<div style="width:48px; height:48px; border-radius:10px; background:rgba(74,144,217,0.15); border:1px solid rgba(74,144,217,0.3); display:flex; align-items:center; justify-content:center; font-size:22px;">📄</div>
|
||||
<div>
|
||||
<div style="display:flex; align-items:center; gap:10px; margin-bottom:8px;">
|
||||
<h3 style="font-size:16px; font-weight:700;">文档分析模块</h3>
|
||||
<span class="mono" style="font-size:10px; background:rgba(0,212,170,0.15); color:var(--green); border:1px solid rgba(0,212,170,0.3); padding:2px 8px; border-radius:4px;">✓ 已完成</span>
|
||||
</div>
|
||||
<ul style="font-size:13px; color:var(--text2); line-height:2; list-style:none;">
|
||||
<li>▸ PDF/Word 文档上传解析,支持阿里云 DocMind OCR + MinerU 双引擎</li>
|
||||
<li>▸ RAG 检索增强问答:BM25 稀疏 + 向量混合检索,Cross-Encoder 精排</li>
|
||||
<li>▸ 文档知识库管理,Workspace 隔离,支持多文档联合问答</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mono" style="font-size:11px; color:var(--text3); white-space:nowrap;">模块 02/05</div>
|
||||
</div>
|
||||
|
||||
<!-- Module 3 -->
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); padding:24px 28px; display:grid; grid-template-columns:auto 1fr auto; gap:20px; align-items:start;">
|
||||
<div style="width:48px; height:48px; border-radius:10px; background:rgba(255,136,0,0.15); border:1px solid rgba(255,136,0,0.3); display:flex; align-items:center; justify-content:center; font-size:22px;">🏭</div>
|
||||
<div>
|
||||
<div style="display:flex; align-items:center; gap:10px; margin-bottom:8px;">
|
||||
<h3 style="font-size:16px; font-weight:700;">EHS 合规模块</h3>
|
||||
<span class="mono" style="font-size:10px; background:rgba(255,136,0,0.15); color:var(--orange); border:1px solid rgba(255,136,0,0.3); padding:2px 8px; border-radius:4px;">⟳ 进行中</span>
|
||||
</div>
|
||||
<ul style="font-size:13px; color:var(--text2); line-height:2; list-style:none;">
|
||||
<li>▸ EHS 风险评估引擎,对接 ISO 45001 PDCA 体系,SIF 重伤风险识别</li>
|
||||
<li>▸ 环境/职业健康/安全三维度合规检查,结构化整改建议生成</li>
|
||||
<li>▸ 合规状态仪表盘,风险等级可视化,历史趋势追踪</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mono" style="font-size:11px; color:var(--text3); white-space:nowrap;">模块 03/05</div>
|
||||
</div>
|
||||
|
||||
<!-- Module 4 -->
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); padding:24px 28px; display:grid; grid-template-columns:auto 1fr auto; gap:20px; align-items:start;">
|
||||
<div style="width:48px; height:48px; border-radius:10px; background:rgba(0,212,170,0.15); border:1px solid rgba(0,212,170,0.3); display:flex; align-items:center; justify-content:center; font-size:22px;">🔌</div>
|
||||
<div>
|
||||
<div style="display:flex; align-items:center; gap:10px; margin-bottom:8px;">
|
||||
<h3 style="font-size:16px; font-weight:700;">API 集成模块</h3>
|
||||
<span class="mono" style="font-size:10px; background:rgba(0,212,170,0.15); color:var(--green); border:1px solid rgba(0,212,170,0.3); padding:2px 8px; border-radius:4px;">✓ 已完成</span>
|
||||
</div>
|
||||
<ul style="font-size:13px; color:var(--text2); line-height:2; list-style:none;">
|
||||
<li>▸ 17+ REST API 接口完整设计,涵盖知识库、合规检查、订阅推送全链路</li>
|
||||
<li>▸ RBAC 权限模型,5 角色分级管控(管理员/合规专员/EHS专员/审核员/查看员)</li>
|
||||
<li>▸ 预留 PLM、ERP、OA、MES 系统集成接口,支持企业现有系统接入</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mono" style="font-size:11px; color:var(--text3); white-space:nowrap;">模块 04/05</div>
|
||||
</div>
|
||||
|
||||
<!-- Module 5 -->
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); padding:24px 28px; display:grid; grid-template-columns:auto 1fr auto; gap:20px; align-items:start;">
|
||||
<div style="width:48px; height:48px; border-radius:10px; background:rgba(123,104,238,0.15); border:1px solid rgba(123,104,238,0.3); display:flex; align-items:center; justify-content:center; font-size:22px;">📊</div>
|
||||
<div>
|
||||
<div style="display:flex; align-items:center; gap:10px; margin-bottom:8px;">
|
||||
<h3 style="font-size:16px; font-weight:700;">报告生成模块</h3>
|
||||
<span class="mono" style="font-size:10px; background:rgba(255,136,0,0.15); color:var(--orange); border:1px solid rgba(255,136,0,0.3); padding:2px 8px; border-radius:4px;">⟳ 进行中</span>
|
||||
</div>
|
||||
<ul style="font-size:13px; color:var(--text2); line-height:2; list-style:none;">
|
||||
<li>▸ AI 自动生成合规分析报告,支持 PDF/Word/Excel 多格式导出</li>
|
||||
<li>▸ 报告模板引擎,适配不同法规体系(车辆安全/数据合规/EHS/碳排放)</li>
|
||||
<li>▸ 报告版本管理与审批流,支持与 SharePoint/Confluence 协同</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mono" style="font-size:11px; color:var(--text3); white-space:nowrap;">模块 05/05</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify in browser**
|
||||
|
||||
Refresh. Expected: 5 module rows, each with colored icon box, title with status badge (绿色"✓ 已完成" or 橙色"⟳ 进行中"), and 3 bullet points of detail.
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Section 4 — 技术架构概览
|
||||
|
||||
**Files:**
|
||||
- Modify: `boss-report.html` — add architecture section
|
||||
|
||||
- [ ] **Step 1: Add architecture section HTML**
|
||||
|
||||
Insert after the progress `<div class="divider"></div>`:
|
||||
|
||||
```html
|
||||
<!-- ── S4: Architecture ── -->
|
||||
<section id="architecture">
|
||||
<div class="section fade-in">
|
||||
<div class="section-label">技术架构</div>
|
||||
<h2 class="section-title">系统分层架构</h2>
|
||||
<p class="section-sub">清洁架构 + Ports & Adapters,5 层分离,高内聚低耦合,可独立替换各层实现</p>
|
||||
|
||||
<div style="display:flex; flex-direction:column; gap:10px; max-width:960px; margin:0 auto;">
|
||||
|
||||
<!-- Layer 1: User -->
|
||||
<div class="fade-in" style="border-radius:10px; border:1px solid rgba(226,0,116,0.4); background:rgba(226,0,116,0.06); overflow:hidden;">
|
||||
<div style="padding:12px 20px; background:linear-gradient(135deg,#b0005a,#e20074); display:flex; align-items:center; gap:12px;">
|
||||
<span style="font-size:18px;">👥</span>
|
||||
<span style="font-weight:700; font-size:15px;">用户层</span>
|
||||
<span style="font-size:11px; opacity:0.75;">合规专员 / EHS专员 / 审核员 / 管理员 / 查看员</span>
|
||||
</div>
|
||||
<div style="padding:14px 20px; display:flex; gap:10px; flex-wrap:wrap;">
|
||||
<span style="background:rgba(226,0,116,0.12); border:1px solid rgba(226,0,116,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:#f04090;">法规感知</span>
|
||||
<span style="background:rgba(226,0,116,0.12); border:1px solid rgba(226,0,116,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:#f04090;">文档分析</span>
|
||||
<span style="background:rgba(226,0,116,0.12); border:1px solid rgba(226,0,116,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:#f04090;">EHS合规</span>
|
||||
<span style="background:rgba(226,0,116,0.12); border:1px solid rgba(226,0,116,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:#f04090;">API集成</span>
|
||||
<span style="background:rgba(226,0,116,0.12); border:1px solid rgba(226,0,116,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:#f04090;">报告生成</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align:center; color:var(--accent); font-size:18px;">↕</div>
|
||||
|
||||
<!-- Layer 2: Frontend -->
|
||||
<div class="fade-in" style="border-radius:10px; border:1px solid rgba(0,212,170,0.4); background:rgba(0,212,170,0.06); overflow:hidden;">
|
||||
<div style="padding:12px 20px; background:linear-gradient(135deg,#00a080,#00d4aa); display:flex; align-items:center; gap:12px;">
|
||||
<span style="font-size:18px;">🖥️</span>
|
||||
<span style="font-weight:700; font-size:15px;">前端层</span>
|
||||
<span style="font-size:11px; opacity:0.75;">React 19 · TypeScript · Vite · React Router v7</span>
|
||||
</div>
|
||||
<div style="padding:14px 20px; display:flex; gap:10px; flex-wrap:wrap;">
|
||||
<span style="background:rgba(0,212,170,0.1); border:1px solid rgba(0,212,170,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--green);">KeepAlive 路由</span>
|
||||
<span style="background:rgba(0,212,170,0.1); border:1px solid rgba(0,212,170,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--green);">三主题系统 (Dark/Dim/Light)</span>
|
||||
<span style="background:rgba(0,212,170,0.1); border:1px solid rgba(0,212,170,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--green);">shadcn/ui 组件库</span>
|
||||
<span style="background:rgba(0,212,170,0.1); border:1px solid rgba(0,212,170,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--green);">Tailwind v4</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align:center; color:var(--accent); font-size:18px;">↕</div>
|
||||
|
||||
<!-- Layer 3: API -->
|
||||
<div class="fade-in" style="border-radius:10px; border:1px solid rgba(74,144,217,0.4); background:rgba(74,144,217,0.06); overflow:hidden;">
|
||||
<div style="padding:12px 20px; background:linear-gradient(135deg,#2060a0,#4a90d9); display:flex; align-items:center; gap:12px;">
|
||||
<span style="font-size:18px;">⚡</span>
|
||||
<span style="font-weight:700; font-size:15px;">API 层</span>
|
||||
<span style="font-size:11px; opacity:0.75;">FastAPI · kbmp-service · mcp-server · Worker</span>
|
||||
</div>
|
||||
<div style="padding:14px 20px; display:flex; gap:10px; flex-wrap:wrap;">
|
||||
<span style="background:rgba(74,144,217,0.1); border:1px solid rgba(74,144,217,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--blue);">知识库管理 API</span>
|
||||
<span style="background:rgba(74,144,217,0.1); border:1px solid rgba(74,144,217,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--blue);">合规检查 API</span>
|
||||
<span style="background:rgba(74,144,217,0.1); border:1px solid rgba(74,144,217,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--blue);">文档解析 API</span>
|
||||
<span style="background:rgba(74,144,217,0.1); border:1px solid rgba(74,144,217,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--blue);">检索问答 API</span>
|
||||
<span style="background:rgba(74,144,217,0.1); border:1px solid rgba(74,144,217,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--blue);">订阅推送 API</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align:center; color:var(--accent); font-size:18px;">↕</div>
|
||||
|
||||
<!-- Layer 4: AI Engine -->
|
||||
<div class="fade-in" style="border-radius:10px; border:1px solid rgba(255,136,0,0.4); background:rgba(255,136,0,0.06); overflow:hidden;">
|
||||
<div style="padding:12px 20px; background:linear-gradient(135deg,#c05000,#ff8800); display:flex; align-items:center; gap:12px;">
|
||||
<span style="font-size:18px;">🧠</span>
|
||||
<span style="font-weight:700; font-size:15px;">AI 引擎层</span>
|
||||
<span style="font-size:11px; opacity:0.75;">RAG Pipeline · LLM · Embedding · Reranker</span>
|
||||
</div>
|
||||
<div style="padding:14px 20px; display:flex; gap:10px; flex-wrap:wrap;">
|
||||
<span style="background:rgba(255,136,0,0.1); border:1px solid rgba(255,136,0,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--orange);">Qwen / DeepSeek LLM</span>
|
||||
<span style="background:rgba(255,136,0,0.1); border:1px solid rgba(255,136,0,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--orange);">BM25 稀疏检索</span>
|
||||
<span style="background:rgba(255,136,0,0.1); border:1px solid rgba(255,136,0,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--orange);">向量语义检索</span>
|
||||
<span style="background:rgba(255,136,0,0.1); border:1px solid rgba(255,136,0,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--orange);">Cross-Encoder 精排</span>
|
||||
<span style="background:rgba(255,136,0,0.1); border:1px solid rgba(255,136,0,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:var(--orange);">阿里云 DocMind OCR</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align:center; color:var(--accent); font-size:18px;">↕</div>
|
||||
|
||||
<!-- Layer 5: Infrastructure -->
|
||||
<div class="fade-in" style="border-radius:10px; border:1px solid rgba(123,104,238,0.4); background:rgba(123,104,238,0.06); overflow:hidden;">
|
||||
<div style="padding:12px 20px; background:linear-gradient(135deg,#5040b0,#7b68ee); display:flex; align-items:center; gap:12px;">
|
||||
<span style="font-size:18px;">🗄️</span>
|
||||
<span style="font-weight:700; font-size:15px;">基础设施层</span>
|
||||
<span style="font-size:11px; opacity:0.75;">Milvus · MySQL · S3/OSS · Prometheus · Grafana</span>
|
||||
</div>
|
||||
<div style="padding:14px 20px; display:flex; gap:10px; flex-wrap:wrap;">
|
||||
<span style="background:rgba(123,104,238,0.1); border:1px solid rgba(123,104,238,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:#9b8cf0;">Milvus 向量数据库</span>
|
||||
<span style="background:rgba(123,104,238,0.1); border:1px solid rgba(123,104,238,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:#9b8cf0;">MySQL 结构化存储</span>
|
||||
<span style="background:rgba(123,104,238,0.1); border:1px solid rgba(123,104,238,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:#9b8cf0;">对象存储 (文档/Markdown)</span>
|
||||
<span style="background:rgba(123,104,238,0.1); border:1px solid rgba(123,104,238,0.25); border-radius:6px; padding:4px 12px; font-size:12px; color:#9b8cf0;">Prometheus + Grafana</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify in browser**
|
||||
|
||||
Refresh. Expected: 5 colored layered boxes with arrows between them, each showing layer name, tech stack, and module tags.
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Section 5 — Demo 核心功能
|
||||
|
||||
**Files:**
|
||||
- Modify: `boss-report.html` — add demo section
|
||||
|
||||
- [ ] **Step 1: Add demo section HTML**
|
||||
|
||||
Insert after the architecture `<div class="divider"></div>`:
|
||||
|
||||
```html
|
||||
<!-- ── S5: Demo ── -->
|
||||
<section id="demo">
|
||||
<div class="section fade-in">
|
||||
<div class="section-label">产品演示</div>
|
||||
<h2 class="section-title">核心功能一览</h2>
|
||||
<p class="section-sub">Demo 已部署,以下为五大核心功能模块的界面与能力说明</p>
|
||||
|
||||
<div style="display:grid; grid-template-columns:repeat(3,1fr); gap:20px;">
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden;">
|
||||
<div style="height:140px; background:linear-gradient(135deg,#1a0a20,#2a1035); display:flex; align-items:center; justify-content:center; position:relative; border-bottom:1px solid var(--border);">
|
||||
<div style="position:absolute; inset:0; display:flex; align-items:center; justify-content:center; flex-direction:column; gap:8px;">
|
||||
<div style="font-size:40px;">📡</div>
|
||||
<div style="display:flex; gap:6px;">
|
||||
<span style="width:8px;height:8px;border-radius:50%;background:#d64545;"></span>
|
||||
<span style="width:8px;height:8px;border-radius:50%;background:#ff8800;"></span>
|
||||
<span style="width:8px;height:8px;border-radius:50%;background:#00d4aa;"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position:absolute; top:10px; right:10px; background:rgba(0,212,170,0.2); color:var(--green); font-size:10px; padding:2px 8px; border-radius:4px; font-weight:700;">LIVE</div>
|
||||
</div>
|
||||
<div style="padding:18px 20px;">
|
||||
<h3 style="font-size:15px; font-weight:700; margin-bottom:8px;">法规感知</h3>
|
||||
<p style="font-size:13px; color:var(--text2); line-height:1.6;">实时监控六大法规源,高/中/低影响分级,支持过滤筛选,点击查看 AI 解读</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden;">
|
||||
<div style="height:140px; background:linear-gradient(135deg,#0a1020,#101a35); display:flex; align-items:center; justify-content:center; position:relative; border-bottom:1px solid var(--border);">
|
||||
<div style="font-size:40px;">💬</div>
|
||||
<div style="position:absolute; bottom:12px; left:12px; right:12px; background:rgba(74,144,217,0.15); border:1px solid rgba(74,144,217,0.3); border-radius:6px; padding:6px 10px; font-size:11px; color:var(--blue);">"GB 18384 对我们的 BMS 设计有什么影响?"</div>
|
||||
</div>
|
||||
<div style="padding:18px 20px;">
|
||||
<h3 style="font-size:15px; font-weight:700; margin-bottom:8px;">文档分析 Q&A</h3>
|
||||
<p style="font-size:13px; color:var(--text2); line-height:1.6;">上传法规/技术文档,AI 即可回答专业问题,引用原文段落,支持追问</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden;">
|
||||
<div style="height:140px; background:linear-gradient(135deg,#0a1808,#101f0f); display:flex; align-items:center; justify-content:center; position:relative; border-bottom:1px solid var(--border);">
|
||||
<div style="font-size:40px;">🏭</div>
|
||||
<div style="position:absolute; top:10px; left:10px; background:rgba(255,68,68,0.2); color:#ff6b6b; font-size:10px; padding:2px 8px; border-radius:4px; font-weight:700;">⚠ 高风险</div>
|
||||
</div>
|
||||
<div style="padding:18px 20px;">
|
||||
<h3 style="font-size:15px; font-weight:700; margin-bottom:8px;">EHS 合规检查</h3>
|
||||
<p style="font-size:13px; color:var(--text2); line-height:1.6;">上传 EHS 文档,自动识别风险条款,生成整改建议,对标 ISO 45001</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden;">
|
||||
<div style="height:140px; background:linear-gradient(135deg,#081510,#0f1f18); display:flex; align-items:center; justify-content:center; position:relative; border-bottom:1px solid var(--border);">
|
||||
<div style="font-size:40px;">🔌</div>
|
||||
<div style="position:absolute; bottom:12px; left:12px; right:12px; display:flex; gap:6px;">
|
||||
<span style="background:rgba(0,212,170,0.15); color:var(--green); font-size:10px; padding:2px 8px; border-radius:4px;">PLM</span>
|
||||
<span style="background:rgba(0,212,170,0.15); color:var(--green); font-size:10px; padding:2px 8px; border-radius:4px;">ERP</span>
|
||||
<span style="background:rgba(0,212,170,0.15); color:var(--green); font-size:10px; padding:2px 8px; border-radius:4px;">OA</span>
|
||||
<span style="background:rgba(0,212,170,0.15); color:var(--green); font-size:10px; padding:2px 8px; border-radius:4px;">MES</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:18px 20px;">
|
||||
<h3 style="font-size:15px; font-weight:700; margin-bottom:8px;">系统集成</h3>
|
||||
<p style="font-size:13px; color:var(--text2); line-height:1.6;">RBAC 权限管控,REST API 开放,预留企业系统对接接口,支持 Webhook 订阅</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; grid-column:span 2;">
|
||||
<div style="height:140px; background:linear-gradient(135deg,#101018,#181830); display:flex; align-items:center; justify-content:center; position:relative; border-bottom:1px solid var(--border);">
|
||||
<div style="font-size:40px;">📊</div>
|
||||
<div style="position:absolute; bottom:12px; right:12px; display:flex; gap:8px;">
|
||||
<span style="background:rgba(226,0,116,0.15); color:var(--accent); font-size:10px; padding:2px 8px; border-radius:4px;">PDF</span>
|
||||
<span style="background:rgba(226,0,116,0.15); color:var(--accent); font-size:10px; padding:2px 8px; border-radius:4px;">Word</span>
|
||||
<span style="background:rgba(226,0,116,0.15); color:var(--accent); font-size:10px; padding:2px 8px; border-radius:4px;">Excel</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:18px 20px;">
|
||||
<h3 style="font-size:15px; font-weight:700; margin-bottom:8px;">合规报告生成</h3>
|
||||
<p style="font-size:13px; color:var(--text2); line-height:1.6;">AI 自动生成合规分析报告,多格式导出,支持模板定制,含版本管理与审批流</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify in browser**
|
||||
|
||||
Refresh. Expected: 5 feature cards in a 3-column grid (last card spanning 2 columns), each with a dark-tinted preview area and description below.
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Section 6 — 业务价值对比
|
||||
|
||||
**Files:**
|
||||
- Modify: `boss-report.html` — add value comparison section
|
||||
|
||||
- [ ] **Step 1: Add value section HTML**
|
||||
|
||||
Insert after the demo `<div class="divider"></div>`:
|
||||
|
||||
```html
|
||||
<!-- ── S6: Value ── -->
|
||||
<section id="value">
|
||||
<div class="section fade-in">
|
||||
<div class="section-label">业务价值</div>
|
||||
<h2 class="section-title">有与没有的差距</h2>
|
||||
<p class="section-sub">量化对比:AI 智能中枢 vs 传统人工合规方式</p>
|
||||
|
||||
<div style="display:grid; grid-template-columns:1fr auto 1fr; gap:0; align-items:stretch; max-width:960px; margin:0 auto 48px;">
|
||||
|
||||
<!-- Without -->
|
||||
<div class="fade-in" style="background:rgba(255,68,68,0.06); border:1px solid rgba(255,68,68,0.2); border-radius:12px 0 0 12px; padding:28px;">
|
||||
<div style="font-size:13px; font-weight:700; color:#ff6b6b; margin-bottom:20px; letter-spacing:1px;">❌ 传统人工方式</div>
|
||||
<ul style="list-style:none; display:flex; flex-direction:column; gap:16px;">
|
||||
<li style="font-size:14px; color:var(--text2);"><span style="color:#ff6b6b; font-weight:700;">数周</span> — 法规解读 + 影响评估周期</li>
|
||||
<li style="font-size:14px; color:var(--text2);"><span style="color:#ff6b6b; font-weight:700;">人工检索</span> — 依赖专家经验,易遗漏</li>
|
||||
<li style="font-size:14px; color:var(--text2);"><span style="color:#ff6b6b; font-weight:700;">碎片化</span> — 各系统数据孤岛,无法联动</li>
|
||||
<li style="font-size:14px; color:var(--text2);"><span style="color:#ff6b6b; font-weight:700;">高成本</span> — 合规专家时间大量消耗在重复工作</li>
|
||||
<li style="font-size:14px; color:var(--text2);"><span style="color:#ff6b6b; font-weight:700;">被动响应</span> — 法规变化后才开始评估</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Arrow -->
|
||||
<div style="display:flex; align-items:center; justify-content:center; padding:0 20px; background:var(--bg-card); border-top:1px solid var(--border); border-bottom:1px solid var(--border);">
|
||||
<div style="font-size:28px; color:var(--accent);">→</div>
|
||||
</div>
|
||||
|
||||
<!-- With -->
|
||||
<div class="fade-in" style="background:rgba(0,212,170,0.06); border:1px solid rgba(0,212,170,0.2); border-radius:0 12px 12px 0; padding:28px;">
|
||||
<div style="font-size:13px; font-weight:700; color:var(--green); margin-bottom:20px; letter-spacing:1px;">✅ AI+合规智能中枢</div>
|
||||
<ul style="list-style:none; display:flex; flex-direction:column; gap:16px;">
|
||||
<li style="font-size:14px; color:var(--text2);"><span style="color:var(--green); font-weight:700;">分钟级</span> — 法规发布即自动解读,影响秒级评估</li>
|
||||
<li style="font-size:14px; color:var(--text2);"><span style="color:var(--green); font-weight:700;">RAG 智能检索</span> — 语义理解,精准定位条款</li>
|
||||
<li style="font-size:14px; color:var(--text2);"><span style="color:var(--green); font-weight:700;">统一平台</span> — 多源法规、多系统数据融合</li>
|
||||
<li style="font-size:14px; color:var(--text2);"><span style="color:var(--green); font-weight:700;">降低重复劳动 70%+</span> — 专家聚焦高价值判断</li>
|
||||
<li style="font-size:14px; color:var(--text2);"><span style="color:var(--green); font-weight:700;">主动预警</span> — 订阅推送,法规变化即刻触达</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metric highlights -->
|
||||
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:16px; max-width:960px; margin:0 auto;">
|
||||
<div class="fade-in" style="text-align:center; padding:24px; background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius);">
|
||||
<div class="mono" style="font-size:36px; font-weight:700; color:var(--accent);">70%+</div>
|
||||
<div style="font-size:12px; color:var(--text3); margin-top:6px;">重复性工作减少</div>
|
||||
</div>
|
||||
<div class="fade-in" style="text-align:center; padding:24px; background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius);">
|
||||
<div class="mono" style="font-size:36px; font-weight:700; color:var(--green);">分钟级</div>
|
||||
<div style="font-size:12px; color:var(--text3); margin-top:6px;">法规响应时间</div>
|
||||
</div>
|
||||
<div class="fade-in" style="text-align:center; padding:24px; background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius);">
|
||||
<div class="mono" style="font-size:36px; font-weight:700; color:var(--orange);">6+</div>
|
||||
<div style="font-size:12px; color:var(--text3); margin-top:6px;">法规源统一管理</div>
|
||||
</div>
|
||||
<div class="fade-in" style="text-align:center; padding:24px; background:var(--bg-card); border:1px solid var(--border); border-radius:var(--radius);">
|
||||
<div class="mono" style="font-size:36px; font-weight:700; color:var(--blue);">5</div>
|
||||
<div style="font-size:12px; color:var(--text3); margin-top:6px;">业务场景覆盖</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify in browser**
|
||||
|
||||
Refresh. Expected: Side-by-side red/green comparison panel, then 4 metric highlight cards below with large colored numbers.
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Section 7 — 路线图 & Section 8 — 下一步 + 页脚
|
||||
|
||||
**Files:**
|
||||
- Modify: `boss-report.html` — add roadmap, next steps, and footer
|
||||
|
||||
- [ ] **Step 1: Add roadmap + next steps + footer HTML**
|
||||
|
||||
Insert after the value `<div class="divider"></div>`:
|
||||
|
||||
```html
|
||||
<!-- ── S7: Roadmap ── -->
|
||||
<section id="roadmap">
|
||||
<div class="section fade-in">
|
||||
<div class="section-label">发展路线</div>
|
||||
<h2 class="section-title">四阶段推进计划</h2>
|
||||
<p class="section-sub">当前处于第二阶段"能力建设",核心模块已完成,正在推进全量接入</p>
|
||||
|
||||
<div style="position:relative; padding:40px 0;">
|
||||
<!-- Timeline line -->
|
||||
<div style="position:absolute; top:60px; left:12.5%; right:12.5%; height:3px; background:linear-gradient(90deg, var(--green) 50%, var(--border) 50%);"></div>
|
||||
|
||||
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:20px; position:relative;">
|
||||
|
||||
<!-- Phase 1: DONE -->
|
||||
<div class="fade-in" style="text-align:center;">
|
||||
<div style="width:48px; height:48px; border-radius:50%; background:var(--green); border:3px solid var(--green); display:flex; align-items:center; justify-content:center; margin:0 auto 16px; font-size:18px; position:relative; z-index:1;">✓</div>
|
||||
<div style="background:rgba(0,212,170,0.1); border:1px solid rgba(0,212,170,0.3); border-radius:10px; padding:18px 14px;">
|
||||
<div class="mono" style="font-size:10px; color:var(--green); margin-bottom:6px;">阶段 01 · 已完成</div>
|
||||
<h4 style="font-size:14px; font-weight:700; margin-bottom:10px;">POC 验证</h4>
|
||||
<ul style="font-size:12px; color:var(--text2); list-style:none; text-align:left; line-height:1.9;">
|
||||
<li>▸ 架构设计完成</li>
|
||||
<li>▸ 技术栈验证</li>
|
||||
<li>▸ 核心 RAG 流程打通</li>
|
||||
<li>▸ 前端原型搭建</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phase 2: ACTIVE -->
|
||||
<div class="fade-in" style="text-align:center;">
|
||||
<div style="width:48px; height:48px; border-radius:50%; background:var(--accent); border:3px solid var(--accent); display:flex; align-items:center; justify-content:center; margin:0 auto 16px; font-size:18px; position:relative; z-index:1; animation:pulse 2s infinite; box-shadow:0 0 20px rgba(226,0,116,0.4);">⟳</div>
|
||||
<div style="background:rgba(226,0,116,0.1); border:2px solid var(--accent); border-radius:10px; padding:18px 14px; box-shadow:0 0 24px rgba(226,0,116,0.12);">
|
||||
<div class="mono" style="font-size:10px; color:var(--accent); margin-bottom:6px;">阶段 02 · 进行中 ◀ 当前</div>
|
||||
<h4 style="font-size:14px; font-weight:700; margin-bottom:10px;">能力建设</h4>
|
||||
<ul style="font-size:12px; color:var(--text2); list-style:none; text-align:left; line-height:1.9;">
|
||||
<li>▸ 5 大模块开发</li>
|
||||
<li>▸ API 接口完整设计</li>
|
||||
<li>▸ RBAC 权限系统</li>
|
||||
<li>▸ Demo 可演示</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phase 3 -->
|
||||
<div class="fade-in" style="text-align:center;">
|
||||
<div style="width:48px; height:48px; border-radius:50%; background:var(--bg-card); border:3px solid var(--border); display:flex; align-items:center; justify-content:center; margin:0 auto 16px; font-size:18px; position:relative; z-index:1; color:var(--text3);">3</div>
|
||||
<div style="background:var(--bg-card); border:1px solid var(--border); border-radius:10px; padding:18px 14px;">
|
||||
<div class="mono" style="font-size:10px; color:var(--text3); margin-bottom:6px;">阶段 03 · 规划中</div>
|
||||
<h4 style="font-size:14px; font-weight:700; color:var(--text2); margin-bottom:10px;">全量接入</h4>
|
||||
<ul style="font-size:12px; color:var(--text3); list-style:none; text-align:left; line-height:1.9;">
|
||||
<li>▸ IT 系统对接</li>
|
||||
<li>▸ 2-3 个试点部门</li>
|
||||
<li>▸ 用户培训</li>
|
||||
<li>▸ 数据迁移</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phase 4 -->
|
||||
<div class="fade-in" style="text-align:center;">
|
||||
<div style="width:48px; height:48px; border-radius:50%; background:var(--bg-card); border:3px solid var(--border); display:flex; align-items:center; justify-content:center; margin:0 auto 16px; font-size:18px; position:relative; z-index:1; color:var(--text3);">4</div>
|
||||
<div style="background:var(--bg-card); border:1px solid var(--border); border-radius:10px; padding:18px 14px;">
|
||||
<div class="mono" style="font-size:10px; color:var(--text3); margin-bottom:6px;">阶段 04 · 规划中</div>
|
||||
<h4 style="font-size:14px; font-weight:700; color:var(--text2); margin-bottom:10px;">规模运营</h4>
|
||||
<ul style="font-size:12px; color:var(--text3); list-style:none; text-align:left; line-height:1.9;">
|
||||
<li>▸ API 平台开放</li>
|
||||
<li>▸ PLM/ERP/MES 集成</li>
|
||||
<li>▸ 全公司推广</li>
|
||||
<li>▸ 持续优化迭代</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- ── S8: Next Steps ── -->
|
||||
<section id="next">
|
||||
<div class="section fade-in">
|
||||
<div class="section-label">下一步</div>
|
||||
<h2 class="section-title">近期行动项</h2>
|
||||
<p class="section-sub">阶段二收尾,推进阶段三启动所需的关键决策与资源</p>
|
||||
|
||||
<div style="display:grid; grid-template-columns:repeat(2,1fr); gap:20px; max-width:800px;">
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-left:4px solid var(--accent); border-radius:var(--radius); padding:24px;">
|
||||
<div style="font-size:12px; font-weight:700; color:var(--accent); margin-bottom:12px; letter-spacing:1px;">🎯 技术</div>
|
||||
<ul style="font-size:14px; color:var(--text2); list-style:none; line-height:2;">
|
||||
<li>□ 完成 EHS 模块与报告模块开发</li>
|
||||
<li>□ 后端 kbmp-service 生产环境部署</li>
|
||||
<li>□ 端到端集成测试</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-left:4px solid var(--green); border-radius:var(--radius); padding:24px;">
|
||||
<div style="font-size:12px; font-weight:700; color:var(--green); margin-bottom:12px; letter-spacing:1px;">📋 业务</div>
|
||||
<ul style="font-size:14px; color:var(--text2); list-style:none; line-height:2;">
|
||||
<li>□ 确定试点部门(EHS/合规/法务)</li>
|
||||
<li>□ 收集 2-3 个真实合规场景需求</li>
|
||||
<li>□ IT 系统对接方案评估</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-left:4px solid var(--orange); border-radius:var(--radius); padding:24px;">
|
||||
<div style="font-size:12px; font-weight:700; color:var(--orange); margin-bottom:12px; letter-spacing:1px;">🔐 安全合规</div>
|
||||
<ul style="font-size:14px; color:var(--text2); list-style:none; line-height:2;">
|
||||
<li>□ PIPL/DSL 数据合规评审</li>
|
||||
<li>□ 内部安全审计配合</li>
|
||||
<li>□ 数据脱敏与权限细化</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="fade-in" style="background:var(--bg-card); border:1px solid var(--border); border-left:4px solid var(--blue); border-radius:var(--radius); padding:24px;">
|
||||
<div style="font-size:12px; font-weight:700; color:var(--blue); margin-bottom:12px; letter-spacing:1px;">💰 资源申请</div>
|
||||
<ul style="font-size:14px; color:var(--text2); list-style:none; line-height:2;">
|
||||
<li>□ GPU/云资源扩容评估</li>
|
||||
<li>□ 阿里云 DocMind 商业授权</li>
|
||||
<li>□ 阶段三人力资源确认</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Footer ── -->
|
||||
<footer style="border-top:1px solid var(--border); padding:40px; text-align:center; background:var(--bg-card);">
|
||||
<div style="max-width:1200px; margin:0 auto; display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:16px;">
|
||||
<div>
|
||||
<div style="font-size:16px; font-weight:700; color:var(--text); margin-bottom:4px;">AI+合规智能中枢</div>
|
||||
<div style="font-size:12px; color:var(--text3);">EMS & EHS Compliance Intelligence Hub · Internal · Confidential</div>
|
||||
</div>
|
||||
<div style="text-align:right;">
|
||||
<div class="mono" style="font-size:13px; color:var(--accent); font-weight:600;">2026.05</div>
|
||||
<div style="font-size:12px; color:var(--text3); margin-top:4px;">AI 合规项目组 · T-Systems</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Finalize JS — move `querySelectorAll` after all sections are added**
|
||||
|
||||
In the `<script>` block, ensure the JS runs after all sections are in the DOM. The script block is already at the bottom of `<body>`, so this is already correct. No changes needed.
|
||||
|
||||
- [ ] **Step 3: Full page verification**
|
||||
|
||||
Open `boss-report.html` in browser and scroll through all 8 sections. Expected:
|
||||
1. ✅ Hero — dark full-viewport with KPI cards
|
||||
2. ✅ Background — 3 pain point cards
|
||||
3. ✅ Progress — 5 module rows with status badges
|
||||
4. ✅ Architecture — 5 colored layer boxes
|
||||
5. ✅ Demo — 5 feature cards in grid
|
||||
6. ✅ Value — left/right comparison + 4 metric cards
|
||||
7. ✅ Roadmap — 4-phase timeline, phase 2 highlighted in magenta
|
||||
8. ✅ Next Steps — 4 action cards + footer
|
||||
9. ✅ Fixed nav highlights active section on scroll
|
||||
10. ✅ All `.fade-in` elements animate on scroll into view
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
**Spec coverage check:**
|
||||
- ✅ Hero + KPI → Task 2
|
||||
- ✅ Background / pain points → Task 3
|
||||
- ✅ 5 module progress → Task 4
|
||||
- ✅ Architecture diagram → Task 5
|
||||
- ✅ Demo features → Task 6
|
||||
- ✅ Business value comparison → Task 7
|
||||
- ✅ Roadmap (4-phase) → Task 8
|
||||
- ✅ Next steps + footer → Task 8
|
||||
- ✅ Fixed nav with scroll spy → Task 1
|
||||
- ✅ Fade-in animations → Task 1
|
||||
- ✅ Dark theme, T-Systems magenta → Task 1
|
||||
|
||||
**Placeholder scan:** None found. All code is complete and explicit.
|
||||
|
||||
**Type consistency:** No typed interfaces — pure HTML/CSS. All color variables defined in `:root` in Task 1 and used consistently throughout.
|
||||
|
||||
**Scope check:** Single file deliverable, well within one implementation plan.
|
||||
2015
docs/superpowers/plans/2026-06-03-frontend-redesign.md
Normal file
2015
docs/superpowers/plans/2026-06-03-frontend-redesign.md
Normal file
File diff suppressed because it is too large
Load Diff
5658
frontend/package-lock.json
generated
5658
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface TLogoProps {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const TLogo: React.FC<TLogoProps> = ({ size = 40 }) => (
|
||||
<img
|
||||
src="/logo/t_mobile_logo_transparent.png"
|
||||
alt="T-Systems"
|
||||
width={size}
|
||||
height={size}
|
||||
style={{ objectFit: 'contain' }}
|
||||
/>
|
||||
);
|
||||
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts';
|
||||
|
||||
export const TPattern: React.FC = () => {
|
||||
const { theme, isDark } = useTheme();
|
||||
const patternOpacity = isDark ? 0.03 : 0.04;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
width: 300,
|
||||
height: 300,
|
||||
opacity: patternOpacity,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
<svg width="300" height="300" viewBox="0 0 300 300">
|
||||
<defs>
|
||||
<pattern id="grid" width="30" height="30" patternUnits="userSpaceOnUse">
|
||||
<path d="M 30 0 L 0 0 0 30" fill="none" stroke={theme.accent} strokeWidth="1"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="300" height="300" fill="url(#grid)"/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Moon, Sun, SunMedium } from 'lucide-react';
|
||||
|
||||
import { useTheme } from '../../contexts';
|
||||
import { Button } from '../shadcn/ui/button';
|
||||
|
||||
const NEXT_LABELS: Record<string, string> = {
|
||||
dark: '过渡色模式',
|
||||
dim: '亮色模式',
|
||||
light: '暗色模式',
|
||||
};
|
||||
|
||||
export const ThemeToggle: React.FC = () => {
|
||||
const { themeMode, toggleTheme } = useTheme();
|
||||
|
||||
// Shows the NEXT state's icon: dark→SunMedium(dim next), dim→Sun(light next), light→Moon(dark next)
|
||||
const Icon =
|
||||
themeMode === 'dark' ? SunMedium :
|
||||
themeMode === 'dim' ? Sun :
|
||||
Moon;
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={toggleTheme}
|
||||
variant="outline"
|
||||
size="icon-lg"
|
||||
className="rounded-xl border-border bg-card text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
aria-label={`切换到${NEXT_LABELS[themeMode]}`}
|
||||
title={`切换到${NEXT_LABELS[themeMode]}`}
|
||||
>
|
||||
<Icon />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import type { AppTabConfig } from '../../router/tabs';
|
||||
import { shellFrameClassName } from './shell-config';
|
||||
|
||||
interface ContentLayoutProps {
|
||||
children: ReactNode;
|
||||
tab: AppTabConfig;
|
||||
}
|
||||
|
||||
const widthClassMap = {
|
||||
default: 'mx-auto w-full max-w-[1120px]',
|
||||
wide: 'mx-auto w-full max-w-[1440px]',
|
||||
full: 'w-full',
|
||||
} as const;
|
||||
|
||||
export function ContentLayout({ children, tab }: ContentLayoutProps) {
|
||||
const widthClass = widthClassMap[tab.contentWidth];
|
||||
|
||||
return (
|
||||
<main className="flex min-h-0 flex-1 bg-t-bg">
|
||||
<div
|
||||
className={[
|
||||
shellFrameClassName,
|
||||
'relative flex min-h-0 flex-1 justify-center py-8',
|
||||
].join(' ')}
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
'relative flex min-h-0 w-full',
|
||||
widthClass,
|
||||
tab.fillHeight ? 'overflow-hidden' : '',
|
||||
].join(' ')}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Badge } from '../shadcn/ui/badge';
|
||||
import { Separator } from '../shadcn/ui/separator';
|
||||
|
||||
import { shellFrameClassName, shellMeta } from './shell-config';
|
||||
|
||||
export function FooterLayout() {
|
||||
return (
|
||||
<footer className="border-t border-t-border bg-t-bg">
|
||||
<div
|
||||
className={[
|
||||
shellFrameClassName,
|
||||
'flex items-center justify-between gap-6 py-4 text-xs text-t-text3',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="min-w-0 max-w-[360px]">
|
||||
<div className="mono mb-1 tracking-[0.18em] text-t-text2">
|
||||
{shellMeta.productLabel}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-3 whitespace-nowrap rounded-xl border border-border bg-card px-3 py-2 shadow-sm">
|
||||
<Badge variant="secondary" className="mono border-0 bg-transparent px-0 py-0 text-[11px] tracking-[0.24em] text-muted-foreground">
|
||||
{shellMeta.version}
|
||||
</Badge>
|
||||
<Separator orientation="vertical" className="h-4 bg-border" />
|
||||
<Badge variant="outline" className="mono gap-2 border-0 bg-transparent px-0 py-0 text-[11px] tracking-[0.24em] text-[var(--t-green)]">
|
||||
<span className="size-2 rounded-full bg-[var(--t-green)]" />
|
||||
{shellMeta.status}
|
||||
</Badge>
|
||||
<Separator orientation="vertical" className="h-4 bg-border" />
|
||||
<span className="mono text-[11px] tracking-[0.18em] text-muted-foreground">
|
||||
{shellMeta.surface}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { TLogo } from '../common/TLogo';
|
||||
|
||||
export function HeaderBrand() {
|
||||
return (
|
||||
<div className="flex min-w-[280px] shrink-0 items-center gap-4 whitespace-nowrap">
|
||||
<div className="shrink-0">
|
||||
<TLogo size={46} />
|
||||
</div>
|
||||
<div className="flex min-w-0 items-center gap-2 whitespace-nowrap">
|
||||
<span className="text-[1.18rem] font-semibold tracking-[-0.04em] text-foreground">
|
||||
T-Systems
|
||||
</span>
|
||||
<span className="text-[1.02rem] font-light text-muted-foreground">
|
||||
Regulation
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { AppTabConfig } from '../../router/tabs';
|
||||
import { ThemeToggle } from '../common/ThemeToggle';
|
||||
import { Badge } from '../shadcn/ui/badge';
|
||||
import { Separator } from '../shadcn/ui/separator';
|
||||
|
||||
import { HeaderBrand } from './HeaderBrand';
|
||||
import { shellFrameClassName, shellMeta } from './shell-config';
|
||||
import { TabNav } from './TabNav';
|
||||
|
||||
interface HeaderLayoutProps {
|
||||
activeTab: AppTabConfig;
|
||||
}
|
||||
|
||||
export function HeaderLayout({ activeTab }: HeaderLayoutProps) {
|
||||
return (
|
||||
<header className="sticky top-0 z-[100] border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80">
|
||||
<div className={[shellFrameClassName, 'flex h-20 items-center gap-8'].join(' ')}>
|
||||
<HeaderBrand />
|
||||
<div className="min-w-0 flex-1 self-stretch overflow-hidden">
|
||||
<TabNav activeTab={activeTab} />
|
||||
</div>
|
||||
<div className="ml-auto flex shrink-0 items-center gap-3 self-center">
|
||||
<ThemeToggle />
|
||||
<div className="flex h-11 shrink-0 items-center gap-3 whitespace-nowrap rounded-xl border border-border bg-card px-3 shadow-sm">
|
||||
<Badge variant="secondary" className="mono border-0 bg-transparent px-0 py-0 text-[11px] tracking-[0.24em] text-muted-foreground">
|
||||
{shellMeta.version}
|
||||
</Badge>
|
||||
<Separator orientation="vertical" className="h-4 bg-border" />
|
||||
<Badge variant="outline" className="mono gap-2 border-0 bg-transparent px-0 py-0 text-[11px] tracking-[0.24em] text-[var(--t-green)]">
|
||||
<span className="size-2 rounded-full bg-[var(--t-green)]" />
|
||||
{shellMeta.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { appTabs, type AppTabConfig } from '../../router/tabs';
|
||||
|
||||
interface KeepAliveViewportProps {
|
||||
activeTab: AppTabConfig;
|
||||
}
|
||||
|
||||
export function KeepAliveViewport({ activeTab }: KeepAliveViewportProps) {
|
||||
const [mountedTabIds, setMountedTabIds] = useState<string[]>([activeTab.id]);
|
||||
|
||||
useEffect(() => {
|
||||
const timerId = window.setTimeout(() => {
|
||||
setMountedTabIds((prev) => (prev.includes(activeTab.id) ? prev : [...prev, activeTab.id]));
|
||||
}, 0);
|
||||
return () => window.clearTimeout(timerId);
|
||||
}, [activeTab.id]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1">
|
||||
{appTabs.map((tab) => {
|
||||
const shouldRender = tab.keepAlive ? mountedTabIds.includes(tab.id) : tab.id === activeTab.id;
|
||||
if (!shouldRender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const TabComponent = tab.component;
|
||||
const isActive = tab.id === activeTab.id;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={tab.id}
|
||||
aria-hidden={!isActive}
|
||||
className={[
|
||||
'min-h-0 flex-1',
|
||||
isActive ? 'flex' : 'hidden',
|
||||
].join(' ')}
|
||||
>
|
||||
<TabComponent />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import type { AppTabConfig, TabId } from '../../router/tabs';
|
||||
import { appTabs } from '../../router/tabs';
|
||||
|
||||
interface TabNavProps {
|
||||
activeTab: AppTabConfig;
|
||||
}
|
||||
|
||||
interface IndicatorStyle {
|
||||
opacity: number;
|
||||
transform: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
const reducedMotionQuery = '(prefers-reduced-motion: reduce)';
|
||||
|
||||
export function TabNav({ activeTab }: TabNavProps) {
|
||||
const navigate = useNavigate();
|
||||
const trackRef = useRef<HTMLDivElement | null>(null);
|
||||
const buttonRefs = useRef<Record<TabId, HTMLButtonElement | null>>({
|
||||
perception: null,
|
||||
docs: null,
|
||||
compliance: null,
|
||||
status: null,
|
||||
rag: null,
|
||||
});
|
||||
const [indicatorStyle, setIndicatorStyle] = useState<IndicatorStyle>({
|
||||
opacity: 0,
|
||||
transform: 'translateX(0px)',
|
||||
width: 0,
|
||||
});
|
||||
const [reducedMotion, setReducedMotion] = useState(false);
|
||||
|
||||
const handleValueChange = (value: string) => {
|
||||
const nextTab = appTabs.find((tab) => tab.id === value);
|
||||
if (nextTab && nextTab.path !== activeTab.path) {
|
||||
navigate(nextTab.path);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia(reducedMotionQuery);
|
||||
const updateMotionPreference = () => {
|
||||
setReducedMotion(mediaQuery.matches);
|
||||
};
|
||||
|
||||
updateMotionPreference();
|
||||
mediaQuery.addEventListener('change', updateMotionPreference);
|
||||
|
||||
return () => {
|
||||
mediaQuery.removeEventListener('change', updateMotionPreference);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const updateIndicator = () => {
|
||||
const trackNode = trackRef.current;
|
||||
const activeNode = buttonRefs.current[activeTab.id];
|
||||
if (!trackNode || !activeNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trackRect = trackNode.getBoundingClientRect();
|
||||
const activeRect = activeNode.getBoundingClientRect();
|
||||
|
||||
setIndicatorStyle({
|
||||
opacity: 1,
|
||||
transform: `translateX(${activeRect.left - trackRect.left}px)`,
|
||||
width: activeRect.width,
|
||||
});
|
||||
};
|
||||
|
||||
updateIndicator();
|
||||
window.addEventListener('resize', updateIndicator);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', updateIndicator);
|
||||
};
|
||||
}, [activeTab.id]);
|
||||
|
||||
return (
|
||||
<nav className="flex h-full min-w-0 items-stretch overflow-x-auto overflow-y-hidden">
|
||||
<div
|
||||
ref={trackRef}
|
||||
className="relative flex h-full min-w-max flex-nowrap items-stretch gap-3 pr-6"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={[
|
||||
'pointer-events-none absolute bottom-0 left-0 h-0.5 rounded-full bg-primary',
|
||||
reducedMotion
|
||||
? 'transition-none'
|
||||
: 'transition-[transform,width,opacity] duration-220 ease-[cubic-bezier(0.22,1,0.36,1)]',
|
||||
].join(' ')}
|
||||
style={indicatorStyle}
|
||||
/>
|
||||
{appTabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
ref={(node) => {
|
||||
buttonRefs.current[tab.id] = node;
|
||||
}}
|
||||
data-shell-tab="true"
|
||||
type="button"
|
||||
onClick={() => handleValueChange(tab.id satisfies TabId)}
|
||||
aria-current={tab.id === activeTab.id ? 'page' : undefined}
|
||||
className={[
|
||||
'inline-flex h-full shrink-0 appearance-none items-center justify-center border-0 border-b-2 border-transparent bg-transparent px-5 pt-1 text-[0.95rem] font-medium tracking-[0.02em] outline-none',
|
||||
reducedMotion
|
||||
? 'transition-none'
|
||||
: 'transition-[color,opacity] duration-200 ease-out',
|
||||
tab.id === activeTab.id
|
||||
? 'text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
].join(' ')}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import type { ComponentType } from 'react';
|
||||
|
||||
import { CompliancePage } from '../pages/Compliance';
|
||||
import { DocsPage } from '../pages/Docs';
|
||||
import { PerceptionPage } from '../pages/Perception';
|
||||
import { RagChatPage } from '../pages/RagChat';
|
||||
import { StatusPage } from '../pages/Status';
|
||||
|
||||
export type TabId = 'perception' | 'docs' | 'compliance' | 'status' | 'rag';
|
||||
|
||||
export type ContentWidth = 'default' | 'wide' | 'full';
|
||||
|
||||
export interface AppTabConfig {
|
||||
id: TabId;
|
||||
path: string;
|
||||
label: string;
|
||||
component: ComponentType;
|
||||
keepAlive: boolean;
|
||||
contentWidth: ContentWidth;
|
||||
fillHeight?: boolean;
|
||||
}
|
||||
|
||||
export const appTabs: AppTabConfig[] = [
|
||||
{
|
||||
id: 'perception',
|
||||
path: '/perception',
|
||||
label: '智能感知',
|
||||
component: PerceptionPage,
|
||||
keepAlive: true,
|
||||
contentWidth: 'wide',
|
||||
fillHeight: true,
|
||||
},
|
||||
{
|
||||
id: 'docs',
|
||||
path: '/docs',
|
||||
label: '文档管理',
|
||||
component: DocsPage,
|
||||
keepAlive: true,
|
||||
contentWidth: 'default',
|
||||
},
|
||||
{
|
||||
id: 'compliance',
|
||||
path: '/compliance',
|
||||
label: '合规分析',
|
||||
component: CompliancePage,
|
||||
keepAlive: true,
|
||||
contentWidth: 'wide',
|
||||
fillHeight: true,
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
path: '/status',
|
||||
label: '系统状态',
|
||||
component: StatusPage,
|
||||
keepAlive: true,
|
||||
contentWidth: 'default',
|
||||
},
|
||||
{
|
||||
id: 'rag',
|
||||
path: '/rag',
|
||||
label: '法规对话',
|
||||
component: RagChatPage,
|
||||
keepAlive: true,
|
||||
contentWidth: 'wide',
|
||||
fillHeight: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const defaultTab = appTabs.find((tab) => tab.id === 'compliance') ?? appTabs[0];
|
||||
|
||||
export function getTabByPath(pathname: string): AppTabConfig {
|
||||
return appTabs.find((tab) => tab.path === pathname) ?? defaultTab;
|
||||
}
|
||||
@@ -411,7 +411,6 @@ body {
|
||||
.docs-card { display: flex; flex-direction: column; gap: 0; }
|
||||
.doc-row { display: flex; gap: 10px; padding: 10px 0; border-top: 1px solid var(--border); }
|
||||
.doc-score { font-size: 11px; font-weight: 700; font-family: var(--font-mono); color: var(--success); width: 34px; flex-shrink: 0; padding-top: 1px; }
|
||||
.doc-info {}
|
||||
.doc-name { font-size: 12px; font-weight: 600; margin-bottom: 3px; }
|
||||
.doc-clause { font-size: 10px; font-family: var(--font-mono); color: var(--muted); margin-left: 5px; }
|
||||
.doc-snippet { font-size: 11px; color: var(--muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
|
||||
@@ -1,916 +0,0 @@
|
||||
// generate-team-ppt.js
|
||||
const pptxgenjs = require("C:/nvm4w/nodejs/node_modules/pptxgenjs");
|
||||
|
||||
// ── 颜色常量 ──────────────────────────────────────────────
|
||||
const C = {
|
||||
accent: "E20074",
|
||||
accentDk: "BE0060",
|
||||
bg: "F7F7FA",
|
||||
bgCard: "FFFFFF",
|
||||
bgHover: "F0F0F5",
|
||||
border: "E0E0EA",
|
||||
text: "1A1A2E",
|
||||
text2: "4A4A6A",
|
||||
text3: "8888AA",
|
||||
green: "00896A",
|
||||
orange: "CC6200",
|
||||
blue: "2A68C8",
|
||||
purple: "5A46B4",
|
||||
red: "C0392B",
|
||||
darkBg: "1A1A2E",
|
||||
};
|
||||
|
||||
// ── 辅助函数 ─────────────────────────────────────────────
|
||||
function makeShadow() {
|
||||
return { type: "outer", color: "000000", opacity: 0.08, blur: 6, offset: 2, angle: 45 };
|
||||
}
|
||||
|
||||
function topBar(slide) {
|
||||
slide.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: 0.04,
|
||||
fill: { type: "grad", stops: [{ position: 0, color: C.accent }, { position: 100, color: C.accentDk }] },
|
||||
line: { color: C.accent, width: 0 },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function sectionLabel(slide, text, x, y) {
|
||||
slide.addText(text, {
|
||||
x, y, w: 9, h: 0.25,
|
||||
fontSize: 9, bold: true, color: C.accent,
|
||||
charSpacing: 3, fontFace: "Calibri",
|
||||
});
|
||||
}
|
||||
|
||||
function sectionTitle(slide, text, x, y) {
|
||||
slide.addText(text, {
|
||||
x, y, w: 9, h: 0.5,
|
||||
fontSize: 26, bold: true, color: C.text, fontFace: "Calibri",
|
||||
});
|
||||
}
|
||||
|
||||
function sectionSub(slide, text, x, y) {
|
||||
slide.addText(text, {
|
||||
x, y, w: 10, h: 0.3,
|
||||
fontSize: 12, color: C.text3, fontFace: "Calibri",
|
||||
});
|
||||
}
|
||||
|
||||
function slideNum(slide, n, total) {
|
||||
slide.addText(`${n} / ${total}`, {
|
||||
x: 12, y: 7.1, w: 1.2, h: 0.25,
|
||||
fontSize: 9, color: C.text3, align: "right", fontFace: "Calibri Light",
|
||||
});
|
||||
}
|
||||
|
||||
// ── 初始化 PPT ───────────────────────────────────────────
|
||||
const prs = new pptxgenjs();
|
||||
prs.layout = "LAYOUT_WIDE";
|
||||
prs.defineLayout({ name: "LAYOUT_WIDE", width: 13.33, height: 7.5 });
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Slide 1 — 封面
|
||||
// ════════════════════════════════════════════════════════
|
||||
{
|
||||
const sl = prs.addSlide();
|
||||
// 浅色背景
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: "100%",
|
||||
fill: { color: C.bg }, line: { color: C.bg, width: 0 },
|
||||
});
|
||||
|
||||
// 左侧品牌色竖栏 3.2"
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: 3.2, h: 7.5,
|
||||
fill: { type: "grad", stops: [{ position: 0, color: "B0005A" }, { position: 100, color: C.accent }] },
|
||||
line: { color: C.accent, width: 0 },
|
||||
});
|
||||
|
||||
// 左栏:英文副标题
|
||||
sl.addText("EMS & EHS Compliance\nIntelligence Hub", {
|
||||
x: 0.3, y: 1.8, w: 2.6, h: 0.7,
|
||||
fontSize: 11, italic: true, color: "FFFFFF",
|
||||
fontFace: "Calibri Light", align: "center",
|
||||
});
|
||||
|
||||
// 左栏:项目大标题
|
||||
sl.addText("AI + 合规\n智能中枢", {
|
||||
x: 0.2, y: 2.6, w: 2.8, h: 1.2,
|
||||
fontSize: 24, bold: true, color: "FFFFFF",
|
||||
fontFace: "Calibri", align: "center",
|
||||
});
|
||||
|
||||
// 左栏:分隔线
|
||||
sl.addShape("rect", {
|
||||
x: 0.6, y: 3.9, w: 2.0, h: 0.02,
|
||||
fill: { color: "FFFFFF", transparency: 60 },
|
||||
line: { color: "FFFFFF", width: 0 },
|
||||
});
|
||||
|
||||
// 左栏:团队 + 日期
|
||||
sl.addText("T-Systems · AI 合规项目组", {
|
||||
x: 0.2, y: 4.0, w: 2.8, h: 0.28,
|
||||
fontSize: 10, color: "FFFFFF", fontFace: "Calibri Light", align: "center",
|
||||
});
|
||||
sl.addText("2026 年 05 月", {
|
||||
x: 0.2, y: 4.3, w: 2.8, h: 0.28,
|
||||
fontSize: 10, color: "FFFFFF", fontFace: "Calibri Light", align: "center",
|
||||
});
|
||||
|
||||
// 右侧:标签
|
||||
sl.addText("TEAM PROGRESS REPORT", {
|
||||
x: 3.6, y: 1.2, w: 6, h: 0.25,
|
||||
fontSize: 9, bold: true, color: C.accent,
|
||||
charSpacing: 3, fontFace: "Calibri",
|
||||
});
|
||||
|
||||
// 右侧:汇报标题
|
||||
sl.addText("团队阶段性汇报", {
|
||||
x: 3.6, y: 1.5, w: 9, h: 0.65,
|
||||
fontSize: 34, bold: true, color: C.text, fontFace: "Calibri",
|
||||
});
|
||||
|
||||
// 右侧:说明文字
|
||||
sl.addText("基于 Agent 协同的多模块法规合规智能平台\n多团队协作 · 阶段成果汇报 · 分工与规划", {
|
||||
x: 3.6, y: 2.25, w: 9, h: 0.6,
|
||||
fontSize: 12, color: C.text2, fontFace: "Calibri Light",
|
||||
});
|
||||
|
||||
// 右侧:3个数字指标卡
|
||||
const stats = [
|
||||
{ num: "5", label: "功能模块\n已完成/开发中", color: C.accent },
|
||||
{ num: "17+", label: "REST API 接口\n已设计并文档化", color: C.green },
|
||||
{ num: "6+", label: "法规来源\n接入覆盖", color: C.orange },
|
||||
];
|
||||
stats.forEach((s, i) => {
|
||||
const sx = 3.6 + i * 2.8;
|
||||
sl.addShape("rect", {
|
||||
x: sx, y: 3.1, w: 2.5, h: 1.4,
|
||||
fill: { color: C.bgCard }, line: { color: C.border, width: 0.5 },
|
||||
shadow: makeShadow(),
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: sx, y: 3.1, w: 2.5, h: 0.05,
|
||||
fill: { color: s.color }, line: { color: s.color, width: 0 },
|
||||
});
|
||||
sl.addText(s.num, {
|
||||
x: sx + 0.15, y: 3.2, w: 2.2, h: 0.65,
|
||||
fontSize: 40, bold: true, color: s.color, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText(s.label, {
|
||||
x: sx + 0.15, y: 3.85, w: 2.2, h: 0.55,
|
||||
fontSize: 10, color: C.text3, fontFace: "Calibri Light",
|
||||
});
|
||||
});
|
||||
|
||||
// 底部机密标注
|
||||
sl.addText("INTERNAL · CONFIDENTIAL", {
|
||||
x: 3.6, y: 7.1, w: 6, h: 0.25,
|
||||
fontSize: 9, color: C.text3, fontFace: "Calibri",
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Slide 2 — 项目背景
|
||||
// ════════════════════════════════════════════════════════
|
||||
{
|
||||
const sl = prs.addSlide();
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: "100%",
|
||||
fill: { color: C.bg }, line: { color: C.bg, width: 0 },
|
||||
});
|
||||
topBar(sl);
|
||||
sectionLabel(sl, "项目背景", 0.5, 0.55);
|
||||
sectionTitle(sl, "为什么要做这个系统?", 0.5, 0.85);
|
||||
sectionSub(sl, "汽车行业合规管理三大核心痛点,传统人工方式已无法满足需求", 0.5, 1.4);
|
||||
slideNum(sl, 2, 10);
|
||||
|
||||
const painPoints = [
|
||||
{
|
||||
topColor: ["B0005A", "E20074"],
|
||||
icon: "📋",
|
||||
title: "法规碎片化",
|
||||
body: "GB、MIIT、UN-ECE、IATF 16949、ISO 45001、EUR-Lex 等多源法规并存,更新频繁,人工跟踪极易遗漏,合规窗口期短。",
|
||||
},
|
||||
{
|
||||
topColor: ["C05000", "FF8800"],
|
||||
icon: "⏱",
|
||||
title: "响应周期长",
|
||||
body: "从法规发布到内部解读、影响评估、整改计划,人工流程往往需要数周,无法满足快速迭代的合规要求。",
|
||||
},
|
||||
{
|
||||
topColor: ["00A080", "00D4AA"],
|
||||
icon: "💼",
|
||||
title: "人工成本高",
|
||||
body: "合规专家大量时间消耗在文档检索、条款比对、报告撰写等重复性工作上,高价值判断时间被严重压缩。",
|
||||
},
|
||||
];
|
||||
|
||||
painPoints.forEach((p, i) => {
|
||||
const cx = 0.5 + i * 4.22;
|
||||
sl.addShape("rect", {
|
||||
x: cx, y: 1.9, w: 4.0, h: 4.8,
|
||||
fill: { color: C.bgCard }, line: { color: C.border, width: 0.5 },
|
||||
shadow: makeShadow(),
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: cx, y: 1.9, w: 4.0, h: 0.07,
|
||||
fill: { type: "grad", stops: [{ position: 0, color: p.topColor[0] }, { position: 100, color: p.topColor[1] }] },
|
||||
line: { color: p.topColor[0], width: 0 },
|
||||
});
|
||||
sl.addText(p.icon, {
|
||||
x: cx + 0.25, y: 2.1, w: 0.7, h: 0.6,
|
||||
fontSize: 28, fontFace: "Segoe UI Emoji",
|
||||
});
|
||||
sl.addText(p.title, {
|
||||
x: cx + 0.25, y: 2.75, w: 3.5, h: 0.4,
|
||||
fontSize: 16, bold: true, color: C.text, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText(p.body, {
|
||||
x: cx + 0.25, y: 3.25, w: 3.5, h: 2.8,
|
||||
fontSize: 12, color: C.text2, fontFace: "Calibri Light",
|
||||
paraSpaceAfter: 4, lineSpacingMultiple: 1.4,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Slide 3 — 本阶段工作总览
|
||||
// ════════════════════════════════════════════════════════
|
||||
{
|
||||
const sl = prs.addSlide();
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: "100%",
|
||||
fill: { color: C.bg }, line: { color: C.bg, width: 0 },
|
||||
});
|
||||
topBar(sl);
|
||||
sectionLabel(sl, "阶段成果", 0.5, 0.55);
|
||||
sectionTitle(sl, "本阶段工作总览", 0.5, 0.85);
|
||||
sectionSub(sl, "5 个功能模块 · 核心 Agent 问答链路全通 · 角色分工一览", 0.5, 1.4);
|
||||
slideNum(sl, 3, 10);
|
||||
|
||||
const modules = [
|
||||
{
|
||||
icon: "📡", num: "01", name: "法规智能感知",
|
||||
desc: "六大法规源事件流,影响分级过滤,SSE 流式 AI 影响解读",
|
||||
status: "⟳ 进行中", statusColor: C.orange,
|
||||
role: "前端工程师 / 后端工程师",
|
||||
},
|
||||
{
|
||||
icon: "📚", num: "02", name: "文档知识库管理",
|
||||
desc: "PDF/Word 入库,5 步 Pipeline 可视化,双引擎 OCR 解析",
|
||||
status: "⟳ 进行中", statusColor: C.orange,
|
||||
role: "后端工程师 / AI 工程师",
|
||||
},
|
||||
{
|
||||
icon: "🔍", num: "03", name: "合规分析审查",
|
||||
desc: "逐段风险评分,内嵌 Agent 对话,风险聚合仪表盘",
|
||||
status: "⟳ 进行中", statusColor: C.orange,
|
||||
role: "全栈工程师",
|
||||
},
|
||||
{
|
||||
icon: "💬", num: "04", name: "法规 Agent 对话",
|
||||
desc: "Milvus 向量检索 + LLM 全链路贯通,多轮追问,来源归因",
|
||||
status: "✓ 核心链路已通", statusColor: C.green,
|
||||
role: "AI 工程师 / 后端工程师",
|
||||
},
|
||||
{
|
||||
icon: "🖥️", num: "05", name: "系统状态监控",
|
||||
desc: "五项基础设施健康检查,17+ API,系统配置总览",
|
||||
status: "✓ 已完成", statusColor: C.green,
|
||||
role: "后端工程师 / 运维",
|
||||
},
|
||||
];
|
||||
|
||||
modules.forEach((m, i) => {
|
||||
const ry = 1.85 + i * 1.02;
|
||||
sl.addShape("rect", {
|
||||
x: 0.5, y: ry, w: 12.3, h: 0.9,
|
||||
fill: { color: C.bgCard }, line: { color: C.border, width: 0.5 },
|
||||
shadow: makeShadow(),
|
||||
});
|
||||
sl.addText(m.icon, {
|
||||
x: 0.7, y: ry + 0.18, w: 0.55, h: 0.55,
|
||||
fontSize: 20, fontFace: "Segoe UI Emoji",
|
||||
});
|
||||
sl.addText(m.num, {
|
||||
x: 1.3, y: ry + 0.28, w: 0.4, h: 0.28,
|
||||
fontSize: 8, color: C.text3, fontFace: "Calibri", bold: true,
|
||||
});
|
||||
sl.addText(m.name, {
|
||||
x: 1.7, y: ry + 0.12, w: 2.8, h: 0.3,
|
||||
fontSize: 13, bold: true, color: C.text, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText(m.desc, {
|
||||
x: 1.7, y: ry + 0.46, w: 5.8, h: 0.28,
|
||||
fontSize: 10, color: C.text2, fontFace: "Calibri Light",
|
||||
});
|
||||
sl.addText(m.status, {
|
||||
x: 7.7, y: ry + 0.26, w: 2.2, h: 0.28,
|
||||
fontSize: 9, bold: true, color: m.statusColor,
|
||||
fill: { color: m.statusColor, transparency: 88 },
|
||||
line: { color: m.statusColor, width: 0.5, transparency: 70 },
|
||||
align: "center", margin: [2, 6, 2, 6], fontFace: "Calibri",
|
||||
});
|
||||
sl.addText(m.role, {
|
||||
x: 10.1, y: ry + 0.26, w: 2.5, h: 0.28,
|
||||
fontSize: 9, color: C.text3, fontFace: "Calibri Light", align: "right",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Slide 4 — 核心功能演示 1/2
|
||||
// ════════════════════════════════════════════════════════
|
||||
{
|
||||
const sl = prs.addSlide();
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: "100%",
|
||||
fill: { color: C.bg }, line: { color: C.bg, width: 0 },
|
||||
});
|
||||
topBar(sl);
|
||||
sectionLabel(sl, "产品演示", 0.5, 0.55);
|
||||
sectionTitle(sl, "核心功能一览(1/2)", 0.5, 0.85);
|
||||
sectionSub(sl, "前 3 个模块已上线,功能可演示", 0.5, 1.4);
|
||||
slideNum(sl, 4, 10);
|
||||
|
||||
const features = [
|
||||
{
|
||||
bg: ["FCE8F3", "FCD5E8"], icon: "📡", name: "法规智能感知",
|
||||
tags: [{ t: "高影响", c: C.accent }, { t: "中影响", c: C.orange }, { t: "低影响", c: C.green }],
|
||||
liveBadge: true,
|
||||
desc: "六大法规源实时事件流,影响等级分级过滤,选中事件触发 SSE 流式 AI 影响分析",
|
||||
},
|
||||
{
|
||||
bg: ["EEF2FD", "DCE8FB"], icon: "📚", name: "文档知识库管理",
|
||||
tags: [{ t: "LOAD", c: C.blue }, { t: "PARSE", c: C.blue }, { t: "EMBED", c: C.blue }, { t: "STORE", c: C.green }],
|
||||
liveBadge: false,
|
||||
desc: "PDF/Word 上传入库,5 步 Pipeline 可视化进度,双引擎解析,文档列表管理",
|
||||
},
|
||||
{
|
||||
bg: ["FFF5E8", "FDE8CC"], icon: "🔍", name: "合规分析审查",
|
||||
tags: [{ t: "⚠ 高风险", c: C.red }, { t: "✓ 合规", c: C.green }],
|
||||
liveBadge: false,
|
||||
desc: "上传文件逐段风险评分,内嵌 Agent 对话针对具体条款实时问答,风险聚合仪表盘",
|
||||
},
|
||||
];
|
||||
|
||||
features.forEach((f, i) => {
|
||||
const cx = 0.5 + i * 4.22;
|
||||
sl.addShape("rect", {
|
||||
x: cx, y: 1.85, w: 4.0, h: 5.0,
|
||||
fill: { color: C.bgCard }, line: { color: C.border, width: 0.5 },
|
||||
shadow: makeShadow(),
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: cx, y: 1.85, w: 4.0, h: 2.0,
|
||||
fill: { type: "grad", stops: [{ position: 0, color: f.bg[0] }, { position: 100, color: f.bg[1] }] },
|
||||
line: { color: C.border, width: 0 },
|
||||
});
|
||||
sl.addText(f.icon, {
|
||||
x: cx + 1.4, y: 2.0, w: 1.2, h: 0.9,
|
||||
fontSize: 32, align: "center", fontFace: "Segoe UI Emoji",
|
||||
});
|
||||
let tx = cx + 0.15;
|
||||
f.tags.forEach(tag => {
|
||||
const tw = tag.t.length > 4 ? 0.85 : 0.7;
|
||||
sl.addText(tag.t, {
|
||||
x: tx, y: 3.05, w: tw, h: 0.22,
|
||||
fontSize: 8, bold: true, color: tag.c,
|
||||
fill: { color: tag.c, transparency: 88 },
|
||||
line: { color: tag.c, width: 0.4, transparency: 70 },
|
||||
align: "center", margin: [2, 4, 2, 4], fontFace: "Calibri",
|
||||
});
|
||||
tx += tw + 0.05;
|
||||
});
|
||||
if (f.liveBadge) {
|
||||
sl.addText("LIVE", {
|
||||
x: cx + 3.15, y: 1.95, w: 0.65, h: 0.22,
|
||||
fontSize: 8, bold: true, color: C.green,
|
||||
fill: { color: C.green, transparency: 88 },
|
||||
line: { color: C.green, width: 0.4, transparency: 70 },
|
||||
align: "center", margin: [2, 4, 2, 4], fontFace: "Calibri",
|
||||
});
|
||||
}
|
||||
sl.addText(f.name, {
|
||||
x: cx + 0.15, y: 3.45, w: 3.7, h: 0.35,
|
||||
fontSize: 13, bold: true, color: C.text, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText(f.desc, {
|
||||
x: cx + 0.15, y: 3.85, w: 3.7, h: 2.7,
|
||||
fontSize: 11, color: C.text2, fontFace: "Calibri Light",
|
||||
lineSpacingMultiple: 1.4,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Slide 5 — 核心功能演示 2/2
|
||||
// ════════════════════════════════════════════════════════
|
||||
{
|
||||
const sl = prs.addSlide();
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: "100%",
|
||||
fill: { color: C.bg }, line: { color: C.bg, width: 0 },
|
||||
});
|
||||
topBar(sl);
|
||||
sectionLabel(sl, "产品演示", 0.5, 0.55);
|
||||
sectionTitle(sl, "核心功能一览(2/2)", 0.5, 0.85);
|
||||
sectionSub(sl, "Agent 对话核心链路真实运行(Milvus + LLM)· 监控模块已完成", 0.5, 1.4);
|
||||
slideNum(sl, 5, 10);
|
||||
|
||||
// 左宽卡 — Agent 对话 (w=8.3)
|
||||
sl.addShape("rect", {
|
||||
x: 0.5, y: 1.85, w: 8.3, h: 5.0,
|
||||
fill: { color: C.bgCard }, line: { color: C.green, width: 1 },
|
||||
shadow: makeShadow(),
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: 0.5, y: 1.85, w: 8.3, h: 1.8,
|
||||
fill: { type: "grad", stops: [{ position: 0, color: "E8F5EF" }, { position: 100, color: "D0EDE2" }] },
|
||||
line: { color: C.border, width: 0 },
|
||||
});
|
||||
sl.addText("💬", {
|
||||
x: 0.8, y: 1.95, w: 0.8, h: 0.7,
|
||||
fontSize: 26, fontFace: "Segoe UI Emoji",
|
||||
});
|
||||
sl.addText('"GB 18384 对我们的 BMS 设计有什么影响?" → Agent 检索 Milvus → 流式回答 + 来源引用', {
|
||||
x: 0.8, y: 2.78, w: 5.7, h: 0.5,
|
||||
fontSize: 10, color: C.green,
|
||||
fill: { color: C.green, transparency: 90 },
|
||||
line: { color: C.green, width: 0.5, transparency: 70 },
|
||||
margin: [4, 8, 4, 8], fontFace: "Calibri Light",
|
||||
});
|
||||
// 右侧组件 badges
|
||||
const agentBadges = ["Milvus 向量库", "MinIO 对象库", "Embedding 引擎", "Reranker", "Sessions 会话"];
|
||||
agentBadges.forEach((b, i) => {
|
||||
sl.addText(b, {
|
||||
x: 6.55, y: 1.93 + i * 0.3, w: 2.05, h: 0.24,
|
||||
fontSize: 8, color: C.green,
|
||||
fill: { color: C.green, transparency: 90 },
|
||||
line: { color: C.green, width: 0.4, transparency: 60 },
|
||||
align: "center", margin: [2, 4, 2, 4], fontFace: "Calibri",
|
||||
});
|
||||
});
|
||||
sl.addText("法规 Agent 对话", {
|
||||
x: 0.7, y: 3.82, w: 4.5, h: 0.35,
|
||||
fontSize: 14, bold: true, color: C.text, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText("● 核心链路已通", {
|
||||
x: 5.0, y: 3.86, w: 2.2, h: 0.28,
|
||||
fontSize: 10, color: C.green, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText("真实 Milvus 向量检索 + LLM 流式输出全链路贯通\n会话管理、来源归因卡片、多轮追问、法规类型筛选", {
|
||||
x: 0.7, y: 4.25, w: 7.8, h: 0.9,
|
||||
fontSize: 11, color: C.text2, fontFace: "Calibri Light", lineSpacingMultiple: 1.5,
|
||||
});
|
||||
|
||||
// 右窄卡 — 系统监控 (w=3.9)
|
||||
sl.addShape("rect", {
|
||||
x: 9.0, y: 1.85, w: 3.9, h: 5.0,
|
||||
fill: { color: C.bgCard }, line: { color: C.border, width: 0.5 },
|
||||
shadow: makeShadow(),
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: 9.0, y: 1.85, w: 3.9, h: 1.8,
|
||||
fill: { type: "grad", stops: [{ position: 0, color: "F0ECFF" }, { position: 100, color: "E2D8FF" }] },
|
||||
line: { color: C.border, width: 0 },
|
||||
});
|
||||
sl.addText("🖥️", {
|
||||
x: 9.4, y: 2.0, w: 0.7, h: 0.65,
|
||||
fontSize: 24, fontFace: "Segoe UI Emoji",
|
||||
});
|
||||
const healthBadges = [
|
||||
{ t: "Milvus ✓", c: C.green }, { t: "MinIO ✓", c: C.green }, { t: "LLM ✓", c: C.green },
|
||||
];
|
||||
healthBadges.forEach((b, i) => {
|
||||
sl.addText(b.t, {
|
||||
x: 9.05 + i * 1.25, y: 3.0, w: 1.2, h: 0.22,
|
||||
fontSize: 8, bold: true, color: b.c,
|
||||
fill: { color: b.c, transparency: 88 },
|
||||
line: { color: b.c, width: 0.4, transparency: 70 },
|
||||
align: "center", margin: [2, 4, 2, 4], fontFace: "Calibri",
|
||||
});
|
||||
});
|
||||
sl.addText("系统状态监控", {
|
||||
x: 9.15, y: 3.82, w: 3.5, h: 0.35,
|
||||
fontSize: 14, bold: true, color: C.text, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText("✓ 已完成", {
|
||||
x: 9.15, y: 4.25, w: 2, h: 0.25,
|
||||
fontSize: 10, bold: true, color: C.green, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText("五项基础设施实时健康检查,文档统计,系统配置总览(LLM / Embedding / 解析引擎)", {
|
||||
x: 9.15, y: 4.6, w: 3.6, h: 2.0,
|
||||
fontSize: 11, color: C.text2, fontFace: "Calibri Light", lineSpacingMultiple: 1.4,
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Slide 6 — 系统分层架构
|
||||
// ════════════════════════════════════════════════════════
|
||||
{
|
||||
const sl = prs.addSlide();
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: "100%",
|
||||
fill: { color: C.bg }, line: { color: C.bg, width: 0 },
|
||||
});
|
||||
topBar(sl);
|
||||
sectionLabel(sl, "技术架构", 0.5, 0.55);
|
||||
sectionTitle(sl, "系统分层架构", 0.5, 0.85);
|
||||
sectionSub(sl, "清洁架构 + Ports & Adapters,5 层分离,高内聚低耦合,可独立替换各层实现", 0.5, 1.4);
|
||||
slideNum(sl, 6, 10);
|
||||
|
||||
const layers = [
|
||||
{
|
||||
grad: ["B0005A", "E20074"], icon: "👥", name: "用户层",
|
||||
sub: "合规专员 / EHS 专员 / 审核员 / 管理员 / 查看员",
|
||||
tags: ["法规感知", "文档分析", "EHS 合规", "API 集成", "报告生成"],
|
||||
tagC: "E20074",
|
||||
},
|
||||
{
|
||||
grad: ["00A080", "00D4AA"], icon: "🖥️", name: "前端层",
|
||||
sub: "React 19 · TypeScript · Vite · React Router v7",
|
||||
tags: ["KeepAlive 路由", "三主题系统 (Dark/Dim/Light)", "shadcn/ui 组件库", "Tailwind v4"],
|
||||
tagC: "00896A",
|
||||
},
|
||||
{
|
||||
grad: ["2060A0", "4A90D9"], icon: "⚡", name: "API 层",
|
||||
sub: "FastAPI · kbmp-service · mcp-server · Worker 异步",
|
||||
tags: ["知识库管理 API", "合规检查 API", "文档解析 API", "检索问答 API", "订阅推送 API"],
|
||||
tagC: "2A68C8",
|
||||
},
|
||||
{
|
||||
grad: ["C05000", "FF8800"], icon: "🧠", name: "AI 引擎层",
|
||||
sub: "Agent Orchestration · LLM · Embedding · Tool Use",
|
||||
tags: ["Qwen / DeepSeek LLM", "Agent 协同编排", "向量知识检索", "工具调用 (Tool Use)", "阿里云 DocMind OCR"],
|
||||
tagC: "CC6200",
|
||||
},
|
||||
{
|
||||
grad: ["5040B0", "7B68EE"], icon: "🗄️", name: "基础设施层",
|
||||
sub: "Milvus · PostgreSQL · MinIO · Prometheus · Grafana",
|
||||
tags: ["Milvus 向量数据库", "PostgreSQL 结构化存储", "对象存储 (文档/Markdown)", "Prometheus + Grafana"],
|
||||
tagC: "5A46B4",
|
||||
},
|
||||
];
|
||||
|
||||
const layerH = 0.92;
|
||||
const gap = 0.08;
|
||||
layers.forEach((lyr, i) => {
|
||||
const ly = 1.75 + i * (layerH + gap);
|
||||
sl.addShape("rect", {
|
||||
x: 0.5, y: ly, w: 12.3, h: layerH,
|
||||
fill: { color: "FFFFFF" }, line: { color: lyr.tagC, width: 0.5, transparency: 60 },
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: 0.5, y: ly, w: 3.2, h: layerH,
|
||||
fill: { type: "grad", stops: [{ position: 0, color: lyr.grad[0] }, { position: 100, color: lyr.grad[1] }] },
|
||||
line: { color: lyr.grad[0], width: 0 },
|
||||
});
|
||||
sl.addText(lyr.icon, {
|
||||
x: 0.6, y: ly + 0.2, w: 0.55, h: 0.55,
|
||||
fontSize: 18, fontFace: "Segoe UI Emoji",
|
||||
});
|
||||
sl.addText(lyr.name, {
|
||||
x: 1.18, y: ly + 0.12, w: 1.7, h: 0.35,
|
||||
fontSize: 13, bold: true, color: "FFFFFF", fontFace: "Calibri",
|
||||
});
|
||||
sl.addText(lyr.sub, {
|
||||
x: 1.18, y: ly + 0.52, w: 2.3, h: 0.28,
|
||||
fontSize: 8, color: "FFFFFF", fontFace: "Calibri Light",
|
||||
});
|
||||
let tx = 3.85;
|
||||
lyr.tags.forEach(tag => {
|
||||
const tw = Math.max(tag.length * 0.12 + 0.2, 1.1);
|
||||
sl.addText(tag, {
|
||||
x: tx, y: ly + 0.32, w: tw, h: 0.24,
|
||||
fontSize: 9, bold: true, color: lyr.tagC,
|
||||
fill: { color: lyr.tagC, transparency: 88 },
|
||||
line: { color: lyr.tagC, width: 0.4, transparency: 70 },
|
||||
align: "center", margin: [2, 6, 2, 6], fontFace: "Calibri",
|
||||
});
|
||||
tx += tw + 0.12;
|
||||
});
|
||||
if (i < layers.length - 1) {
|
||||
sl.addText("↕", {
|
||||
x: 6.3, y: ly + layerH, w: 0.7, h: gap + 0.04,
|
||||
fontSize: 10, color: "E20074", align: "center", fontFace: "Calibri",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Slide 7 — 业务价值(含布局修正,不超出 7.5" 高度)
|
||||
// ════════════════════════════════════════════════════════
|
||||
{
|
||||
const sl = prs.addSlide();
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: "100%",
|
||||
fill: { color: C.bg }, line: { color: C.bg, width: 0 },
|
||||
});
|
||||
topBar(sl);
|
||||
sectionLabel(sl, "业务价值", 0.5, 0.55);
|
||||
sectionTitle(sl, "有与没有的差距", 0.5, 0.85);
|
||||
sectionSub(sl, "量化对比:AI 智能中枢 vs 传统人工合规方式", 0.5, 1.4);
|
||||
slideNum(sl, 7, 10);
|
||||
|
||||
// 左侧框(传统方式)
|
||||
sl.addShape("rect", {
|
||||
x: 0.5, y: 1.85, w: 5.6, h: 3.5,
|
||||
fill: { color: "FDF0F0" }, line: { color: "E8C0C0", width: 0.5 }, shadow: makeShadow(),
|
||||
});
|
||||
sl.addText("❌ 传统人工方式", {
|
||||
x: 0.75, y: 2.02, w: 5.1, h: 0.32,
|
||||
fontSize: 12, bold: true, color: C.red, fontFace: "Calibri",
|
||||
});
|
||||
[
|
||||
["数周", "法规解读 + 影响评估周期"],
|
||||
["人工检索", "依赖专家经验,易遗漏"],
|
||||
["碎片化", "各系统数据孤岛,无法联动"],
|
||||
["高成本", "合规专家时间大量消耗"],
|
||||
["被动响应", "法规变化后才开始评估"],
|
||||
].forEach(([kw, txt], i) => {
|
||||
sl.addText(kw, {
|
||||
x: 0.75, y: 2.42 + i * 0.52, w: 1.4, h: 0.28,
|
||||
fontSize: 10, bold: true, color: C.red, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText("— " + txt, {
|
||||
x: 2.2, y: 2.42 + i * 0.52, w: 3.7, h: 0.28,
|
||||
fontSize: 10, color: C.text2, fontFace: "Calibri Light",
|
||||
});
|
||||
});
|
||||
|
||||
// 中间箭头
|
||||
sl.addShape("rect", {
|
||||
x: 6.15, y: 1.85, w: 0.9, h: 3.5,
|
||||
fill: { color: C.bgHover }, line: { color: C.border, width: 0.5 },
|
||||
});
|
||||
sl.addText("→", {
|
||||
x: 6.15, y: 3.35, w: 0.9, h: 0.5,
|
||||
fontSize: 22, color: C.accent, align: "center", fontFace: "Calibri",
|
||||
});
|
||||
|
||||
// 右侧框(AI 平台)
|
||||
sl.addShape("rect", {
|
||||
x: 7.1, y: 1.85, w: 5.7, h: 3.5,
|
||||
fill: { color: "F0FAF6" }, line: { color: "A0D8C0", width: 0.5 }, shadow: makeShadow(),
|
||||
});
|
||||
sl.addText("✅ AI + 合规智能中枢", {
|
||||
x: 7.35, y: 2.02, w: 5.2, h: 0.32,
|
||||
fontSize: 12, bold: true, color: C.green, fontFace: "Calibri",
|
||||
});
|
||||
[
|
||||
["分钟级", "法规发布即自动解读,影响秒级评估"],
|
||||
["Agent 智能协同", "多步推理,精准定位条款"],
|
||||
["统一平台", "多源法规、多系统数据融合"],
|
||||
["降低重复劳动 70%+", "专家聚焦高价值判断"],
|
||||
["主动预警", "订阅推送,法规变化即刻触达"],
|
||||
].forEach(([kw, txt], i) => {
|
||||
sl.addText(kw, {
|
||||
x: 7.35, y: 2.42 + i * 0.52, w: 2.3, h: 0.28,
|
||||
fontSize: 10, bold: true, color: C.green, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText("— " + txt, {
|
||||
x: 9.7, y: 2.42 + i * 0.52, w: 2.8, h: 0.28,
|
||||
fontSize: 10, color: C.text2, fontFace: "Calibri Light",
|
||||
});
|
||||
});
|
||||
|
||||
// 底部 4 KPI 卡(y=5.6,不超出 7.5" 边界)
|
||||
[
|
||||
{ num: "70%+", label: "重复性工作减少", color: C.accent },
|
||||
{ num: "分钟级", label: "法规响应时间", color: C.green },
|
||||
{ num: "6+", label: "法规源统一管理", color: C.orange },
|
||||
{ num: "5", label: "业务场景覆盖", color: C.blue },
|
||||
].forEach((k, i) => {
|
||||
const kx = 0.5 + i * 3.2;
|
||||
sl.addShape("rect", {
|
||||
x: kx, y: 5.6, w: 3.0, h: 1.55,
|
||||
fill: { color: C.bgCard }, line: { color: C.border, width: 0.5 }, shadow: makeShadow(),
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: kx, y: 5.6, w: 3.0, h: 0.05,
|
||||
fill: { color: k.color }, line: { color: k.color, width: 0 },
|
||||
});
|
||||
sl.addText(k.num, {
|
||||
x: kx + 0.15, y: 5.72, w: 2.7, h: 0.65,
|
||||
fontSize: 28, bold: true, color: k.color, fontFace: "Calibri", align: "center",
|
||||
});
|
||||
sl.addText(k.label, {
|
||||
x: kx + 0.15, y: 6.4, w: 2.7, h: 0.3,
|
||||
fontSize: 10, color: C.text3, fontFace: "Calibri Light", align: "center",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Slide 8 — 四阶段路线图
|
||||
// ════════════════════════════════════════════════════════
|
||||
{
|
||||
const sl = prs.addSlide();
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: "100%",
|
||||
fill: { color: C.bg }, line: { color: C.bg, width: 0 },
|
||||
});
|
||||
topBar(sl);
|
||||
sectionLabel(sl, "发展路线", 0.5, 0.55);
|
||||
sectionTitle(sl, "四阶段推进计划", 0.5, 0.85);
|
||||
sectionSub(sl, "当前处于第二阶段“Demo 打磨”,核心 Agent 链路已通,正在补全剩余模块", 0.5, 1.4);
|
||||
slideNum(sl, 8, 10);
|
||||
|
||||
// 时间线底线
|
||||
sl.addShape("rect", { x: 1.5, y: 2.35, w: 2.5, h: 0.05, fill: { color: C.green }, line: { color: C.green, width: 0 } });
|
||||
sl.addShape("rect", { x: 4.0, y: 2.35, w: 2.5, h: 0.05, fill: { color: C.accent }, line: { color: C.accent, width: 0 } });
|
||||
sl.addShape("rect", { x: 6.5, y: 2.35, w: 5.3, h: 0.05, fill: { color: C.border }, line: { color: C.border, width: 0 } });
|
||||
|
||||
const phases = [
|
||||
{
|
||||
icon: "✓", iconBg: C.green, borderC: C.green, borderW: 1,
|
||||
label: "阶段 01 · 已完成", title: "技术验证 POC",
|
||||
items: ["清洁架构 + Ports & Adapters", "Agent 协同框架搭建", "Milvus 向量检索链路打通", "前后端项目结构建立"],
|
||||
cardBg: "F0FAF6", cardBorder: "A0D8C0",
|
||||
},
|
||||
{
|
||||
icon: "⟳", iconBg: C.accent, borderC: C.accent, borderW: 2,
|
||||
label: "阶段 02 · 进行中 ◀ 当前", title: "Demo 打磨",
|
||||
items: ["5 页面前端已上线", "法规 Agent 对话全通", "Pipeline 已通,OCR 联调中", "EHS 合规 & 监控开发中"],
|
||||
cardBg: "FEF0F6", cardBorder: "E20074",
|
||||
},
|
||||
{
|
||||
icon: "3", iconBg: C.text3, borderC: C.border, borderW: 1,
|
||||
label: "阶段 03 · 待启动", title: "生产部署",
|
||||
items: ["PLM/ERP/MES 集成", "Agent 能力扩展", "Milvus 集群化部署", "合规 & 监控完善"],
|
||||
cardBg: C.bgCard, cardBorder: C.border,
|
||||
},
|
||||
{
|
||||
icon: "4", iconBg: C.text3, borderC: C.border, borderW: 1,
|
||||
label: "阶段 04 · 待启动", title: "规模推广",
|
||||
items: ["多租户 & 权限体系", "主动预警 & 订阅", "1-2 行业试点推广", "数据治理 & 安全合规"],
|
||||
cardBg: C.bgCard, cardBorder: C.border,
|
||||
},
|
||||
];
|
||||
|
||||
phases.forEach((p, i) => {
|
||||
const cx = 0.6 + i * 3.1;
|
||||
sl.addShape("ellipse", {
|
||||
x: cx + 0.85, y: 2.1, w: 0.55, h: 0.55,
|
||||
fill: { color: p.iconBg }, line: { color: p.iconBg, width: 0 },
|
||||
});
|
||||
sl.addText(p.icon, {
|
||||
x: cx + 0.85, y: 2.12, w: 0.55, h: 0.5,
|
||||
fontSize: 11, bold: true, color: "FFFFFF", align: "center", fontFace: "Calibri",
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: cx, y: 2.75, w: 2.85, h: 4.35,
|
||||
fill: { color: p.cardBg }, line: { color: p.cardBorder, width: p.borderW },
|
||||
shadow: makeShadow(),
|
||||
});
|
||||
sl.addText(p.label, {
|
||||
x: cx + 0.12, y: 2.9, w: 2.6, h: 0.24,
|
||||
fontSize: 8, bold: true, color: i === 1 ? C.accent : C.text3, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText(p.title, {
|
||||
x: cx + 0.12, y: 3.18, w: 2.6, h: 0.38,
|
||||
fontSize: 14, bold: true, color: C.text, fontFace: "Calibri",
|
||||
});
|
||||
p.items.forEach((item, j) => {
|
||||
sl.addText("▸ " + item, {
|
||||
x: cx + 0.12, y: 3.65 + j * 0.6, w: 2.62, h: 0.38,
|
||||
fontSize: 10, color: C.text2, fontFace: "Calibri Light",
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Slide 9 — 下阶段重点 & 分工
|
||||
// ════════════════════════════════════════════════════════
|
||||
{
|
||||
const sl = prs.addSlide();
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: "100%",
|
||||
fill: { color: C.bg }, line: { color: C.bg, width: 0 },
|
||||
});
|
||||
topBar(sl);
|
||||
sectionLabel(sl, "下一步", 0.5, 0.55);
|
||||
sectionTitle(sl, "下阶段重点 & 角色分工", 0.5, 0.85);
|
||||
sectionSub(sl, "Demo 收尾为核心目标,同步推进业务对齐与技术准备", 0.5, 1.4);
|
||||
slideNum(sl, 9, 10);
|
||||
|
||||
const actions = [
|
||||
{ icon: "🎯", title: "Demo 收尾", items: ["补全 parse/embed Pipeline", "接入 1-2 个真实文档场景", "Mock 替换为真实 LLM 调用"] },
|
||||
{ icon: "🤝", title: "业务对齐", items: ["确认演示场景与业务方", "收集业务方反馈", "需求优先级排序"] },
|
||||
{ icon: "🔧", title: "技术债清理", items: ["RBAC 权限设计实现", "接入 DocMind 正式账号", "前端性能优化 & KeepAlive"] },
|
||||
{ icon: "📋", title: "资源 & 决策", items: ["GPU / 算力需求评估", "确认生产 LLM 方案", "确定生产环境部署规划"] },
|
||||
];
|
||||
|
||||
actions.forEach((a, i) => {
|
||||
const col = i % 2;
|
||||
const row = Math.floor(i / 2);
|
||||
const cx = 0.5 + col * 4.25;
|
||||
const cy = 1.85 + row * 2.65;
|
||||
sl.addShape("rect", {
|
||||
x: cx, y: cy, w: 4.0, h: 2.45,
|
||||
fill: { color: C.bgCard }, line: { color: C.border, width: 0.5 }, shadow: makeShadow(),
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: cx, y: cy, w: 4.0, h: 0.05,
|
||||
fill: { color: C.accent }, line: { color: C.accent, width: 0 },
|
||||
});
|
||||
sl.addText(a.icon, {
|
||||
x: cx + 0.18, y: cy + 0.15, w: 0.55, h: 0.5,
|
||||
fontSize: 18, fontFace: "Segoe UI Emoji",
|
||||
});
|
||||
sl.addText(a.title, {
|
||||
x: cx + 0.75, y: cy + 0.2, w: 3.1, h: 0.38,
|
||||
fontSize: 13, bold: true, color: C.text, fontFace: "Calibri",
|
||||
});
|
||||
a.items.forEach((item, j) => {
|
||||
sl.addText("▸ " + item, {
|
||||
x: cx + 0.18, y: cy + 0.7 + j * 0.52, w: 3.6, h: 0.35,
|
||||
fontSize: 10, color: C.text2, fontFace: "Calibri Light",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 右侧角色分工栏
|
||||
sl.addShape("rect", {
|
||||
x: 9.1, y: 1.85, w: 3.8, h: 5.25,
|
||||
fill: { color: C.bgCard }, line: { color: C.border, width: 0.5 }, shadow: makeShadow(),
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: 9.1, y: 1.85, w: 3.8, h: 0.05,
|
||||
fill: { color: C.blue }, line: { color: C.blue, width: 0 },
|
||||
});
|
||||
sl.addText("角色分工", {
|
||||
x: 9.3, y: 2.0, w: 3.4, h: 0.35,
|
||||
fontSize: 13, bold: true, color: C.text, fontFace: "Calibri",
|
||||
});
|
||||
[
|
||||
{ role: "前端工程师", tasks: "法规感知 UI / 文档管理 / 性能优化" },
|
||||
{ role: "后端工程师", tasks: "Pipeline 补全 / API 稳定性 / RBAC" },
|
||||
{ role: "AI 工程师", tasks: "Agent 调优 / LLM 接入 / Embedding" },
|
||||
{ role: "全栈 / 架构", tasks: "合规分析模块 / 系统集成 / 文档" },
|
||||
].forEach((r, i) => {
|
||||
const ry = 2.55 + i * 1.12;
|
||||
sl.addShape("rect", {
|
||||
x: 9.25, y: ry, w: 3.5, h: 0.9,
|
||||
fill: { color: C.bgHover }, line: { color: C.border, width: 0.4 },
|
||||
});
|
||||
sl.addText(r.role, {
|
||||
x: 9.4, y: ry + 0.1, w: 3.2, h: 0.28,
|
||||
fontSize: 10, bold: true, color: C.blue, fontFace: "Calibri",
|
||||
});
|
||||
sl.addText(r.tasks, {
|
||||
x: 9.4, y: ry + 0.42, w: 3.2, h: 0.36,
|
||||
fontSize: 9, color: C.text2, fontFace: "Calibri Light",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Slide 10 — 结语 & Q&A
|
||||
// ════════════════════════════════════════════════════════
|
||||
{
|
||||
const sl = prs.addSlide();
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: "100%",
|
||||
fill: { color: C.darkBg }, line: { color: C.darkBg, width: 0 },
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: 0, y: 0, w: "100%", h: 0.06,
|
||||
fill: { type: "grad", stops: [{ position: 0, color: C.accent }, { position: 100, color: C.accentDk }] },
|
||||
line: { color: C.accent, width: 0 },
|
||||
});
|
||||
sl.addText("感谢聆听", {
|
||||
x: 1.5, y: 1.8, w: 10.3, h: 1.0,
|
||||
fontSize: 48, bold: true, color: "FFFFFF", align: "center", fontFace: "Calibri",
|
||||
});
|
||||
sl.addText("AI + 合规智能中枢 · 团队阶段性汇报", {
|
||||
x: 1.5, y: 2.9, w: 10.3, h: 0.4,
|
||||
fontSize: 14, color: C.text3, align: "center", fontFace: "Calibri Light",
|
||||
});
|
||||
sl.addShape("rect", {
|
||||
x: 4.5, y: 3.5, w: 4.33, h: 0.02,
|
||||
fill: { color: C.accent, transparency: 40 }, line: { color: C.accent, width: 0 },
|
||||
});
|
||||
sl.addText("Q & A", {
|
||||
x: 1.5, y: 3.7, w: 10.3, h: 0.85,
|
||||
fontSize: 44, bold: true, color: C.accent, align: "center", fontFace: "Calibri",
|
||||
});
|
||||
sl.addText("欢迎提问与交流", {
|
||||
x: 1.5, y: 4.6, w: 10.3, h: 0.4,
|
||||
fontSize: 15, color: "FFFFFF", align: "center", fontFace: "Calibri Light",
|
||||
});
|
||||
sl.addText("T-Systems · AI 合规项目组 · 2026.05 · INTERNAL CONFIDENTIAL", {
|
||||
x: 1.0, y: 6.9, w: 11.33, h: 0.28,
|
||||
fontSize: 9, color: C.text3, align: "center", fontFace: "Calibri Light",
|
||||
});
|
||||
}
|
||||
|
||||
// ── 保存 ────────────────────────────────────────────────
|
||||
const OUT = "C:/Projects/AIProjects/AIRegulations/AIRegulation-DocAnalysis-Demo/team-report.pptx";
|
||||
prs.writeFile({ fileName: OUT })
|
||||
.then(() => console.log("✅ 生成完成:", OUT))
|
||||
.catch(e => { console.error("❌ 生成失败:", e); process.exit(1); });
|
||||
Reference in New Issue
Block a user