刘立冬 2 weken geleden
bovenliggende
commit
3a66f18e6d
1 gewijzigde bestanden met toevoegingen van 239 en 55 verwijderingen
  1. 239 55
      src/visualizers/deconstruction_visualizer.py

+ 239 - 55
src/visualizers/deconstruction_visualizer.py

@@ -879,15 +879,14 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
             padding: 20px;
             padding: 20px;
         }}
         }}
 
 
-        /* 候选词库固定区域 */
+        /* 候选词库固定区域 - 移到顶部横跨全宽 */
         .candidate-library {{
         .candidate-library {{
-            position: sticky;
-            top: 0;
-            z-index: 100;
             background: white;
             background: white;
-            border-bottom: 2px solid #e5e7eb;
-            padding: 15px;
-            margin-bottom: 10px;
+            max-width: 1400px;
+            margin: 0 auto 20px;
+            padding: 15px 20px;
+            border-radius: 8px;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
         }}
         }}
 
 
         .candidate-header {{
         .candidate-header {{
@@ -927,11 +926,13 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
             max-height: 0;
             max-height: 0;
             overflow: hidden;
             overflow: hidden;
             transition: max-height 0.3s ease;
             transition: max-height 0.3s ease;
+            opacity: 0;
         }}
         }}
 
 
         .candidate-content.expanded {{
         .candidate-content.expanded {{
             max-height: 400px;
             max-height: 400px;
             overflow-y: auto;
             overflow-y: auto;
+            opacity: 1;
         }}
         }}
 
 
         .candidate-section {{
         .candidate-section {{
@@ -2820,11 +2821,10 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
         }}
         }}
 
 
         .high-similarity-item {{
         .high-similarity-item {{
-            padding: 10px 12px;
-            margin: 8px 0;
+            padding: 12px 15px;
+            border-bottom: 1px solid #e5e7eb;
             background: white;
             background: white;
             border-left: 3px solid #22c55e;
             border-left: 3px solid #22c55e;
-            border-radius: 4px;
             transition: all 0.2s ease;
             transition: all 0.2s ease;
         }}
         }}
 
 
