sync: Phase 3 Agent Features from private repo

Features synced:
- LucideIconPicker component (115 icons for dimension customization)
- Dimension editing modal (descriptions + icons)
- Compound filters (per-column AND filters)
- Improved dimension assignment with keyword tagging
- queryDimensions and getDimension agent tools

New files:
- src/components/common/LucideIconPicker.tsx
- src/tools/database/queryDimensions.ts
- src/tools/database/getDimension.ts

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
“BeeRad”
2025-12-25 10:05:02 +11:00
co-authored by Claude Opus 4.5
parent 57a20899a5
commit c9d85998c1
8 changed files with 1630 additions and 174 deletions
+418
View File
@@ -0,0 +1,418 @@
"use client";
import { useState, useMemo } from 'react';
import {
// Core
Folder,
BookOpen,
Lightbulb,
Star,
Heart,
Briefcase,
Code,
FileText,
Globe,
Hash,
Inbox,
Layers,
Link,
MessageSquare,
Music,
Paperclip,
Search,
Settings,
Tag,
User,
Video,
Zap,
Archive,
Award,
Bell,
Bookmark,
Calendar,
Camera,
Clock,
Cloud,
Coffee,
Compass,
Database,
Flag,
Gift,
Home,
Image,
Key,
Mail,
Map,
Mic,
Package,
Smile,
Target,
// Thinking & Ideas
Brain,
Sparkles,
Wand2,
FlaskConical,
Atom,
// Content & Media
Newspaper,
Podcast,
Youtube,
Twitter,
Rss,
Radio,
Tv,
Film,
// Work & Business
Rocket,
TrendingUp,
PieChart,
Presentation,
LineChart,
BarChart3,
CircleDollarSign,
Wallet,
// People & Social
Users,
UserCircle,
Contact,
MessagesSquare,
Share2,
// Nature & Environment
Leaf,
Sun,
Moon,
Mountain,
Trees,
Flower2,
// Objects & Tools
Wrench,
Hammer,
Paintbrush,
Pencil,
Scissors,
Ruler,
// Tech & Development
Terminal,
GitBranch,
Bug,
Cpu,
Server,
Wifi,
Smartphone,
Laptop,
// Documents & Files
FileCode,
FilePlus,
FolderOpen,
ClipboardList,
ScrollText,
StickyNote,
// Health & Wellness
HeartPulse,
Activity,
Dumbbell,
Apple,
// Navigation & Location
Navigation,
MapPin,
Route,
Signpost,
// Misc Popular
Crown,
Diamond,
Gem,
Shield,
Lock,
Eye,
ThumbsUp,
CheckCircle,
AlertCircle,
Info,
HelpCircle,
Play,
Pause,
SkipForward,
type LucideIcon
} from 'lucide-react';
// Static icon registry - no dynamic imports, works perfectly in Tauri WebView
export const ICON_REGISTRY: Record<string, LucideIcon> = {
// Core
Folder,
BookOpen,
Lightbulb,
Star,
Heart,
Briefcase,
Code,
FileText,
Globe,
Hash,
Inbox,
Layers,
Link,
MessageSquare,
Music,
Paperclip,
Search,
Settings,
Tag,
User,
Video,
Zap,
Archive,
Award,
Bell,
Bookmark,
Calendar,
Camera,
Clock,
Cloud,
Coffee,
Compass,
Database,
Flag,
Gift,
Home,
Image,
Key,
Mail,
Map,
Mic,
Package,
Smile,
Target,
// Thinking & Ideas
Brain,
Sparkles,
Wand2,
FlaskConical,
Atom,
// Content & Media
Newspaper,
Podcast,
Youtube,
Twitter,
Rss,
Radio,
Tv,
Film,
// Work & Business
Rocket,
TrendingUp,
PieChart,
Presentation,
LineChart,
BarChart3,
CircleDollarSign,
Wallet,
// People & Social
Users,
UserCircle,
Contact,
MessagesSquare,
Share2,
// Nature & Environment
Leaf,
Sun,
Moon,
Mountain,
Trees,
Flower2,
// Objects & Tools
Wrench,
Hammer,
Paintbrush,
Pencil,
Scissors,
Ruler,
// Tech & Development
Terminal,
GitBranch,
Bug,
Cpu,
Server,
Wifi,
Smartphone,
Laptop,
// Documents & Files
FileCode,
FilePlus,
FolderOpen,
ClipboardList,
ScrollText,
StickyNote,
// Health & Wellness
HeartPulse,
Activity,
Dumbbell,
Apple,
// Navigation & Location
Navigation,
MapPin,
Route,
Signpost,
// Misc Popular
Crown,
Diamond,
Gem,
Shield,
Lock,
Eye,
ThumbsUp,
CheckCircle,
AlertCircle,
Info,
HelpCircle,
Play,
Pause,
SkipForward,
};
// Helper to get icon component by name
export function getIconByName(name: string): LucideIcon {
return ICON_REGISTRY[name] || Folder;
}
// Render icon by name - useful for dynamic rendering
export function DynamicIcon({
name,
size = 16,
className,
style
}: {
name: string;
size?: number;
className?: string;
style?: React.CSSProperties;
}) {
const IconComponent = getIconByName(name);
return <IconComponent size={size} className={className} style={style} />;
}
interface LucideIconPickerProps {
selectedIcon: string;
onSelect: (iconName: string) => void;
onClose?: () => void;
}
export default function LucideIconPicker({ selectedIcon, onSelect, onClose }: LucideIconPickerProps) {
const [searchQuery, setSearchQuery] = useState('');
const iconNames = Object.keys(ICON_REGISTRY);
const filteredIcons = useMemo(() => {
if (!searchQuery.trim()) return iconNames;
const query = searchQuery.toLowerCase();
return iconNames.filter(name => name.toLowerCase().includes(query));
}, [searchQuery, iconNames]);
return (
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '12px',
maxHeight: '300px'
}}>
{/* Search input */}
<div style={{ position: 'relative' }}>
<Search
size={14}
style={{
position: 'absolute',
left: '10px',
top: '50%',
transform: 'translateY(-50%)',
color: '#666'
}}
/>
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search icons..."
style={{
width: '100%',
padding: '8px 12px 8px 32px',
background: '#111',
border: '1px solid #333',
borderRadius: '6px',
color: '#e5e5e5',
fontSize: '13px',
outline: 'none'
}}
onFocus={(e) => {
e.currentTarget.style.borderColor = '#22c55e';
}}
onBlur={(e) => {
e.currentTarget.style.borderColor = '#333';
}}
/>
</div>
{/* Icon grid */}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(8, 1fr)',
gap: '4px',
overflowY: 'auto',
maxHeight: '240px',
padding: '4px'
}}>
{filteredIcons.map((iconName) => {
const IconComponent = ICON_REGISTRY[iconName];
const isSelected = selectedIcon === iconName;
return (
<button
key={iconName}
onClick={() => {
onSelect(iconName);
onClose?.();
}}
title={iconName}
style={{
width: '36px',
height: '36px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: isSelected ? 'rgba(34, 197, 94, 0.15)' : 'transparent',
border: isSelected ? '1px solid #22c55e' : '1px solid transparent',
borderRadius: '6px',
cursor: 'pointer',
color: isSelected ? '#22c55e' : '#888',
transition: 'all 0.15s ease'
}}
onMouseEnter={(e) => {
if (!isSelected) {
e.currentTarget.style.background = '#1a1a1a';
e.currentTarget.style.color = '#e5e5e5';
}
}}
onMouseLeave={(e) => {
if (!isSelected) {
e.currentTarget.style.background = 'transparent';
e.currentTarget.style.color = '#888';
}
}}
>
<IconComponent size={18} />
</button>
);
})}
</div>
{filteredIcons.length === 0 && (
<div style={{
padding: '20px',
textAlign: 'center',
color: '#666',
fontSize: '13px'
}}>
No icons match "{searchQuery}"
</div>
)}
</div>
);
}
File diff suppressed because it is too large Load Diff
+12 -1
View File
@@ -7,6 +7,8 @@ import AddNodeButton from './AddNodeButton';
import Chip from '../common/Chip';
import { getNodeIcon } from '@/utils/nodeIcons';
import SearchModal from './SearchModal';
import { DynamicIcon } from '../common/LucideIconPicker';
import { usePersistentState } from '@/hooks/usePersistentState';
interface NodesPanelProps {
selectedNodes: Set<number>;
@@ -47,6 +49,9 @@ export default function NodesPanel({ selectedNodes, onNodeSelect, onNodeCreated,
const [dragHoverDimension, setDragHoverDimension] = useState<string | null>(null);
const draggedNodeRef = useRef<{ id: number; dimensions?: string[] } | null>(null);
// Dimension icons (shared with FolderViewOverlay via localStorage)
const [dimensionIcons] = usePersistentState<Record<string, string>>('ui.dimensionIcons', {});
useEffect(() => {
fetchNodes();
fetchLockedDimensions();
@@ -774,7 +779,13 @@ export default function NodesPanel({ selectedNodes, onNodeSelect, onNodeCreated,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', flex: 1 }}>
{isExpanded ? (
{dimensionIcons[lockedDim.dimension] ? (
<DynamicIcon
name={dimensionIcons[lockedDim.dimension]}
size={16}
style={{ color: isExpanded ? '#7de8a5' : '#64748b' }}
/>
) : isExpanded ? (
<FolderOpen size={16} color="#7de8a5" />
) : (
<Folder size={16} color="#64748b" />