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

@@ -1,13 +1,11 @@
import type {
ModelListResponse,
StreamAnalyzeRequest,
StreamAnalyzeResponse,
Step1Result,
CausalChain,
Step0Result,
CategoryDefinition,
DynamicStep1Result,
DynamicCausalChain
DAGStreamAnalyzeResponse
} from '../types';
// 自動使用當前瀏覽器的 hostname支援遠端存取
@@ -19,10 +17,9 @@ export interface SSECallbacks {
onCategoriesResolved?: (categories: CategoryDefinition[]) => void;
onStep1Start?: () => void;
onStep1Complete?: (result: Step1Result | DynamicStep1Result) => void;
onChainStart?: (index: number, total: number) => void;
onChainComplete?: (index: number, chain: CausalChain | DynamicCausalChain) => void;
onChainError?: (index: number, error: string) => void;
onDone?: (response: StreamAnalyzeResponse) => void;
onRelationshipsStart?: () => void;
onRelationshipsComplete?: (count: number) => void;
onDone?: (response: DAGStreamAnalyzeResponse) => void;
onError?: (error: string) => void;
}
@@ -87,14 +84,11 @@ export async function analyzeAttributesStream(
case 'step1_complete':
callbacks.onStep1Complete?.(eventData.result);
break;
case 'chain_start':
callbacks.onChainStart?.(eventData.index, eventData.total);
case 'relationships_start':
callbacks.onRelationshipsStart?.();
break;
case 'chain_complete':
callbacks.onChainComplete?.(eventData.index, eventData.chain);
break;
case 'chain_error':
callbacks.onChainError?.(eventData.index, eventData.error);
case 'relationships_complete':
callbacks.onRelationshipsComplete?.(eventData.count);
break;
case 'done':
callbacks.onDone?.(eventData);