@@ -2833,6 +2833,7 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
         }}
         }}
 
 
         .high-feature-name {{
         .high-feature-name {{
+            font-size: 14px;
             font-weight: 600;
             font-weight: 600;
             color: #166534;
             color: #166534;
             margin-bottom: 4px;
             margin-bottom: 4px;
@@ -2840,12 +2841,13 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
 
 
         .high-feature-score {{
         .high-feature-score {{
             display: inline-block;
             display: inline-block;
-            font-size: 13px;
+            font-size: 12px;
             font-weight: 600;
             font-weight: 600;
             color: #16a34a;
             color: #16a34a;
             background: #dcfce7;
             background: #dcfce7;
             padding: 2px 8px;
             padding: 2px 8px;
             border-radius: 4px;
             border-radius: 4px;
+            margin-right: 6px;
         }}
         }}
 
 
         .high-feature-meta {{
         .high-feature-meta {{
@@ -3115,10 +3117,16 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
         </div>
         </div>
     </div>
     </div>
 
 
+    <!-- 候选词库区域 -->
+    <div id="candidateLibraryContainer"></div>
+
     <!-- 主容器 - 级联布局 -->
     <!-- 主容器 - 级联布局 -->
     <div class="main-container">
     <div class="main-container">
         <!-- 左侧栏:原始特征列表 -->
         <!-- 左侧栏:原始特征列表 -->
-        <div class="left-sidebar" id="leftSidebar"></div>
+        <div class="left-sidebar" id="leftSidebar">
+            <div class="sidebar-header">选题点</div>
+            <div class="sidebar-content" id="leftContent"></div>
+        </div>
 
 
         <!-- 中间栏:base_word列表 -->
         <!-- 中间栏:base_word列表 -->
         <div class="middle-sidebar" id="middleSidebar">
         <div class="middle-sidebar" id="middleSidebar">
@@ -3134,6 +3142,7 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
 
 
         <!-- 详情区域:搜索结果 -->
         <!-- 详情区域:搜索结果 -->
         <div class="detail-area" id="detailArea">
         <div class="detail-area" id="detailArea">
+            <div class="sidebar-header">搜索结果</div>
             <div class="detail-placeholder">👈 请从左侧选择特征查看详情</div>
             <div class="detail-placeholder">👈 请从左侧选择特征查看详情</div>
             <div class="detail-content" id="detailContent"></div>
             <div class="detail-content" id="detailContent"></div>
         </div>
         </div>
@@ -3419,19 +3428,15 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
         let selectedSearchWordIdx = null;
         let selectedSearchWordIdx = null;
 
 
         function renderLeftSidebar() {{
         function renderLeftSidebar() {{
-            const sidebar = document.getElementById('leftSidebar');
+            const leftContent = document.getElementById('leftContent');
             let html = '';
             let html = '';
 
 
-            // 添加候选词库
-            const allCandidates = extractAllCandidates();
-            html += renderCandidateLibrary(allCandidates);
-
-            // 1. 高相似度特征区域(≥0.8)
+            // 1. 高相似度选题点区域(≥0.8)
             if (highSimilarityFeatures && highSimilarityFeatures.length > 0) {{
             if (highSimilarityFeatures && highSimilarityFeatures.length > 0) {{
                 html += `
                 html += `
                     <div class="high-similarity-section">
                     <div class="high-similarity-section">
                         <div class="high-similarity-header">
                         <div class="high-similarity-header">
-                            <div class="high-similarity-title">📋 当前帖子-已匹配(≥0.8)</div>
+                            <div class="high-similarity-title">📋 选题点-已匹配(≥0.8)</div>
                             <div class="high-similarity-count">${{highSimilarityFeatures.length}}个</div>
                             <div class="high-similarity-count">${{highSimilarityFeatures.length}}个</div>
                         </div>
                         </div>
                         <div class="high-similarity-list">
                         <div class="high-similarity-list">
@@ -3444,9 +3449,11 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
 
 
                     html += `
                     html += `
                         <div class="high-similarity-item">
                         <div class="high-similarity-item">
-                            <div class="high-feature-name">✓ ${{name}}</div>
-                            <div class="high-feature-score">${{similarity.toFixed(2)}}</div>
-                            <div class="high-feature-meta">${{dimension}}</div>
+                            <div class="high-feature-name">👤 ${{name}}</div>
+                            <div class="cascade-item-meta">
+                                <span class="high-feature-score">相似度: ${{similarity.toFixed(2)}}</span>
+                                <span class="high-feature-meta">${{dimension}}</span>
+                            </div>
                         </div>
                         </div>
                     `;
                     `;
                 }});
                 }});
@@ -3457,13 +3464,13 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
                 `;
                 `;
             }}
             }}
 
 
-            // 2. 部分匹配特征区域(0.5-0.8)
+            // 2. 部分匹配选题点区域(0.5-0.8)
             const partialMatchCount = data.length;
             const partialMatchCount = data.length;
             if (partialMatchCount > 0) {{
             if (partialMatchCount > 0) {{
                 html += `
                 html += `
                     <div class="partial-similarity-section">
                     <div class="partial-similarity-section">
                         <div class="partial-similarity-header">
                         <div class="partial-similarity-header">
-                            <div class="partial-similarity-title">📋 当前帖子-部分匹配(0.5-0.8)</div>
+                            <div class="partial-similarity-title">📋 选题点-部分匹配(0.5-0.8)</div>
                             <div class="partial-similarity-count">${{partialMatchCount}}个</div>
                             <div class="partial-similarity-count">${{partialMatchCount}}个</div>
                         </div>
                         </div>
                     </div>
                     </div>
@@ -3500,12 +3507,12 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
                 }});
                 }});
             }}
             }}
 
 
