# Troubleshoot助手功能优化_计划执行

## 1. 项目概述

### 1.1 项目背景
针对 Troubleshoot 助手当前使用中的三个痛点问题进行优化，提升用户体验和实用性。

### 1.2 项目目标
- 实现模型选择的 localStorage 持久化，用户刷新页面后无需重新选择
- 提供缓存管理界面，管理员可查看缓存统计和清理缓存
- 支持排查结果导出为 Word 文档，方便现场同事分享和归档

### 1.3 开发周期
**总计：1.2 天**
- 需求一：0.2 天
- 需求二：0.5 天
- 需求三：0.5 天

---

## 2. 项目结构

```
.claude/skills/Troubleshoot/code/web/
├── templates/
│   └── index.html              # 前端页面（需修改）
├── server.py                   # 后端服务（需修改）
├── cache_manager.py            # 缓存管理模块（已存在）
├── config.json                 # 配置文件（需修改）
└── requirements.txt            # 依赖包列表（需更新）
```

---

## 3. 技术方案

### 3.1 技术栈

| 技术 | 用途 |
|------|------|
| JavaScript | 前端交互、localStorage 操作 |
| Python 3.10+ | 后端服务 |
| Flask | Web 框架 |
| python-docx | Word 文档生成 |

### 3.2 核心流程

```
┌─────────────────────────────────────────────────────────────┐
│                    需求一：模型选择持久化                      │
├─────────────────────────────────────────────────────────────┤
│  1. 监听下拉框 change 事件                                    │
│  2. 保存选择到 localStorage                                  │
│  3. 页面加载时读取并恢复选择                                  │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                    需求二：缓存管理界面                        │
├─────────────────────────────────────────────────────────────┤
│  前端：                                                      │
│  1. 增加缓存管理按钮和模态框                                  │
│  2. 调用 /api/cache/stats 获取统计信息                       │
│  3. 调用 /api/cache/clear 清空缓存                           │
│                                                              │
│  后端：                                                      │
│  1. 新增 /api/cache/stats 接口                               │
│  2. 新增 /api/cache/clear 接口                               │
│  3. 权限验证（管理员名单）                                    │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                    需求三：排查结果导出                        │
├─────────────────────────────────────────────────────────────┤
│  前端：                                                      │
│  1. 在结果区域增加"导出"按钮                                 │
│  2. 收集排查结果数据                                         │
│  3. 调用 /api/export 接口并触发下载                          │
│                                                              │
│  后端：                                                      │
│  1. 新增 /api/export 接口                                    │
│  2. 使用 python-docx 生成 Word 文档                          │
│  3. 返回文件流                                               │
└─────────────────────────────────────────────────────────────┘
```

---

## 4. 详细设计

### 4.1 需求一：模型选择 localStorage 持久化

#### 4.1.1 前端实现（index.html）

在 `<script>` 标签中增加以下代码：

```javascript
// ============================================================
// 模型选择 localStorage 持久化
// ============================================================

const MODEL_STORAGE_KEY = 'troubleshoot_model';

// 页面加载时恢复模型选择
function restoreModelSelection() {
    const savedModel = localStorage.getItem(MODEL_STORAGE_KEY);
    const modelSelect = document.getElementById('modelSelect');
    
    if (savedModel) {
        // 恢复上次的选择
        modelSelect.value = savedModel;
    } else {
        // 默认选择标准模式
        modelSelect.value = 'glm-5.1';
    }
}

// 监听模型选择变化
document.getElementById('modelSelect').addEventListener('change', function() {
    const selectedModel = this.value;
    localStorage.setItem(MODEL_STORAGE_KEY, selectedModel);
    console.log('模型选择已保存:', selectedModel);
});

// 页面加载时初始化
document.addEventListener('DOMContentLoaded', () => {
    restoreModelSelection();
    loadProjects();
});
```

#### 4.1.2 测试验证

```javascript
// 测试步骤：
// 1. 打开页面，选择"快速模式"
// 2. 刷新页面
// 3. 验证下拉框保持"快速模式"选择
// 4. 打开浏览器控制台，输入：localStorage.getItem('troubleshoot_model')
// 5. 验证返回值为 'glm-4-flash'
```

