feat: Migrate to React Flow and add Fixed + Dynamic category mode

Frontend:
- Migrate MindmapDAG from D3.js to React Flow (@xyflow/react)
- Add custom node components (QueryNode, CategoryHeaderNode, AttributeNode)
- Add useDAGLayout hook for column-based layout
- Add "AI" badge for LLM-suggested categories
- Update CategorySelector with Fixed + Dynamic mode option
- Improve dark/light theme support

Backend:
- Add FIXED_PLUS_DYNAMIC category mode
- Filter duplicate category names in LLM suggestions
- Update prompts to exclude fixed categories when suggesting new ones
- Improve LLM service with better error handling and logging
- Auto-remove /no_think prefix for non-Qwen models
- Add smart JSON format detection for model compatibility
- Improve JSON extraction with multiple parsing strategies

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-03 01:22:57 +08:00
parent 91f7f41bc1
commit 1ed1dab78f
21 changed files with 1254 additions and 614 deletions

View File

@@ -120,17 +120,26 @@ def get_flat_attribute_prompt(query: str, categories: Optional[List[str]] = None
# ===== Dynamic category system prompts =====
def get_step0_category_analysis_prompt(query: str, suggested_count: int = 3) -> str:
def get_step0_category_analysis_prompt(
query: str,
suggested_count: int = 3,
exclude_categories: List[str] | None = None
) -> str:
"""Step 0: LLM 分析建議類別"""
exclude_text = ""
if exclude_categories:
exclude_text = f"\n【禁止使用的類別】{', '.join(exclude_categories)}(這些已經是固定類別,不要重複建議)\n"
return f"""/no_think
分析「{query}」,建議 {suggested_count} 個最適合的屬性類別來描述它。
【常見類別參考】材料、功能、用途、使用族群、特性、形狀、顏色、尺寸、品牌、價格區間
【常見類別參考】特性、形狀、顏色、尺寸、品牌、價格區間、重量、風格、場合、季節、技術規格
{exclude_text}
【重要】
1. 選擇最能描述此物件本質的類別
2. 類別之間應該有邏輯關係(如:材料→功能→用途)
2. 類別之間應該有邏輯關係
3. 不要選擇過於抽象或重複的類別
4. 必須建議與參考列表不同的、有創意的類別
只回傳 JSON
{{
@@ -213,3 +222,47 @@ def get_step2_dynamic_causal_chain_prompt(
只回傳 JSON
{json.dumps(json_template, ensure_ascii=False, indent=2)}"""
# ===== DAG relationship prompt =====
def get_step2_dag_relationships_prompt(
query: str,
categories: List, # List[CategoryDefinition]
attributes_by_category: Dict[str, List[str]],
) -> str:
"""生成相鄰類別之間的自然關係"""
sorted_cats = sorted(categories, key=lambda x: x.order if hasattr(x, 'order') else x.get('order', 0))
# Build attribute listing
attr_listing = "\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
])
# Build direction hints
direction_hints = "".join([cat.name if hasattr(cat, 'name') else cat['name'] for cat in sorted_cats])
return f"""/no_think
分析「{query}」的屬性關係。
{attr_listing}
【關係方向】{direction_hints}
【規則】
1. 只建立相鄰類別之間的關係(例如:材料→功能,功能→用途)
2. 只輸出真正有因果或關聯關係的配對
3. 一個屬性可連接多個下游屬性,也可以不連接任何屬性
4. 不需要每個屬性都有連接
5. 關係應該合理且有意義
回傳 JSON
{{
"relationships": [
{{"source_category": "類別A", "source": "屬性名", "target_category": "類別B", "target": "屬性名"}},
...
]
}}
只回傳 JSON。"""