-            // 3. 低相似度特征区域(<0.5)
+            // 3. 低相似度选题点区域(<0.5)
             if (lowSimilarityFeatures && lowSimilarityFeatures.length > 0) {{
             if (lowSimilarityFeatures && lowSimilarityFeatures.length > 0) {{
                 html += `
                 html += `
                     <div class="low-similarity-section">
                     <div class="low-similarity-section">
                         <div class="low-similarity-header">
                         <div class="low-similarity-header">
-                            <div class="low-similarity-title">📋 当前帖子-未匹配(<0.5)</div>
+                            <div class="low-similarity-title">📋 选题点-未匹配(<0.5)</div>
                             <div class="low-similarity-count">${{lowSimilarityFeatures.length}}个</div>
                             <div class="low-similarity-count">${{lowSimilarityFeatures.length}}个</div>
                         </div>
                         </div>
                         <div class="low-similarity-list">
                         <div class="low-similarity-list">
@@ -3531,7 +3538,7 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
                 `;
                 `;
             }}
             }}
 
 
-            sidebar.innerHTML = html;
+            leftContent.innerHTML = html;
         }}
         }}
 
 
         // ========== 级联交互函数 ==========
         // ========== 级联交互函数 ==========
@@ -3632,6 +3639,12 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
                 const sourceWord = sw.source_word || '';
                 const sourceWord = sw.source_word || '';
                 const score = sw.score || 0;
                 const score = sw.score || 0;
 
 
+                // 获取综合评分信息
+                const comprehensiveScore = sw.comprehensive_score || 0;
+                const scoreDetail = sw.comprehensive_score_detail || {{}};
+                const totalN = scoreDetail.N || 0;
+                const matchM = scoreDetail.M || 0;
+
                 const evaluation = sw['evaluation_with_filter'];
                 const evaluation = sw['evaluation_with_filter'];
                 let evalBadges = '';
                 let evalBadges = '';
                 if (evaluation) {{
                 if (evaluation) {{
@@ -3661,6 +3674,14 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
                         <div style="font-size:11px;color:#6b7280;margin-top:4px;">
                         <div style="font-size:11px;color:#6b7280;margin-top:4px;">
                             组合词: ${{sourceWord}} → [${{score.toFixed(2)}}]
                             组合词: ${{sourceWord}} → [${{score.toFixed(2)}}]
                         </div>
                         </div>
+                        <div style="font-size:12px;margin-top:6px;display:flex;align-items:center;gap:8px;">
+                            <span style="color:#7c3aed;font-weight:600;">
+                                综合评分: ${{comprehensiveScore.toFixed(3)}}
+                            </span>
+                            <span style="color:#059669;font-weight:600;">
+                                M/N: ${{matchM}}/${{totalN}}
+                            </span>
+                        </div>
                         ${{evalBadges ? `<div style="margin-top:4px;">${{evalBadges}}</div>` : ''}}
                         ${{evalBadges ? `<div style="margin-top:4px;">${{evalBadges}}</div>` : ''}}
                         ${{reasoning ? `
                         ${{reasoning ? `
                             <div class="search-word-reasoning">
                             <div class="search-word-reasoning">
@@ -3698,6 +3719,20 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
             }}
             }}
         }}
         }}
 
 