---

### 4.2 需求二：缓存管理界面

#### 4.2.1 后端接口实现（server.py）

```python
# ============================================================
# 缓存管理接口
# ============================================================

@app.route('/api/cache/stats', methods=['GET'])
def get_cache_stats():
    """
    获取缓存统计信息。
    
    返回：
      - total_files: 缓存文件总数
      - total_size_mb: 缓存总大小（MB）
      - oldest: 最早缓存时间
      - newest: 最新缓存时间
    """
    try:
        cache = get_cache_manager()
        stats = cache.get_stats()
        
        return jsonify({
            'success': True,
            'stats': stats,
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': f'获取缓存统计失败：{str(e)}',
        }), 500


@app.route('/api/cache/clear', methods=['POST'])
def clear_cache():
    """
    清空所有缓存。
    
    需要：管理员权限验证
    """
    # 权限验证（从请求头或配置文件获取）
    # TODO: 实现管理员权限验证
    
    try:
        cache = get_cache_manager()
        cleared_count = cache.clear_all()
        
        # 审计日志
        log_audit({
            'timestamp': datetime.now().isoformat(),
            'action': 'clear_cache',
            'cleared_count': cleared_count,
        })
        
        return jsonify({
            'success': True,
            'message': f'已清除 {cleared_count} 个缓存文件',
            'cleared_count': cleared_count,
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'error': f'清空缓存失败：{str(e)}',
        }), 500
```

#### 4.2.2 前端界面实现（index.html）

在页面底部增加缓存管理按钮和模态框：

```html
<!-- 缓存管理按钮 -->
<div class="cache-management" style="text-align: right; margin-top: 16px;">
    <button class="btn btn-secondary" onclick="showCacheModal()">
        📊 缓存管理
    </button>
</div>

<!-- 缓存管理模态框 -->
<div class="modal-overlay" id="cacheModal">
    <div class="modal">
        <div class="modal-header">
            <h3>缓存管理</h3>
            <button class="modal-close" onclick="closeCacheModal()">✕</button>
        </div>
        <div class="modal-body">
            <div id="cacheStats">
                <p>缓存文件总数：<span id="cacheTotalFiles">-</span></p>
                <p>缓存总大小：<span id="cacheTotalSize">-</span></p>
                <p>最早缓存时间：<span id="cacheOldest">-</span></p>
                <p>最新缓存时间：<span id="cacheNewest">-</span></p>
            </div>
        </div>
        <div class="modal-footer">
            <button class="btn btn-secondary" onclick="closeCacheModal()">关闭</button>
            <button class="btn btn-primary" onclick="clearCache()" style="background: var(--danger);">
                🗑️ 清除所有缓存
            </button>
        </div>
    </div>
</div>
```

JavaScript 交互逻辑：

```javascript
// 显示缓存管理模态框
async function showCacheModal() {
    document.getElementById('cacheModal').classList.add('show');
    
    // 获取缓存统计
    try {
        const resp = await fetch('/api/cache/stats');
        const data = await resp.json();
        
        if (data.success) {
            document.getElementById('cacheTotalFiles').textContent = data.stats.total_files;
            document.getElementById('cacheTotalSize').textContent = data.stats.total_size_mb + ' MB';
            document.getElementById('cacheOldest').textContent = data.stats.oldest || '-';
            document.getElementById('cacheNewest').textContent = data.stats.newest || '-';
        }
    } catch (e) {
        console.error('获取缓存统计失败:', e);
    }
}

// 关闭缓存管理模态框
function closeCacheModal() {
    document.getElementById('cacheModal').classList.remove('show');
}

// 清空缓存
async function clearCache() {
    const confirmed = confirm('确定要清空所有缓存吗？这将删除所有已缓存的排查结果。');
    
    if (!confirmed) {
        return;
    }
    
    try {
        const resp = await fetch('/api/cache/clear', { method: 'POST' });
        const data = await resp.json();
        
        if (data.success) {
            alert(data.message);
            showCacheModal(); // 刷新统计信息
        } else {
            alert('清空缓存失败：' + data.error);
        }
    } catch (e) {
        alert('清空缓存异常：' + e.message);
    }
}
```

