chore: docs 结构整改 + compose 双目录对齐(合并重建提交)

本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交
(5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。

一、docs 结构整改(整改 #14)
根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入,
随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录
树并存于 docs/,共 791 文件、双分类体系冲突。

修复动作:
- b2 同名异主题文件改名迁移保全 9 个
- C 类 39 个孤立文件按主题正确归类
- A/B1 类 222 个重复文件删除(新结构已有内容副本)
- 9 个旧独有空目录删除
- 270 处内部引用按 verified 映射改写
- 整改记录 #14 登记于 04-运维文档/部署运维

结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。
残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。

二、compose 双目录对齐(消除踩坑 A)
- docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist
  改为 src/frontend-*/dist(h5 / agent / admin / terminal)
- docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/
- 效果:本地 docker compose up 不再把根目录 stale dist 挂回,
  与线上一致,分叉隐患消除(已 docker compose config 校验通过)

防复发铁律:
- 重构须提交;仓库修复须 git stash -u 或先 commit
- 新结构须 git add 并提交,避免再次 untracked 复活
- H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
This commit is contained in:
Simon
2026-08-07 22:31:32 +08:00
parent 5a77a89ab1
commit facc04aa65
573 changed files with 129347 additions and 909 deletions
@@ -0,0 +1,231 @@
# PRD - 知识库闭环
> **REQ编号**: REQ-知识-001
> **版本**: v1.1
> **优先级**: P0
> **阶段**: 近期(1-2个月)
> **作者**: 宋献
> **日期**: 2026-07-19
> **状态**: 待开发
> **原型**: `原型-REQ-知识-001-知识库闭环-v1.0.html`
> **关联**: REQ-知识-002 已合并(训练师审批作为核心环节)
---
## 一、问题陈述
**用户问题**
- 知识库 P2-01 标注"已完成",但实际是桩实现(stub),API 未挂载
- 员工提问时 AI 无法引用真实知识库内容
- 知识建议→训练师审批→入库→AI引用的全流程未打通
**业务目标**
- 实现知识库全闭环:知识建议 → 审批 → 入库 → AI 引用
- 知识库回答准确率提升至 85% 以上
---
## 二、需求范围
### 2.1 核心功能
| 功能 | 描述 |
|------|------|
| 知识建议API挂载 | 将 P2-01 的知识建议接口从桩实现改为真实调用 |
| 知识入库 | 知识通过审批后写入 Neo4j 图数据库 |
| 知识引用 | AI 回答时从知识库检索相关内容并引用 |
| 审批流打通 | 训练师在坐席端可审批知识建议 |
### 2.2 非目标
- 不包含知识库的自动学习/自训练能力
- 不包含知识库的版本管理
- 不包含知识库的分类/标签管理(后续迭代)
---
## 三、用户故事
| 角色 | 用户故事 | 验收标准 |
|------|----------|---------|
| 员工 | 我提问后,AI 能引用知识库中的相关内容 | 知识库有答案的问题,AI 回答时显示引用来源 |
| 坐席 | 我可以提交知识建议供审批 | 提交后进入待审批列表 |
| 训练师 | 我可以审批知识建议 | 通过/拒绝后知识入库或打回 |
| 管理员 | 我可以查看知识库覆盖情况 | 管理后台能看到知识节点数量、更新日志 |
---
## 四、功能详情
### 4.1 知识建议 API 挂载
**现状**`knowledge_iteration_router` 被注释,未挂载真实 API
**改造**
1. 取消注释 `knowledge_iteration_router`
2. 挂载真实知识检索 API
3. 配置 Neo4j 图存储连接
### 4.2 知识入库流程
```
用户提问 → AI分析 → 知识建议 → 训练师审批 → 写入Neo4j → AI可引用
```
| 步骤 | 动作 | 责任人 |
|------|------|--------|
| 1 | 知识建议生成 | AI服务 |
| 2 | 入库申请提交 | 坐席/管理员 |
| 3 | 审批(通过/拒绝) | 训练师 |
| 4 | 写入Neo4j | 后端服务 |
| 5 | 索引更新 | 后端服务 |
### 4.3 审批界面
- 坐席端增加「知识审批」Tab
- 显示待审批列表:知识标题、摘要、申请人、提交时间
- 支持:通过、拒绝、修改后通过
---
## 五、指标设计
| 指标 | 目标 | 测量方式 |
|------|------|---------|
| 知识库引用率 | ≥ 60% | AI回答中引用知识库的比例 |
| 知识库准确率 | ≥ 85% | 知识库答案被标记为正确的比例 |
| 审批平均耗时 | ≤ 24h | 知识从提交到审批完成的平均时间 |
---
## 六、技术依赖
| 依赖项 | 说明 |
|--------|------|
| Neo4j 图数据库 | 知识存储 |
| Dify API | 知识检索 |
| RAGFlow | 知识向量化 |
---
## 七、API 接口设计
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/knowledge/suggest` | POST | 知识建议(AI分析对话后生成) |
| `/api/knowledge/submit` | POST | 提交知识入库申请 |
| `/api/knowledge/approve` | POST | 审批知识(通过/拒绝) |
| `/api/knowledge/approve/list` | GET | 待审批列表 |
| `/api/knowledge/search` | GET | 知识库检索 |
| `/api/knowledge/graph` | GET | 知识图谱可视化 |
### 7.1 POST /api/knowledge/submit
**请求体**
```json
{
"title": "VPN连接失败排查步骤",
"content": "1. 检查VPN客户端版本...\n2. 检查网络环境...",
"category": "网络问题",
"tags": ["VPN", "网络", "连接"],
"source_conversation_id": "conv_xxx"
}
```
**响应**
```json
{
"success": true,
"knowledge_id": "kn_001",
"status": "pending_approval"
}
```
### 7.2 POST /api/knowledge/approve
**请求体**
```json
{
"knowledge_id": "kn_001",
"action": "approve", // "approve" | "reject" | "modify_approve"
"comment": "内容准确,可以入库",
"modified_content": null // 当 action 为 modify_approve 时使用
}
```
---
## 八、数据模型
### 8.1 Knowledge 表
```sql
CREATE TABLE knowledge_base (
id SERIAL PRIMARY KEY,
knowledge_id VARCHAR(64) UNIQUE NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
category VARCHAR(50),
tags JSONB DEFAULT '[]',
status VARCHAR(20) DEFAULT 'draft', -- draft / pending / approved / rejected
source_conversation_id VARCHAR(64),
created_by VARCHAR(64),
created_at TIMESTAMP DEFAULT NOW(),
approved_by VARCHAR(64),
approved_at TIMESTAMP,
neo4j_node_id VARCHAR(64) -- 写入Neo4j后的节点ID
);
```
### 8.2 Knowledge Approval History 表
```sql
CREATE TABLE knowledge_approval_history (
id SERIAL PRIMARY KEY,
knowledge_id VARCHAR(64) NOT NULL,
action VARCHAR(20) NOT NULL, -- submit / approve / reject / modify
comment TEXT,
operator_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
```
---
## 九、技术架构
### 9.1 知识流动
```
用户提问 → Dify分析 → 知识建议 → 坐席提交 → 训练师审批 → Neo4j入库 → AI检索引用
↓ ↓ ↓ ↓ ↓
AI回复 建议内容 待审批 审批记录 图数据库
```
### 9.2 核心改造点
1. **取消注释** `knowledge_iteration_router`,挂载真实 API
2. **Neo4j 集成**:配置图数据库连接,实现知识写入和检索
3. **审批工作流**:坐席端增加知识审批 Tab,训练师操作
4. **知识检索**AI 回答时调用 RAGFlow/Dify 检索知识库
---
## 十、验收标准
| 场景 | 验收条件 |
|------|----------|
| 知识提交 | 坐席提交知识 → 进入待审批列表 → 训练师可见 |
| 知识审批-通过 | 训练师点击通过 → 状态变为已批准 → 写入Neo4j |
| 知识审批-拒绝 | 训练师点击拒绝 → 状态变为已拒绝 → 申请人收到通知 |
| AI引用 | 员工提问 → AI回答时显示「知识库引用」来源卡片 |
| 知识检索 | 管理后台可搜索知识库内容 |
| 审批历史 | 知识详情页显示完整审批操作历史 |
---
## 十一、关联文档
- 技术方案: `02-技术文档/技术架构/增量设计-知识库迭代与痛点缓解-20260711.md`
- 前置需求: REQ-AI-001 复杂场景与统一路由
- UI设计: `原型-REQ-知识-001-知识库闭环-v1.0.html`
@@ -0,0 +1,562 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IT智能服务台 · 知识库迭代 v0.7.2 原型</title>
<style>
:root {
--accent: #07C160;
--accent-light: #e8f5e9;
--danger: #fa5151;
--warning: #ffc300;
--bg: #f5f5f5;
--card: #fff;
--border: #e5e5e5;
--text: #333;
--dim: #999;
--radius: 8px;
}
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--text); }
h2 { font-size:1.1em; margin-bottom:12px; display:flex; align-items:center; gap:8px; }
h3 { font-size:0.95em; margin-bottom:8px; }
.section { max-width:1080px; margin:16px auto; background:var(--card); border-radius:var(--radius); padding:20px; box-shadow:0 1px 3px rgba(0,0,0,.06); }
.section-label { display:inline-block; font-size:0.72em; padding:2px 8px; border-radius:10px; background:var(--accent-light); color:var(--accent); margin-bottom:10px; font-weight:600; }
.grid { display:grid; gap:16px; }
.grid-2 { grid-template-columns:1fr 1fr; }
.grid-3 { grid-template-columns:repeat(3,1fr); }
.card { border:1px solid var(--border); border-radius:var(--radius); padding:14px; background:var(--card); }
.card-mock { min-height:300px; position:relative; overflow:hidden; border:1px solid var(--border); border-radius:var(--radius); }
/* Triage Card */
.triage-card { background:var(--card); border:1.5px solid var(--accent); border-radius:12px; padding:16px; position:relative; box-shadow:0 2px 12px rgba(7,193,96,.12); }
.triage-badge { position:absolute; top:-8px; left:16px; background:var(--accent); color:#fff; font-size:0.7em; padding:2px 10px; border-radius:10px; }
.triage-step { font-size:0.75em; color:var(--dim); margin-bottom:8px; }
.triage-title { font-weight:600; margin-bottom:12px; font-size:0.95em; }
.triage-option { display:flex; align-items:center; gap:10px; padding:10px 14px; border:1px solid var(--border); border-radius:8px; margin-bottom:8px; cursor:pointer; transition:all .15s; }
.triage-option:hover { border-color:var(--accent); background:var(--accent-light); }
.triage-option .prob { margin-left:auto; font-size:0.85em; color:var(--accent); font-weight:600; }
.triage-option .dot { width:18px; height:18px; border-radius:50%; border:2px solid var(--border); flex-shrink:0; }
.triage-option:hover .dot { border-color:var(--accent); background:var(--accent-light); }
.triage-footer { display:flex; justify-content:space-between; align-items:center; margin-top:10px; }
.triage-expert { font-size:0.75em; color:var(--dim); }
.triage-toggle { width:36px; height:20px; border-radius:10px; background:var(--border); position:relative; cursor:pointer; }
.triage-toggle.on { background:var(--accent); }
.triage-toggle::after { content:''; width:16px; height:16px; border-radius:50%; background:#fff; position:absolute; top:2px; left:2px; transition:.2s; }
.triage-toggle.on::after { left:18px; }
/* Confidence Banner */
.conf-banner { padding:12px 16px; border-radius:var(--radius); display:flex; align-items:center; gap:12px; margin-bottom:12px; }
.conf-banner.warn { background:#fff8e1; border:1px solid var(--warning); }
.conf-banner .conf-icon { font-size:1.3em; }
.conf-banner .conf-text { font-size:0.82em; flex:1; }
.conf-banner .conf-badge { background:var(--warning); color:#333; font-size:0.7em; padding:2px 8px; border-radius:8px; font-weight:600; }
.conf-banner .conf-transfer { color:var(--accent); font-size:0.78em; cursor:pointer; text-decoration:underline; white-space:nowrap; }
.conf-snapshot { background:#fafafa; padding:8px 12px; border-radius:6px; font-size:0.72em; color:var(--dim); margin-top:6px; }
/* Approval Card */
.approval-card { background:var(--card); border:1px solid var(--border); border-radius:var(--radius); padding:14px; }
.approval-card.pending { border-left:3px solid var(--warning); }
.approval-card.duplicate { border-left:3px solid var(--danger); }
.approval-card .approval-header { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
.approval-card .approval-type { font-size:0.68em; padding:1px 8px; border-radius:8px; background:var(--accent-light); color:var(--accent); }
.approval-card .approval-type.duplicate-tag { background:#fde8e8; color:var(--danger); }
.approval-card .approval-title { font-weight:600; font-size:0.9em; }
.approval-card .approval-body { font-size:0.78em; color:#666; line-height:1.5; }
.approval-card .approval-actions { display:flex; gap:8px; margin-top:10px; }
.approval-card .approval-actions button { font-size:0.75em; padding:5px 14px; border-radius:6px; border:1px solid var(--border); cursor:pointer; background:#fff; }
.approval-card .approval-actions .btn-accept { background:var(--accent); color:#fff; border-color:var(--accent); }
.approval-card .approval-actions .btn-reject { color:var(--danger); border-color:var(--danger); }
.approval-card .approval-actions .btn-rewrite { color:#666; }
/* Duplicate warning */
.dup-warning { background:#fde8e8; border:1px solid var(--danger); border-radius:var(--radius); padding:10px 14px; margin-top:10px; font-size:0.75em; }
.dup-warning .dup-title { font-weight:600; color:var(--danger); margin-bottom:4px; }
.dup-item { display:flex; align-items:center; justify-content:space-between; padding:6px 0; border-bottom:1px solid #f5c6c6; }
.dup-item:last-child { border-bottom:none; }
.dup-sim { color:var(--danger); font-weight:600; font-size:0.85em; }
/* SVG Mini Topology */
.mini-topo { width:100%; height:80px; margin:6px 0; background:#fafafa; border-radius:6px; display:flex; align-items:center; justify-content:center; }
.mini-topo svg { max-height:70px; }
/* ECharts area */
.echarts-area { width:100%; height:300px; border:1px solid var(--border); border-radius:var(--radius); background:#fafafa; }
.echarts-placeholder { display:flex; align-items:center; justify-content:center; height:100%; color:var(--dim); font-size:0.85em; }
/* Knowledge List */
.kb-list { }
.kb-item { display:flex; align-items:center; padding:10px 12px; border-bottom:1px solid var(--border); gap:12px; }
.kb-item:last-child { border-bottom:none; }
.kb-item .kb-status { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
.kb-item .kb-status.pending { background:var(--warning); }
.kb-item .kb-status.approved { background:var(--accent); }
.kb-item .kb-status.rejected { background:var(--danger); }
.kb-item .kb-info { flex:1; }
.kb-item .kb-name { font-weight:500; font-size:0.85em; }
.kb-item .kb-meta { font-size:0.7em; color:var(--dim); }
.kb-item .kb-actions { display:flex; gap:6px; }
.kb-item button { font-size:0.7em; padding:3px 10px; border-radius:4px; border:1px solid var(--border); cursor:pointer; background:#fff; }
.kb-item button:hover { border-color:var(--accent); }
/* View toggle */
.view-toggle { display:flex; gap:0; margin-bottom:12px; }
.view-toggle button { padding:6px 16px; border:1px solid var(--border); background:#fff; cursor:pointer; font-size:0.8em; }
.view-toggle button:first-child { border-radius:var(--radius) 0 0 var(--radius); }
.view-toggle button:last-child { border-radius:0 var(--radius) var(--radius) 0; }
.view-toggle button.active { background:var(--accent); color:#fff; border-color:var(--accent); }
/* Stats */
.stats { display:flex; gap:12px; margin-bottom:14px; }
.stat-item { flex:1; text-align:center; padding:10px; border-radius:var(--radius); background:var(--accent-light); }
.stat-item .stat-num { font-size:1.5em; font-weight:700; color:var(--accent); }
.stat-item .stat-label { font-size:0.7em; color:var(--dim); }
/* Merge Dialog */
.merge-dialog { background:var(--card); border:2px solid var(--warning); border-radius:12px; padding:18px; }
.merge-dialog .merge-title { font-weight:600; font-size:0.9em; margin-bottom:8px; display:flex; align-items:center; gap:6px; }
/* Chat mock */
.chat-area { background:#f0f0f0; border-radius:var(--radius); padding:20px; min-height:240px; }
.chat-msg { max-width:75%; margin-bottom:10px; }
.chat-msg.user { margin-left:auto; }
.chat-bubble { padding:8px 12px; border-radius:12px; font-size:0.82em; line-height:1.5; }
.chat-msg.user .chat-bubble { background:var(--accent); color:#fff; border-bottom-right-radius:4px; }
.chat-msg.bot .chat-bubble { background:#fff; border:1px solid #e0e0e0; border-bottom-left-radius:4px; }
.small-note { font-size:0.68em; color:var(--dim); margin-top:6px; }
</style>
</head>
<body style="padding:20px">
<!-- ============ H5 员工端 ============ -->
<div class="section">
<span class="section-label">H5 员工端</span>
<h2>📱 分诊交互 + 置信门控</h2>
<div class="grid grid-2">
<!-- 左侧:分诊置顶卡片 -->
<div class="card-mock" style="min-height:360px">
<div class="chat-area">
<!-- AI 回答 -->
<div class="chat-msg bot">
<div class="chat-bubble" style="position:relative">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px">
<span style="font-size:0.65em;color:var(--dim)">AI 助手</span>
<span style="font-size:0.6em;margin-left:auto;color:#f0a000">🟡 72%</span>
</div>
您好!关于 Outlook 登录失败的问题,我先帮您分诊确认几个信息 👇
</div>
</div>
<!-- 置顶分诊卡片 -->
<div style="margin:10px 0">
<div class="triage-card">
<span class="triage-badge">🤖 AI 分诊</span>
<div class="triage-step">问题确认 · 第 1/3 步</div>
<div class="triage-title">Outlook 是否弹出了具体的错误提示?</div>
<div class="triage-option">
<div class="dot"></div>
<span>弹出了,提示「无法连接到服务器」</span>
<span class="prob">68%</span>
</div>
<div class="triage-option">
<div class="dot"></div>
<span>弹出了,提示「密码错误」</span>
<span class="prob">22%</span>
</div>
<div class="triage-option">
<div class="dot"></div>
<span>没有弹出任何提示,直接闪退</span>
<span class="prob">8%</span>
</div>
<div class="triage-option">
<div class="dot"></div>
<span>其他情况</span>
<span class="prob">2%</span>
</div>
<div class="triage-footer">
<span class="triage-expert">专家模式(一键多步)</span>
<div class="triage-toggle on"></div>
</div>
</div>
</div>
<!-- 转人工入口 -->
<div style="text-align:center;margin-top:8px;font-size:0.72em;color:var(--dim)">
没有匹配的选项?<a href="#" style="color:var(--accent)">🔔 转人工协助</a>
</div>
</div>
</div>
<!-- 右侧:置信门控横幅 -->
<div class="card-mock" style="min-height:360px">
<div class="chat-area">
<!-- 低置信场景 -->
<div class="chat-msg bot">
<div class="chat-bubble" style="position:relative">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px">
<span style="font-size:0.65em;color:var(--dim)">AI 助手</span>
<span style="font-size:0.6em;margin-left:auto;color:var(--danger)">🔴 48%</span>
</div>
关于您描述的打印机故障,可能涉及硬件层面,我收集到以下上下文供坐席参考...
</div>
</div>
<!-- 置信门控横幅 -->
<div class="conf-banner warn">
<span class="conf-icon">⚠️</span>
<div>
<div class="conf-text">
<span class="conf-badge">置信度 48%</span>
低于安全阈值(70%),AI 可能无法准确回答
</div>
<div class="conf-snapshot">
已收集上下文:打印机型号 HP M404dn · 错误灯闪烁 · 最近更换过墨盒 · 网络连接正常
</div>
</div>
<span class="conf-transfer">转人工 →</span>
</div>
<!-- 已转人工确认 -->
<div class="chat-msg bot">
<div class="chat-bubble" style="background:#e8f5e9;border:1px solid var(--accent);color:var(--text);text-align:center">
✅ 已为您转接人工坐席<br>
<span style="font-size:0.72em;color:var(--dim)">预计等待 2 分钟,当前排队第 1 位</span>
</div>
</div>
</div>
<div class="small-note" style="padding:0 10px;">
📌 当 AI 置信度 &lt; 0.7 时置顶显示,含已收集的上下文快照
</div>
</div>
</div>
</div>
<!-- ============ 坐席端 ============ -->
<div class="section">
<span class="section-label">坐席端</span>
<h2>💬 内联审批卡片 + 拓扑预览 + 重复检测 + 代答排除</h2>
<!-- 审批卡片(带重复检测) -->
<div class="grid grid-3">
<div class="card-mock" style="min-height:320px">
<div style="font-size:0.78em;color:var(--dim);margin-bottom:8px">📋 会话中浮现的知识建议</div>
<div class="approval-card pending">
<div class="approval-header">
<span class="approval-type">💡 知识建议</span>
<span style="font-size:0.68em;color:var(--dim);margin-left:auto">置信 92%</span>
</div>
<div class="approval-title">Outlook 登录失败 → 检查密码是否过期</div>
<div class="approval-body">用户在 AD 域的密码每 90 天过期,需引导通过 CTRL+ALT+DEL → 更改密码。适用 Outlook 2019/365。</div>
<!-- 迷你拓扑 -->
<div class="mini-topo">
<svg viewBox="0 0 240 60" style="width:100%;height:60px">
<circle cx="60" cy="30" r="14" fill="#07C16020" stroke="#07C160" stroke-width="1.5"/>
<text x="60" y="34" text-anchor="middle" font-size="9" fill="#07C160">Issue</text>
<line x1="74" y1="30" x2="106" y2="30" stroke="#07C160" stroke-width="1.2" marker-end="url(#arrow)"/>
<circle cx="120" cy="30" r="14" fill="#ffc30020" stroke="#ffc300" stroke-width="1.5"/>
<text x="120" y="34" text-anchor="middle" font-size="9" fill="#b8860b">Action</text>
<line x1="134" y1="30" x2="166" y2="30" stroke="#07C160" stroke-width="1.2"/>
<circle cx="180" cy="30" r="14" fill="#e0e0e0" stroke="#999" stroke-width="1.5"/>
<text x="180" y="34" text-anchor="middle" font-size="9" fill="#666">Relation</text>
<defs><marker id="arrow" markerWidth="6" markerHeight="4" refX="6" refY="2" orient="auto"><polygon points="0 0, 6 2, 0 4" fill="#07C160"/></marker></defs>
</svg>
</div>
<div class="approval-actions">
<button class="btn-accept">✅ 采纳</button>
<button class="btn-reject">❌ 驳回</button>
<button class="btn-rewrite">✏️ 改写</button>
<select style="font-size:0.7em;margin-left:auto;padding:4px 8px;border-radius:4px;border:1px solid var(--border)">
<option>全员可见</option>
<option>仅坐席</option>
</select>
</div>
</div>
</div>
<!-- 重复检测卡片 -->
<div class="card-mock" style="min-height:320px">
<div style="font-size:0.78em;color:var(--dim);margin-bottom:8px">⚠️ 检测到重复知识</div>
<div class="approval-card duplicate">
<div class="approval-header">
<span class="approval-type duplicate-tag">⚠ 可能重复</span>
<span style="font-size:0.68em;color:var(--dim);margin-left:auto">置信 85%</span>
</div>
<div class="approval-title">Outlook 无法登录 → 密码重置</div>
<div class="approval-body">密码过期导致登录失败,需重置 AD 密码。覆盖 Outlook 2016/2019。</div>
<div class="dup-warning">
<div class="dup-title">⚠️ 发现 2 条相似知识</div>
<div class="dup-item">
<span>Outlook 登录失败 → 密码过期检查</span>
<span class="dup-sim">89%</span>
</div>
<div class="dup-item">
<span>邮箱密码过期重置流程</span>
<span class="dup-sim">76%</span>
</div>
</div>
<div class="approval-actions">
<button class="btn-accept">🔄 合并</button>
<button class="btn-reject">❌ 新建</button>
<button style="font-size:0.75em;padding:5px 14px;border-radius:6px;border:1px solid var(--border);cursor:pointer;background:#fff">✏️ 改写</button>
</div>
</div>
</div>
<!-- 代答排除 -->
<div class="card-mock" style="min-height:320px">
<div style="font-size:0.78em;color:var(--dim);margin-bottom:8px">✋ 坐席代答 + 排除控件</div>
<div style="padding:12px">
<div style="font-weight:600;font-size:0.85em;margin-bottom:8px">AI 推荐回复(1/3</div>
<div class="card" style="margin-bottom:8px;position:relative">
<div style="font-size:0.78em;line-height:1.5;padding-right:24px">
您好,Outlook 登录失败通常是密码过期导致的。请按 CTRL+ALT+DEL → 更改密码,新密码需包含大写、小写和数字。
</div>
<span style="position:absolute;top:6px;right:6px;cursor:pointer;font-size:0.8em;" title="排除此建议"></span>
</div>
<div class="card" style="margin-bottom:8px;opacity:0.6;position:relative">
<div style="font-size:0.78em;line-height:1.5;padding-right:24px;text-decoration:line-through">
请检查网络连接,确认可以 ping 通 exchange.servyou.com.cn。
</div>
<span style="position:absolute;top:6px;right:6px;cursor:pointer;font-size:0.8em;color:var(--danger)"></span>
<span style="font-size:0.6em;color:var(--danger)">已排除,不再推荐</span>
</div>
<div class="card" style="border:1px solid var(--accent);position:relative">
<div style="font-size:0.78em;line-height:1.5;padding-right:24px">
如果密码修改后仍无法登录,建议使用 Web 版 OWA 临时访问:https://mail.servyou.com.cn
</div>
<span style="position:absolute;top:6px;right:6px;cursor:pointer;color:var(--accent);font-size:0.8em" title="推荐此方案"></span>
</div>
<div class="small-note">Ctrl+1/2/3 快捷采纳 · 30s 倒计时 · ⭐ 标记为推荐</div>
</div>
</div>
</div>
</div>
<!-- ============ 管理后台 ============ -->
<div class="section">
<span class="section-label">管理后台</span>
<h2>⚙️ 知识迭代管理 · 图谱可视化 · 合并去重</h2>
<!-- 统计栏 -->
<div class="stats">
<div class="stat-item">
<div class="stat-num">12</div>
<div class="stat-label">待审核</div>
</div>
<div class="stat-item">
<div class="stat-num">47</div>
<div class="stat-label">已采纳</div>
</div>
<div class="stat-item">
<div class="stat-num">156</div>
<div class="stat-label">图中节点</div>
</div>
<div class="stat-item">
<div class="stat-num">3</div>
<div class="stat-label">重复待合并</div>
</div>
</div>
<!-- 视图切换 -->
<div class="view-toggle">
<button class="active">📋 列表视图</button>
<button>🔮 图谱视图</button>
</div>
<div class="grid grid-2">
<!-- 知识列表 -->
<div>
<div class="kb-list card">
<div class="kb-item">
<div class="kb-status pending"></div>
<div class="kb-info">
<div class="kb-name">Outlook 登录失败 → 密码过期检查</div>
<div class="kb-meta">置信 92% · 全员可见 · 3 分钟前</div>
</div>
<div class="kb-actions">
<button style="color:var(--accent)">审核</button>
</div>
</div>
<div class="kb-item">
<div class="kb-status pending"></div>
<div class="kb-info">
<div class="kb-name">打印机 HP M404 卡纸 → 清洁搓纸轮</div>
<div class="kb-meta">置信 85% · IT 设备 · 5 分钟前</div>
</div>
<div class="kb-actions">
<button style="color:var(--accent)">审核</button>
<button style="color:var(--danger);font-size:0.65em">⚠ 重复</button>
</div>
</div>
<div class="kb-item">
<div class="kb-status pending"></div>
<div class="kb-info">
<div class="kb-name">VPN 连接超时 → 切换协议 IKEv2</div>
<div class="kb-meta">置信 78% · 全员可见 · 12 分钟前</div>
</div>
<div class="kb-actions">
<button style="color:var(--accent)">审核</button>
</div>
</div>
<div class="kb-item">
<div class="kb-status approved"></div>
<div class="kb-info">
<div class="kb-name">WiFi 自动断开 → 更新网卡驱动</div>
<div class="kb-meta">已采纳 · 全员可见 · 1 小时前</div>
</div>
</div>
<div class="kb-item">
<div class="kb-status approved"></div>
<div class="kb-info">
<div class="kb-name">蓝屏 0x0000007B → 检查磁盘模式 AHCI</div>
<div class="kb-meta">已采纳 · IT 设备 · 2 小时前</div>
</div>
</div>
</div>
</div>
<!-- ECharts 图谱 -->
<div>
<div class="echarts-area">
<svg viewBox="0 0 400 300" style="width:100%;height:100%">
<!-- Connections -->
<line x1="200" y1="45" x2="80" y2="140" stroke="#07C16040" stroke-width="1.5"/>
<line x1="200" y1="45" x2="320" y2="140" stroke="#07C16040" stroke-width="1.5"/>
<line x1="80" y1="140" x2="80" y2="230" stroke="#f0a00040" stroke-width="1.5"/>
<line x1="320" y1="140" x2="320" y2="230" stroke="#07C16040" stroke-width="1.5"/>
<line x1="80" y1="230" x2="320" y2="230" stroke="#e0e0e0" stroke-width="1" stroke-dasharray="4"/>
<!-- Nodes -->
<circle cx="200" cy="45" r="22" fill="#07C160" opacity="0.15"/>
<circle cx="200" cy="45" r="22" fill="none" stroke="#07C160" stroke-width="2"/>
<text x="200" y="40" text-anchor="middle" font-size="8" fill="#666">问题</text>
<text x="200" y="55" text-anchor="middle" font-size="7" fill="#07C160">登录</text>
<circle cx="80" cy="140" r="20" fill="#ffc30020"/>
<circle cx="80" cy="140" r="20" fill="none" stroke="#f0a000" stroke-width="1.5"/>
<text x="80" y="136" text-anchor="middle" font-size="8" fill="#666">处置</text>
<text x="80" y="149" text-anchor="middle" font-size="7" fill="#b8860b">密码</text>
<circle cx="320" cy="140" r="20" fill="#07C16020"/>
<circle cx="320" cy="140" r="20" fill="none" stroke="#07C160" stroke-width="1.5"/>
<text x="320" y="136" text-anchor="middle" font-size="8" fill="#666">处置</text>
<text x="320" y="149" text-anchor="middle" font-size="7" fill="#07C160">OWA</text>
<circle cx="80" cy="230" r="16" fill="#e0e0e0"/>
<circle cx="80" cy="230" r="16" fill="none" stroke="#999" stroke-width="1"/>
<text x="80" y="233" text-anchor="middle" font-size="7" fill="#999">AD域</text>
<circle cx="320" cy="230" r="16" fill="#e0e0e0"/>
<circle cx="320" cy="230" r="16" fill="none" stroke="#999" stroke-width="1"/>
<text x="320" y="233" text-anchor="middle" font-size="7" fill="#999">Web版</text>
</svg>
</div>
<div class="small-note" style="text-align:center;margin-top:8px">
📌 Issue(蓝绿)→ Action(橙色)→ Relation(灰)| 拖拽可移动 · 滚轮缩放
</div>
</div>
</div>
<!-- 合并去重弹窗 -->
<div class="merge-dialog" style="margin-top:16px">
<div class="merge-title">⚠️ 检测到重复知识 — 建议合并</div>
<div class="grid grid-2" style="margin-top:10px">
<div class="card" style="background:#fafafa">
<div style="font-size:0.72em;color:var(--accent);margin-bottom:4px">源(保留)</div>
<div style="font-weight:500;font-size:0.82em">Outlook 登录失败 → 密码过期检查</div>
<div style="font-size:0.7em;color:var(--dim);margin-top:4px">置信 92% · 3 次采纳 · 标签: outlook, password, ad</div>
</div>
<div class="card" style="background:#fde8e8">
<div style="font-size:0.72em;color:var(--danger);margin-bottom:4px">目标(合并入)</div>
<div style="font-weight:500;font-size:0.82em">Outlook 无法登录 → 密码重置</div>
<div style="font-size:0.7em;color:var(--dim);margin-top:4px">置信 85% · 1 次采纳 · 标签: outlook, password</div>
<div style="font-size:0.7em;color:var(--danger);margin-top:2px">相似度: 89%</div>
</div>
</div>
<div style="text-align:right;margin-top:12px">
<button style="padding:6px 20px;border-radius:6px;border:1px solid var(--border);background:#fff;cursor:pointer">取消</button>
<button style="padding:6px 20px;border-radius:6px;border:none;background:var(--accent);color:#fff;cursor:pointer;margin-left:8px">执行合并</button>
</div>
</div>
<!-- 审核弹窗中的图谱 -->
<div style="margin-top:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">
<div style="font-weight:600;font-size:0.85em;margin-bottom:8px">🔍 图谱编辑 — 审核知识条目</div>
<div style="display:flex;gap:16px">
<div style="flex:1">
<label style="font-size:0.72em;color:var(--dim)">Issue(问题节点)</label>
<input type="text" value="Outlook 登录失败" style="width:100%;padding:6px 10px;border:1px solid var(--border);border-radius:4px;margin:4px 0;font-size:0.82em"/>
<label style="font-size:0.72em;color:var(--dim)">Action(处置方案)</label>
<input type="text" value="检查密码是否过期并引导重置" style="width:100%;padding:6px 10px;border:1px solid var(--border);border-radius:4px;margin:4px 0;font-size:0.82em"/>
<label style="font-size:0.72em;color:var(--dim)">Relation(关联节点)</label>
<input type="text" value="AD域密码策略" style="width:100%;padding:6px 10px;border:1px solid var(--border);border-radius:4px;margin:4px 0;font-size:0.82em"/>
<label style="font-size:0.72em;color:var(--dim)">Parent Issue(父问题)</label>
<input type="text" value="邮箱客户端问题" style="width:100%;padding:6px 10px;border:1px solid var(--border);border-radius:4px;margin:4px 0;font-size:0.82em"/>
</div>
<div style="width:160px;background:#fafafa;border-radius:var(--radius);display:flex;align-items:center;justify-content:center">
<svg viewBox="0 0 140 200" style="width:100%">
<circle cx="70" cy="30" r="16" fill="#07C16020" stroke="#07C160" stroke-width="1.5"/>
<text x="70" y="34" text-anchor="middle" font-size="8" fill="#07C160">邮箱客户端</text>
<line x1="70" y1="46" x2="70" y2="74" stroke="#07C160" stroke-width="1.2"/>
<circle cx="70" cy="85" r="16" fill="#07C16020" stroke="#07C160" stroke-width="1.5"/>
<text x="70" y="89" text-anchor="middle" font-size="8" fill="#07C160">登录失败</text>
<line x1="70" y1="101" x2="70" y2="129" stroke="#f0a000" stroke-width="1.2"/>
<circle cx="70" cy="140" r="16" fill="#ffc30020" stroke="#f0a000" stroke-width="1.5"/>
<text x="70" y="144" text-anchor="middle" font-size="8" fill="#b8860b">密码重置</text>
<line x1="70" y1="156" x2="70" y2="179" stroke="#999" stroke-width="1"/>
<circle cx="70" cy="188" r="12" fill="#f0f0f0" stroke="#999" stroke-width="1"/>
<text x="70" y="191" text-anchor="middle" font-size="7" fill="#999">AD域</text>
</svg>
</div>
</div>
</div>
</div>
<!-- ============ 流程闭合 ============ -->
<div class="section">
<span class="section-label">全链路</span>
<h2>🔄 会话关闭 → 自动生成知识建议 → 训练师审核 → 写图</h2>
<div style="display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap;padding:20px 0">
<div class="card" style="text-align:center;width:130px">
<div style="font-size:1.5em">💬</div>
<div style="font-weight:600;font-size:0.78em">会话关闭</div>
<div style="font-size:0.65em;color:var(--dim)">resolve API</div>
</div>
<span style="font-size:1.3em;color:var(--accent)"></span>
<div class="card" style="text-align:center;width:130px">
<div style="font-size:1.5em">🤖</div>
<div style="font-weight:600;font-size:0.78em">Dify 生成</div>
<div style="font-size:0.65em;color:var(--dim)">异步 ensure_future</div>
</div>
<span style="font-size:1.3em;color:var(--accent)"></span>
<div class="card" style="text-align:center;width:130px">
<div style="font-size:1.5em"></div>
<div style="font-weight:600;font-size:0.78em">Pending</div>
<div style="font-size:0.65em;color:var(--dim)">等待审核</div>
</div>
<span style="font-size:1.3em;color:var(--accent)"></span>
<div class="card" style="text-align:center;width:130px">
<div style="font-size:1.5em"></div>
<div style="font-weight:600;font-size:0.78em">审批通过</div>
<div style="font-size:0.65em;color:var(--dim)">训练师审核</div>
</div>
<span style="font-size:1.3em;color:var(--accent)"></span>
<div class="card" style="text-align:center;width:130px;border:1.5px solid var(--accent)">
<div style="font-size:1.5em">🔮</div>
<div style="font-weight:600;font-size:0.78em">写图</div>
<div style="font-size:0.65em;color:var(--dim)">Neo4j</div>
</div>
</div>
</div>
<p style="text-align:center;color:var(--dim);font-size:0.72em;margin:20px 0">
IT智能服务台 · 知识库迭代 v0.7.2 原型 v1 · 企微浅色风格 · accent=#07C160
</p>
</body>
</html>