feat: add mindmap components from jarvis frontend

- Copied mindmap visualization components (ReactFlow-based interactive graph)
- Added MindmapViewer, ReactFlowEditor, MermaidViewer
- Included all node types: Concept, Task, Idea, Project
- Added controls: NodeCreateModal, ExportButton
- Created mindmap route at /mindmap
- Added useGraphData hook for knowledge graph API
- Copied auth-client and api utilities (dependencies)

Note: Requires better-auth packages to be installed for full compilation
This commit is contained in:
Jason Woltje
2026-01-29 21:45:56 -06:00
parent af8f5df111
commit aa267b56d8
15 changed files with 1758 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
'use client';
import { Handle, Position, NodeProps } from '@xyflow/react';
import { ReactNode } from 'react';
export interface BaseNodeData {
label: string;
content?: string | null;
nodeType: string;
tags?: string[];
domain?: string | null;
[key: string]: unknown;
}
interface BaseNodeProps extends NodeProps {
data: BaseNodeData;
icon: ReactNode;
color: string;
borderStyle?: 'solid' | 'dashed' | 'dotted';
}
export function BaseNode({
data,
selected,
icon,
color,
borderStyle = 'solid',
}: BaseNodeProps) {
return (
<div
className={`
px-4 py-3 rounded-lg shadow-md min-w-[150px] max-w-[250px]
bg-white dark:bg-gray-800
border-2 transition-all duration-200
${selected ? 'ring-2 ring-blue-500 ring-offset-2 dark:ring-offset-gray-900' : ''}
`}
style={{
borderColor: color,
borderStyle,
}}
>
<Handle
type="target"
position={Position.Top}
className="w-3 h-3 !bg-gray-400 dark:!bg-gray-500"
/>
<div className="flex items-start gap-2">
<div
className="flex-shrink-0 w-6 h-6 rounded flex items-center justify-center text-white text-sm"
style={{ backgroundColor: color }}
>
{icon}
</div>
<div className="flex-1 min-w-0">
<div className="font-medium text-gray-900 dark:text-gray-100 truncate">
{data.label}
</div>
{data.content && (
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">
{data.content}
</div>
)}
{data.tags && data.tags.length > 0 && (
<div className="flex flex-wrap gap-1 mt-2">
{data.tags.slice(0, 3).map((tag) => (
<span
key={tag}
className="px-1.5 py-0.5 text-xs rounded bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300"
>
{tag}
</span>
))}
{data.tags.length > 3 && (
<span className="text-xs text-gray-400">+{data.tags.length - 3}</span>
)}
</div>
)}
</div>
</div>
<Handle
type="source"
position={Position.Bottom}
className="w-3 h-3 !bg-gray-400 dark:!bg-gray-500"
/>
</div>
);
}

View File

@@ -0,0 +1,24 @@
'use client';
import { NodeProps } from '@xyflow/react';
import { BaseNode, BaseNodeData } from './BaseNode';
export function ConceptNode(props: NodeProps) {
return (
<BaseNode
{...props}
data={props.data as BaseNodeData}
icon={
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"
/>
</svg>
}
color="#6366f1"
/>
);
}

View File

@@ -0,0 +1,25 @@
'use client';
import { NodeProps } from '@xyflow/react';
import { BaseNode, BaseNodeData } from './BaseNode';
export function IdeaNode(props: NodeProps) {
return (
<BaseNode
{...props}
data={props.data as BaseNodeData}
icon={
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 10V3L4 14h7v7l9-11h-7z"
/>
</svg>
}
color="#f59e0b"
borderStyle="dashed"
/>
);
}

View File

@@ -0,0 +1,24 @@
'use client';
import { NodeProps } from '@xyflow/react';
import { BaseNode, BaseNodeData } from './BaseNode';
export function ProjectNode(props: NodeProps) {
return (
<BaseNode
{...props}
data={props.data as BaseNodeData}
icon={
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
/>
</svg>
}
color="#3b82f6"
/>
);
}

View File

@@ -0,0 +1,24 @@
'use client';
import { NodeProps } from '@xyflow/react';
import { BaseNode, BaseNodeData } from './BaseNode';
export function TaskNode(props: NodeProps) {
return (
<BaseNode
{...props}
data={props.data as BaseNodeData}
icon={
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
/>
</svg>
}
color="#10b981"
/>
);
}