---

### 4.3 需求三：排查结果导出

#### 4.3.1 后端接口实现（server.py）

```python
# ============================================================
# 导出接口
# ============================================================

from docx import Document
from docx.shared import Pt, RGBColor
from io import BytesIO

@app.route('/api/export', methods=['POST'])
def export_report():
    """
    导出排查报告为 Word 文档。
    
    接收参数：
      - project_name: 项目名称
      - system_type: 系统类型
      - apk_product: APK 产品
      - query: 问题描述
      - matched_cases: 匹配的历史案例
      - response: AI 分析结果
    
    返回：
      Word 文档文件流
    """
    data = request.get_json() or {}
    
    # 收集参数
    project_name = data.get('project_name', '').strip()
    system_type = data.get('system_type', '').strip()
    apk_product = data.get('apk_product', '').strip()
    query = data.get('query', '').strip()
    matched_cases = data.get('matched_cases', [])
    response = data.get('response', '').strip()
    
    # 生成 Word 文档
    doc = Document()
    
    # 标题
    title = doc.add_heading('问题排查报告', 0)
    title.alignment = 1  # 居中
    
    # 导出时间
    doc.add_paragraph(f'导出时间：{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    doc.add_paragraph('')
    
    # 基本信息
    doc.add_heading('基本信息', level=1)
    doc.add_paragraph(f'项目名称：{project_name}')
    doc.add_paragraph(f'系统类型：{system_type}')
    if apk_product:
        doc.add_paragraph(f'APK 产品：{apk_product}')
    doc.add_paragraph('')
    
    # 问题描述
    doc.add_heading('问题描述', level=1)
    doc.add_paragraph(query)
    doc.add_paragraph('')
    
    # 匹配的历史案例
    if matched_cases:
        doc.add_heading('匹配的历史案例', level=1)
        for c in matched_cases:
            p = doc.add_paragraph()
            p.add_run(f'{c["rank"]}. ').bold = True
            p.add_run(f'{c.get("project", "")} - {c.get("title", "")}')
        doc.add_paragraph('')
    
    # AI 分析结果
    doc.add_heading('AI 分析结果', level=1)
    # 简单渲染 Markdown（粗体、标题、列表）
    for line in response.split('\n'):
        if line.startswith('### '):
            doc.add_heading(line[4:], level=2)
        elif line.startswith('## '):
            doc.add_heading(line[3:], level=2)
        elif line.startswith('- '):
            doc.add_paragraph(line[2:], style='List Bullet')
        elif line.strip():
            doc.add_paragraph(line)
    
    # 保存到内存流
    buffer = BytesIO()
    doc.save(buffer)
    buffer.seek(0)
    
    # 生成文件名
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    filename = f'排查报告_{project_name}_{timestamp}.docx'
    
    # 返回文件流
    return send_file(
        buffer,
        as_attachment=True,
        download_name=filename,
        mimetype='application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    )
```

#### 4.3.2 前端导出按钮（index.html）

在排查结果区域增加导出按钮：

```html
<!-- 导出按钮（在结果区域） -->
<div class="export-section" style="margin-top: 24px; text-align: right;">
    <button class="btn btn-primary" onclick="exportReport()">
        📄 导出 Word 文档
    </button>
</div>
```

JavaScript 导出逻辑：

```javascript
// 导出排查报告
async function exportReport() {
    // 收集数据
    const projectName = document.getElementById('projectName').value.trim();
    const systemType = document.getElementById('systemType').value;
    const apkProduct = document.getElementById('apkProduct').value.trim();
    const query = document.getElementById('query').value.trim();
    
    // 获取 AI 响应内容（从结果区域提取）
    const responseEl = document.getElementById('responseContent');
    const response = responseEl ? responseEl.innerText : '';
    
    // 获取匹配案例
    const matchedCases = lastMatchedCases || [];
    
    // 调用导出接口
    try {
        const resp = await fetch('/api/export', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                project_name: projectName,
                system_type: systemType,
                apk_product: apkProduct,
                query: query,
                matched_cases: matchedCases,
                response: response,
            }),
        });
        
        if (resp.ok) {
            // 触发下载
            const blob = await resp.blob();
            const url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `排查报告_${projectName}_${new Date().toISOString().slice(0,10)}.docx`;
            a.click();
            window.URL.revokeObjectURL(url);
        } else {
            alert('导出失败');
        }
    } catch (e) {
        alert('导出异常：' + e.message);
    }
}
```