+        // 折叠/展开帖子评估详情
+        function toggleEvalDetail(evalDetailId) {{
+            const detailEl = document.getElementById(evalDetailId);
+            const toggleBtn = event.currentTarget;
+
+            if (detailEl.style.display === 'none') {{
+                detailEl.style.display = 'block';
+                toggleBtn.innerHTML = '详情 ▲';
+            }} else {{
+                detailEl.style.display = 'none';
+                toggleBtn.innerHTML = '详情 ▼';
+            }}
+        }}
+
         // 选择search word - 显示detail area
         // 选择search word - 显示detail area
         function selectSearchWord(featureIdx, baseWordIdx, swIdx) {{
         function selectSearchWord(featureIdx, baseWordIdx, swIdx) {{
             selectedSearchWordIdx = swIdx;
             selectedSearchWordIdx = swIdx;
@@ -3745,48 +3780,127 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
             notes.forEach((note, noteIdx) => {{
             notes.forEach((note, noteIdx) => {{
                 const noteCard = note.note_card || {{}};
                 const noteCard = note.note_card || {{}};
                 const title = noteCard.display_title || '无标题';
                 const title = noteCard.display_title || '无标题';
-                const cover = (noteCard.image_list && noteCard.image_list[0]) || noteCard.cover?.url_default || '';
+                const imageList = noteCard.image_list || [];
+                const cover = imageList[0] || noteCard.cover?.url_default || '';
                 const noteId = note.id || note.note_id || '';
                 const noteId = note.id || note.note_id || '';
                 const type = noteCard.type || 'normal';
                 const type = noteCard.type || 'normal';
-                const typeIcon = type === 'video' ? '🎬' : '📷';
-
-                // 获取评估信息
-                const noteEval = sw.evaluation_with_filter?.notes_with_scores?.find(n => n.note_id === noteId || n.id === noteId);
-                const evalScore = noteEval?.evaluation_score || 0;
-                const matchLevel = noteEval?.match_level || '未评估';
-
+                const typeIcon = type === 'video' ? '🎬' : '🖼️';
+                const user = noteCard.user || {{}};
+                const userName = user.nickname || '未知用户';
+                const userAvatar = user.avatar || '';
+
+                // 从notes_evaluation获取评估信息(通过note_index匹配)
+                const notesEvaluation = sw.evaluation_with_filter?.notes_evaluation || [];
+                const noteEval = notesEvaluation[noteIdx] || {{}};
+
+                const queryRelevance = noteEval['Query相关性'] || '未评估';
+                const comprehensiveScore = noteEval['综合得分'] || 0;
+                const matchType = noteEval['匹配类型'] || '未评估';
+                const explanation = noteEval['说明'] || noteEval['评分说明'] || '';
+                const firstLayerEval = noteEval['第一层评估'] || {{}};
+
+                // 根据匹配类型确定样式
                 let matchClass = '';
                 let matchClass = '';
                 let matchColor = '';
                 let matchColor = '';
-                if (matchLevel.includes('完全匹配')) {{
+                let matchIcon = '';
+                if (matchType.includes('完全匹配')) {{
                     matchClass = 'match-complete';
                     matchClass = 'match-complete';
                     matchColor = '#22c55e';
                     matchColor = '#22c55e';
-                }} else if (matchLevel.includes('相似匹配')) {{
+                    matchIcon = '🟢';
+                }} else if (matchType.includes('相似匹配')) {{
                     matchClass = 'match-similar';
                     matchClass = 'match-similar';
                     matchColor = '#f59e0b';
                     matchColor = '#f59e0b';
-                }} else if (matchLevel.includes('弱相似')) {{
+                    matchIcon = '🟡';
+                }} else if (matchType.includes('弱相似')) {{
                     matchClass = 'match-weak';
                     matchClass = 'match-weak';
                     matchColor = '#f97316';
                     matchColor = '#f97316';
-                }} else if (matchLevel.includes('无匹配')) {{
+                    matchIcon = '🟠';
+                }} else if (matchType.includes('无匹配')) {{
                     matchClass = 'match-none';
                     matchClass = 'match-none';
                     matchColor = '#dc2626';
                     matchColor = '#dc2626';
-                }} else {{
+                    matchIcon = '🔴';
+                }} else if (matchType.includes('过滤')) {{
                     matchClass = 'match-filtered';
                     matchClass = 'match-filtered';
                     matchColor = '#6b7280';
                     matchColor = '#6b7280';
+                    matchIcon = '⚫';
+                }} else {{
+                    matchClass = 'match-unknown';
+                    matchColor = '#9ca3af';
+                    matchIcon = '⚪';
                 }}
                 }}
 
 
+                const evalDetailId = `eval-detail-${{featureIdx}}-${{baseWordIdx}}-${{swIdx}}-${{noteIdx}}`;
+
+                // 检查是否有解构数据
+                const hasDeconstruction = deconstructionData[noteId] != null;
+
                 html += `
                 html += `
-                    <div class="note-card ${{matchClass}}" style="border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;background:white;cursor:pointer;transition:all 0.2s;border-left:4px solid ${{matchColor}};"
-                         onclick="openDeconstructionModal('${{noteId}}')">
-                        ${{cover ? `<img src="${{cover}}" style="width:100%;height:180px;object-fit:cover;">` : `<div style="width:100%;height:180px;background:#f3f4f6;display:flex;align-items:center;justify-content:center;color:#9ca3af;">${{typeIcon}}</div>`}}
+                    <div class="note-card ${{matchClass}}" style="border:2px solid #fbbf24;border-radius:12px;overflow:hidden;background:white;transition:all 0.2s;">
+                        <!-- 图片轮播区域 -->
+                        <div style="position:relative;width:100%;height:200px;background:#f3f4f6;">
+                            ${{cover ? `<img src="${{cover}}" style="width:100%;height:100%;object-fit:cover;">` : `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:#9ca3af;">${{typeIcon}}</div>`}}
+                            <div style="position:absolute;top:10px;right:10px;background:rgba(0,0,0,0.6);color:white;padding:4px 10px;border-radius:20px;font-size:12px;font-weight:600;">
+                                1/${{imageList.length || 1}}
+                            </div>
+                        </div>
+
+                        <!-- 内容区域 -->
                         <div style="padding:12px;">
                         <div style="padding:12px;">
-                            <div style="font-size:14px;font-weight:600;margin-bottom:8px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;">
+                            <!-- 标题 -->
+                            <div style="font-size:14px;font-weight:600;margin-bottom:8px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;color:#1f2937;">
                                 ${{title}}
                                 ${{title}}
                             </div>
                             </div>
-                            <div style="font-size:11px;color:#6b7280;display:flex;justify-content:space-between;align-items:center;">
-                                <span>${{typeIcon}} ${{type}}</span>
-                                <span style="color:${{matchColor}};font-weight:600;">${{evalScore.toFixed(2)}}</span>
+
+                            <!-- 类型标签 -->
+                            <div style="margin-bottom:8px;">
+                                <span style="display:inline-block;padding:2px 8px;background:#ecfdf5;color:#059669;font-size:11px;border-radius:4px;">
+                                    ${{typeIcon}} 图文
+                                </span>
+                            </div>
+
+                            <!-- 作者信息 -->
+                            <div style="display:flex;align-items:center;margin-bottom:10px;">
+                                ${{userAvatar ? `<img src="${{userAvatar}}" style="width:20px;height:20px;border-radius:50%;margin-right:6px;">` : '<div style="width:20px;height:20px;border-radius:50%;background:#e5e7eb;margin-right:6px;"></div>'}}
+                                <span style="font-size:11px;color:#6b7280;">${{userName}}</span>
+                            </div>
+
+                            <!-- 评分徽章 -->
+                            <div style="display:flex;align-items:center;justify-content:space-between;padding:8px;background:#f9fafb;border-radius:6px;margin-bottom:8px;">
+                                <span style="font-size:12px;color:${{matchColor}};font-weight:600;">
+                                    ${{matchIcon}} ${{matchType}} (${{comprehensiveScore.toFixed(1)}}分)
+                                </span>
+                                <button onclick="event.stopPropagation(); toggleEvalDetail('${{evalDetailId}}')"
+                                        style="font-size:11px;color:#667eea;background:none;border:none;cursor:pointer;padding:2px 6px;">
+                                    详情 ▼
+                                </button>
+                            </div>
+
+                            <!-- 评估详情(可展开) -->
+                            <div id="${{evalDetailId}}" style="display:none;font-size:11px;color:#6b7280;padding:8px;background:#fef3c7;border-radius:6px;margin-top:8px;max-height:150px;overflow-y:auto;">
+                                <div style="margin-bottom:6px;">
+                                    <strong style="color:#92400e;">Query相关性:</strong> ${{queryRelevance}}
+                                </div>
+                                ${{explanation ? `
+                                    <div style="margin-bottom:6px;">
+                                        <strong style="color:#92400e;">说明:</strong> ${{explanation}}
+                                    </div>
+                                ` : ''}}
+                                ${{firstLayerEval['说明'] ? `
+                                    <div>
+                                        <strong style="color:#92400e;">第一层评估:</strong> ${{firstLayerEval['说明']}}
+                                    </div>
+                                ` : ''}}
                             </div>
                             </div>
                         </div>
                         </div>
+
+                        <!-- 解构按钮(仅当有解构数据时显示) -->
+                        ${{hasDeconstruction ? `
+                            <button class="deconstruction-toggle-btn"
+                                    onclick="event.stopPropagation(); openDeconstructionModal('${{noteId}}', '${{title.replace(/'/g, "\\'")}}')"
+                                    style="width:100%;padding:10px;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:white;border:none;border-top:1px solid #e5e7eb;cursor:pointer;font-size:13px;font-weight:600;transition:all 0.3s;display:flex;align-items:center;justify-content:center;gap:6px;">
+                                🎯 查看解构
+                            </button>
+                        ` : ''}}
                     </div>
                     </div>
                 `;
                 `;
             }});
             }});
@@ -4042,7 +4156,10 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
 
 
         // 打开解构模态窗口
         // 打开解构模态窗口
         function openDeconstructionModal(noteId, noteTitle) {{
         function openDeconstructionModal(noteId, noteTitle) {{
-            console.log('🔧 [调试] openDeconstructionModal被调用, noteId:', noteId);
+            console.log('🔧 [调试] ========== openDeconstructionModal 开始 ==========');
+            console.log('🔧 [调试] noteId:', noteId);
+            console.log('🔧 [调试] noteTitle:', noteTitle);
+            console.log('🔧 [调试] noteId类型:', typeof noteId);
 
 
             const modal = document.getElementById('deconstructionModal');
             const modal = document.getElementById('deconstructionModal');
             const modalContent = document.getElementById('modalContent');
             const modalContent = document.getElementById('modalContent');
@@ -4056,21 +4173,45 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
             // 设置标题
             // 设置标题
             modalNoteTitle.textContent = noteTitle || '解构分析';
             modalNoteTitle.textContent = noteTitle || '解构分析';
 
 
-            // 检查是否有数据
+            // 详细检查数据
+            console.log('📊 [调试] similarityData对象类型:', typeof similarityData);
+            console.log('📊 [调试] similarityData的keys数量:', Object.keys(similarityData).length);
+            console.log('📊 [调试] 前5个可用的noteId:', Object.keys(similarityData).slice(0, 5));
+
             const hasSimilarityData = !!similarityData[noteId];
             const hasSimilarityData = !!similarityData[noteId];
-            console.log('📊 [调试] 相似度数据存在:', hasSimilarityData);
+            console.log('📊 [调试] similarityData[noteId]存在:', hasSimilarityData);
+
+            if (hasSimilarityData) {{
+                const data = similarityData[noteId];
+                console.log('📊 [调试] similarityData[noteId]内容:', data);
+                console.log('📊 [调试] deconstructed_features字段存在:', 'deconstructed_features' in data);
+                if ('deconstructed_features' in data) {{
+                    console.log('📊 [调试] deconstructed_features长度:', data.deconstructed_features ? data.deconstructed_features.length : 'null/undefined');
+                }}
+            }}
+
+            // 同时检查deconstructionData
+            const hasDeconstructionData = !!deconstructionData[noteId];
+            console.log('📊 [调试] deconstructionData[noteId]存在:', hasDeconstructionData);
+            if (hasDeconstructionData) {{
+                console.log('📊 [调试] deconstructionData[noteId]:', deconstructionData[noteId]);
+            }}
 
 
             if (!hasSimilarityData) {{
             if (!hasSimilarityData) {{
                 console.warn('⚠️ [警告] 未找到相似度数据, noteId:', noteId);
                 console.warn('⚠️ [警告] 未找到相似度数据, noteId:', noteId);
-                console.log('📋 [调试] 可用的noteId列表:', Object.keys(similarityData));
-                modalContent.innerHTML = '<div style="padding: 30px; text-align: center; color: #6b7280;">暂无解构数据</div>';
+                console.log('📋 [调试] 完整的可用noteId列表:', Object.keys(similarityData));
+                modalContent.innerHTML = '<div style="padding: 30px; text-align: center; color: #6b7280;">暂无解构数据<br><small>noteId: ' + noteId + '</small></div>';
             }} else {{
             }} else {{
                 try {{
                 try {{
-                    modalContent.innerHTML = renderDeconstructionContent(noteId);
+                    console.log('🎨 [调试] 开始渲染解构内容...');
+                    const content = renderDeconstructionContent(noteId);
+                    console.log('🎨 [调试] 渲染返回的HTML长度:', content.length);
+                    modalContent.innerHTML = content;
                     console.log('✅ [调试] 解构内容渲染成功');
                     console.log('✅ [调试] 解构内容渲染成功');
                 }} catch (error) {{
                 }} catch (error) {{
                     console.error('❌ [错误] 渲染解构内容失败:', error);
                     console.error('❌ [错误] 渲染解构内容失败:', error);
-                    modalContent.innerHTML = `<div style="padding: 30px; text-align: center; color: red;">渲染错误: ${{error.message}}</div>`;
+                    console.error('❌ [错误] 错误堆栈:', error.stack);
+                    modalContent.innerHTML = `<div style="padding: 30px; text-align: center; color: red;">渲染错误: ${{error.message}}<br><pre style="text-align:left;font-size:10px;">${{error.stack}}</pre></div>`;
                 }}
                 }}
             }}
             }}
 
 
@@ -4078,6 +4219,7 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
             modal.classList.add('active');
             modal.classList.add('active');
             document.body.style.overflow = 'hidden'; // 禁止背景滚动
             document.body.style.overflow = 'hidden'; // 禁止背景滚动
             console.log('✅ [调试] 模态窗口已显示');
             console.log('✅ [调试] 模态窗口已显示');
+            console.log('🔧 [调试] ========== openDeconstructionModal 结束 ==========');
         }}
         }}
 
 
         // 关闭模态窗口
         // 关闭模态窗口
@@ -4265,27 +4407,46 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
 
 
         // 渲染解构内容
         // 渲染解构内容
         function renderDeconstructionContent(noteId) {{
         function renderDeconstructionContent(noteId) {{
+            console.log('🎨 [renderDeconstructionContent] ========== 开始 ==========');
+            console.log('🎨 [renderDeconstructionContent] noteId:', noteId);
+            console.log('🎨 [renderDeconstructionContent] noteId类型:', typeof noteId);
+
             const similarityInfo = similarityData[noteId];
             const similarityInfo = similarityData[noteId];
+            console.log('🎨 [renderDeconstructionContent] similarityInfo:', similarityInfo);
+            console.log('🎨 [renderDeconstructionContent] similarityInfo是否为null/undefined:', similarityInfo == null);
+
             if (!similarityInfo) {{
             if (!similarityInfo) {{
+                console.warn('⚠️ [renderDeconstructionContent] similarityInfo不存在');
                 return `<div style="padding: 30px; text-align: center; color: #6b7280;">
                 return `<div style="padding: 30px; text-align: center; color: #6b7280;">
                     <div style="font-size: 48px; margin-bottom: 10px;">📭</div>
                     <div style="font-size: 48px; margin-bottom: 10px;">📭</div>
                     <div style="font-size: 16px; margin-bottom: 8px;">暂无解构数据</div>
                     <div style="font-size: 16px; margin-bottom: 8px;">暂无解构数据</div>
                     <div style="font-size: 14px; opacity: 0.7;">该帖子未进行特征解构分析</div>
                     <div style="font-size: 14px; opacity: 0.7;">该帖子未进行特征解构分析</div>
+                    <div style="font-size: 12px; opacity: 0.5; margin-top: 8px;">noteId: ${{noteId}}</div>
                 </div>`;
                 </div>`;
             }}
             }}
 
 
             const originalFeature = similarityInfo.original_feature || '未知特征';
             const originalFeature = similarityInfo.original_feature || '未知特征';
+            console.log('🎨 [renderDeconstructionContent] originalFeature:', originalFeature);
+
             const features = similarityInfo.deconstructed_features || [];
             const features = similarityInfo.deconstructed_features || [];
+            console.log('🎨 [renderDeconstructionContent] features:', features);
+            console.log('🎨 [renderDeconstructionContent] features类型:', typeof features);
+            console.log('🎨 [renderDeconstructionContent] features.length:', features.length);
+            console.log('🎨 [renderDeconstructionContent] features是数组:', Array.isArray(features));
 
 
             if (features.length === 0) {{
             if (features.length === 0) {{
+                console.warn('⚠️ [renderDeconstructionContent] features长度为0');
                 return `<div style="padding: 30px; text-align: center; color: #6b7280;">
                 return `<div style="padding: 30px; text-align: center; color: #6b7280;">
                     <div style="font-size: 48px; margin-bottom: 10px;">🔍</div>
                     <div style="font-size: 48px; margin-bottom: 10px;">🔍</div>
                     <div style="font-size: 16px; margin-bottom: 8px;">未提取到解构特征</div>
                     <div style="font-size: 16px; margin-bottom: 8px;">未提取到解构特征</div>
                     <div style="font-size: 14px; opacity: 0.7;">原始特征:"${{originalFeature}}"</div>
                     <div style="font-size: 14px; opacity: 0.7;">原始特征:"${{originalFeature}}"</div>
                     <div style="font-size: 13px; opacity: 0.6; margin-top: 10px;">该帖子虽然评分较高,但AI未能从中提取到有效的解构特征</div>
                     <div style="font-size: 13px; opacity: 0.6; margin-top: 10px;">该帖子虽然评分较高,但AI未能从中提取到有效的解构特征</div>
+                    <div style="font-size: 12px; opacity: 0.5; margin-top: 8px;">noteId: ${{noteId}}</div>
                 </div>`;
                 </div>`;
             }}
             }}
 
 
+            console.log('✅ [renderDeconstructionContent] 有效特征数量:', features.length);
+
             // 按维度分组
             // 按维度分组
             const dimensionGroups = {{}};
             const dimensionGroups = {{}};
             features.forEach(feat => {{
             features.forEach(feat => {{
@@ -4620,7 +4781,25 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
         console.log('📊 [数据] 评估特征数:', data.length);
         console.log('📊 [数据] 评估特征数:', data.length);
         console.log('📊 [数据] 解构结果数:', Object.keys(deconstructionData).length);
         console.log('📊 [数据] 解构结果数:', Object.keys(deconstructionData).length);
         console.log('📊 [数据] 相似度分析数:', Object.keys(similarityData).length);
         console.log('📊 [数据] 相似度分析数:', Object.keys(similarityData).length);
-        console.log('📋 [数据] 相似度分析可用noteId:', Object.keys(similarityData));
+        console.log('📋 [数据] 解构数据前5个noteId:', Object.keys(deconstructionData).slice(0, 5));
+        console.log('📋 [数据] 相似度分析前5个noteId:', Object.keys(similarityData).slice(0, 5));
+
+        // 测试特定noteId
+        const testNoteId = '6662ada60000000015012f7d';
+        console.log('🧪 [测试] 检查特定noteId:', testNoteId);
+        console.log('🧪 [测试] 在deconstructionData中:', testNoteId in deconstructionData);
+        console.log('🧪 [测试] 在similarityData中:', testNoteId in similarityData);
+        if (testNoteId in similarityData) {{
+            const testData = similarityData[testNoteId];
+            console.log('🧪 [测试] similarityData内容:', testData);
+            console.log('🧪 [测试] deconstructed_features存在:', 'deconstructed_features' in testData);
+            if ('deconstructed_features' in testData) {{
+                console.log('🧪 [测试] deconstructed_features长度:', testData.deconstructed_features ? testData.deconstructed_features.length : 'null/undefined');
+                if (testData.deconstructed_features && testData.deconstructed_features.length > 0) {{
+                    console.log('🧪 [测试] 第一个特征:', testData.deconstructed_features[0]);
+                }}
+            }}
+        }}
         console.log('='.repeat(60));
         console.log('='.repeat(60));
 
 
         // 初始化
         // 初始化
@@ -4628,6 +4807,11 @@ def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
             console.log('✅ [系统] DOM加载完成,开始初始化...');
             console.log('✅ [系统] DOM加载完成,开始初始化...');
 
 
             try {{
             try {{
+                // 渲染候选词库(移到顶部独立区域)
+                const allCandidates = extractAllCandidates();
+                document.getElementById('candidateLibraryContainer').innerHTML = renderCandidateLibrary(allCandidates);
+                console.log('✅ [系统] 候选词库渲染完成');
+
                 renderLeftSidebar();
                 renderLeftSidebar();
                 console.log('✅ [系统] 左侧导航(级联模式)渲染完成');
                 console.log('✅ [系统] 左侧导航(级联模式)渲染完成');