feat: Add dynamic category system for attribute analysis

Backend:
- Add CategoryMode enum with 4 modes (fixed_only, fixed_plus_custom, custom_only, dynamic_auto)
- Add Step 0 for LLM category analysis before attribute generation
- Implement dynamic prompts for Step 1/2 that work with N categories
- Add execute_step0(), resolve_final_categories(), assemble_dynamic_attribute_tree()
- Update SSE events to include step0_start, step0_complete, categories_resolved

Frontend:
- Add CategorySelector component with mode selection, custom category input, and category count slider
- Update types with CategoryDefinition, Step0Result, DynamicStep1Result, DynamicCausalChain
- Update api.ts with new SSE event handlers
- Update useAttribute hook with category parameters
- Integrate CategorySelector into InputPanel
- Fix mindmap to dynamically extract and display N categories (was hardcoded to 4)
- Add CSS styles for depth 5-8 to support more category levels

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-02 23:04:35 +08:00
parent eb6c0c51fa
commit 91f7f41bc1
11 changed files with 727 additions and 53 deletions

View File

@@ -1,4 +1,5 @@
from typing import List, Optional
from typing import List, Optional, Dict
import json
DEFAULT_CATEGORIES = ["材料", "功能", "用途", "使用族群", "特性"]
@@ -115,3 +116,100 @@ def get_flat_attribute_prompt(query: str, categories: Optional[List[str]] = None
用戶輸入:{query}"""
return prompt
# ===== Dynamic category system prompts =====
def get_step0_category_analysis_prompt(query: str, suggested_count: int = 3) -> str:
"""Step 0: LLM 分析建議類別"""
return f"""/no_think
分析「{query}」,建議 {suggested_count} 個最適合的屬性類別來描述它。
【常見類別參考】材料、功能、用途、使用族群、特性、形狀、顏色、尺寸、品牌、價格區間
【重要】
1. 選擇最能描述此物件本質的類別
2. 類別之間應該有邏輯關係(如:材料→功能→用途)
3. 不要選擇過於抽象或重複的類別
只回傳 JSON
{{
"categories": [
{{"name": "類別1", "description": "說明1", "order": 0}},
{{"name": "類別2", "description": "說明2", "order": 1}}
]
}}
物件:{query}"""
def get_step1_dynamic_attributes_prompt(
query: str,
categories: List # List[CategoryDefinition]
) -> str:
"""動態 Step 1 - 根據類別列表生成屬性"""
# 按 order 排序並構建描述
sorted_cats = sorted(categories, key=lambda x: x.order if hasattr(x, 'order') else x.get('order', 0))
category_desc = "\n".join([
f"- {cat.name if hasattr(cat, 'name') else cat['name']}: {cat.description if hasattr(cat, 'description') else cat.get('description', '相關屬性')}"
for cat in sorted_cats
])
category_keys = [cat.name if hasattr(cat, 'name') else cat['name'] for cat in sorted_cats]
json_template = {cat: ["屬性1", "屬性2", "屬性3"] for cat in category_keys}
return f"""/no_think
分析「{query}」,列出以下類別的屬性。每個類別列出 3-5 個常見屬性。
【類別列表】
{category_desc}
只回傳 JSON
{json.dumps(json_template, ensure_ascii=False, indent=2)}
物件:{query}"""
def get_step2_dynamic_causal_chain_prompt(
query: str,
categories: List, # List[CategoryDefinition]
attributes_by_category: Dict[str, List[str]],
existing_chains: List[Dict[str, str]],
chain_index: int
) -> str:
"""動態 Step 2 - 生成動態類別的因果鏈"""
sorted_cats = sorted(categories, key=lambda x: x.order if hasattr(x, 'order') else x.get('order', 0))
# 構建可選屬性
available_attrs = "\n".join([
f"{cat.name if hasattr(cat, 'name') else cat['name']}{', '.join(attributes_by_category.get(cat.name if hasattr(cat, 'name') else cat['name'], []))}"
for cat in sorted_cats
])
# 已生成的因果鏈
existing_text = ""
if existing_chains:
chains_list = [
"".join([chain.get(cat.name if hasattr(cat, 'name') else cat['name'], '?') for cat in sorted_cats])
for chain in existing_chains
]
existing_text = f"\n【已生成,請勿重複】\n" + "\n".join([f"- {c}" for c in chains_list])
# JSON 模板
json_template = {cat.name if hasattr(cat, 'name') else cat['name']: f"選擇的{cat.name if hasattr(cat, 'name') else cat['name']}" for cat in sorted_cats}
return f"""/no_think
為「{query}」生成第 {chain_index} 條因果鏈。
【可選屬性】
{available_attrs}
{existing_text}
【規則】
1. 從每個類別選擇一個屬性
2. 因果關係必須合理
3. 不要重複
只回傳 JSON
{json.dumps(json_template, ensure_ascii=False, indent=2)}"""