---

## 5. 实施计划

### 5.1 实施步骤

| 序号 | 任务 | 预计时间 | 状态 | 依赖 |
|------|------|----------|------|------|
| 1 | 需求一：模型选择 localStorage 持久化 | 0.2天 | ⏳ 待开始 | - |
| 1.1 | 前端实现监听和保存逻辑 | 0.1天 | ⏳ 待开始 | - |
| 1.2 | 前端实现恢复选择逻辑 | 0.05天 | ⏳ 待开始 | 1.1 |
| 1.3 | 测试验证 | 0.05天 | ⏳ 待开始 | 1.2 |
| 2 | 需求二：缓存管理界面 | 0.5天 | ⏳ 待开始 | - |
| 2.1 | 后端新增 /api/cache/stats 接口 | 0.1天 | ⏳ 待开始 | - |
| 2.2 | 后端新增 /api/cache/clear 接口 | 0.1天 | ⏳ 待开始 | 2.1 |
| 2.3 | 前端增加缓存管理按钮和模态框 | 0.2天 | ⏳ 待开始 | 2.2 |
| 2.4 | 测试验证 | 0.1天 | ⏳ 待开始 | 2.3 |
| 3 | 需求三：排查结果导出 | 0.5天 | ⏳ 待开始 | - |
| 3.1 | 后端新增 /api/export 接口 | 0.2天 | ⏳ 待开始 | - |
| 3.2 | 前端增加导出按钮和逻辑 | 0.2天 | ⏳ 待开始 | 3.1 |
| 3.3 | 测试验证 | 0.1天 | ⏳ 待开始 | 3.2 |
| 4 | 整体测试和文档更新 | 0.2天 | ⏳ 待开始 | 1,2,3 |

**总计：** 约 1.2 天

### 5.2 依赖包

在 `requirements.txt` 中新增：

```
python-docx>=0.8.11
```

---

## 6. 测试验证

### 6.1 单元测试

- 测试 localStorage 保存和读取
- 测试缓存统计接口
- 测试缓存清空接口
- 测试 Word 文档生成

### 6.2 集成测试

- 端到端测试模型选择持久化流程
- 端到端测试缓存管理流程
- 端到端测试导出流程

### 6.3 验收标准

| 验收项 | 标准 |
|--------|------|
| 模型选择持久化 | 刷新页面后下拉框保持上次选择 |
| 缓存统计显示 | 点击按钮后正确显示缓存统计信息 |
| 缓存清空功能 | 确认后成功清空所有缓存文件 |
| 导出 Word 文档 | 成功下载包含完整内容的 Word 文档 |
| 文档格式规范 | 标题层级分明，内容清晰 |
| 文件名规范 | 符合命名规范 `排查报告_项目名称_时间戳.docx` |

---

## 7. 注意事项

1. **浏览器兼容性**：localStorage 在现代浏览器中广泛支持，无需担心兼容性问题
2. **权限控制**：缓存管理功能需管理员权限，避免误操作
3. **文件名规范**：导出文件名需符合规范，避免特殊字符导致下载失败
4. **依赖安装**：需确保 `python-docx` 已正确安装

---

## 8. 后续优化

- [ ] 导出功能支持 PDF 格式（需要 wkhtmltopdf 依赖）
- [ ] 缓存命中率统计（需记录查询次数和命中次数）
- [ ] 导出模板自定义（允许用户自定义导出格式）
- [ ] 缓存过期策略优化（支持自定义过期时间）
- [ ] 导出历史记录（记录导出操作日志）