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,10 +1,11 @@
import { useState, useRef, useCallback } from 'react';
import { ConfigProvider, Layout, theme, Typography } from 'antd';
import { ConfigProvider, Layout, theme, Typography, Space } from 'antd';
import { ApartmentOutlined } from '@ant-design/icons';
import { ThemeToggle } from './components/ThemeToggle';
import { InputPanel } from './components/InputPanel';
import { MindmapPanel } from './components/MindmapPanel';
import { useAttribute } from './hooks/useAttribute';
import type { MindmapD3Ref } from './components/MindmapD3';
import type { MindmapDAGRef } from './components/MindmapDAG';
import type { CategoryMode } from './types';
const { Header, Sider, Content } = Layout;
@@ -22,7 +23,7 @@ function App() {
nodeSpacing: 32,
fontSize: 14,
});
const mindmapRef = useRef<MindmapD3Ref>(null);
const mindmapRef = useRef<MindmapDAGRef>(null);
const handleAnalyze = async (
query: string,
@@ -36,12 +37,8 @@ function App() {
await analyze(query, model, temperature, chainCount, categoryMode, customCategories, suggestedCategoryCount);
};
const handleExpandAll = useCallback(() => {
mindmapRef.current?.expandAll();
}, []);
const handleCollapseAll = useCallback(() => {
mindmapRef.current?.collapseAll();
const handleResetView = useCallback(() => {
mindmapRef.current?.resetView();
}, []);
return (
@@ -57,11 +54,37 @@ function App() {
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 24px',
background: isDark
? 'linear-gradient(90deg, #141414 0%, #1f1f1f 50%, #141414 100%)'
: 'linear-gradient(90deg, #fff 0%, #fafafa 50%, #fff 100%)',
borderBottom: isDark ? '1px solid #303030' : '1px solid #f0f0f0',
boxShadow: isDark
? '0 2px 8px rgba(0, 0, 0, 0.3)'
: '0 2px 8px rgba(0, 0, 0, 0.06)',
}}
>
<Title level={4} style={{ margin: 0, color: isDark ? '#fff' : '#000' }}>
Attribute Agent
</Title>
<Space align="center" size="middle">
<ApartmentOutlined
style={{
fontSize: 24,
color: isDark ? '#177ddc' : '#1890ff',
}}
/>
<Title
level={4}
style={{
margin: 0,
background: isDark
? 'linear-gradient(90deg, #177ddc 0%, #69c0ff 100%)'
: 'linear-gradient(90deg, #1890ff 0%, #40a9ff 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text',
}}
>
Attribute Agent
</Title>
</Space>
<ThemeToggle isDark={isDark} onToggle={setIsDark} />
</Header>
<Layout>
@@ -96,8 +119,7 @@ function App() {
currentResult={currentResult}
onAnalyze={handleAnalyze}
onLoadHistory={loadFromHistory}
onExpandAll={handleExpandAll}
onCollapseAll={handleCollapseAll}
onResetView={handleResetView}
visualSettings={visualSettings}
onVisualSettingsChange={setVisualSettings}
/>