File size: 6,188 Bytes
4fdaf19 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | import React, { useMemo, useState } from 'react';
import { BrainCircuit, GitBranch, ShieldAlert, Sparkles } from 'lucide-react';
import { buildMindwalkGraph, buildNovaActPrompt, limitationFunctions, MindwalkNode } from '../services/mindwalkAdapter';
import { publishTabEvent } from '../services/tabBus';
interface MindwalkGraphViewProps {
activeTab?: string;
activeView?: string;
}
const stateColor: Record<string, string> = {
unvisited: 'bg-gray-800 border-gray-700 text-gray-400',
seen: 'bg-emerald-500/15 border-emerald-400/30 text-emerald-200',
read: 'bg-sky-500/15 border-sky-300/40 text-sky-100',
edited: 'bg-amber-500/20 border-amber-300/50 text-amber-100 shadow-amber-900/40',
limited: 'bg-rose-500/20 border-rose-300/50 text-rose-100 shadow-rose-950/40',
};
const MindwalkGraphView: React.FC<MindwalkGraphViewProps> = ({ activeTab = 'nova-act', activeView = 'mindwalk' }) => {
const graph = useMemo(() => buildMindwalkGraph(activeTab, activeView), [activeTab, activeView]);
const [selected, setSelected] = useState<MindwalkNode>(graph.nodes.find((node) => node.id === `${activeTab}:${activeView}`) || graph.nodes[0]);
const prompt = buildNovaActPrompt(selected);
const injectLimitations = () => {
fetch('/api/nova-act/limitations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ limitationIds: selected.limitationIds, prompt }),
}).catch((error) => console.error('Failed to inject Nova Act limitations:', error));
publishTabEvent({
source: 'mindwalk',
target: 'nova-act',
action: 'limitations.inject',
payload: {
nodeId: selected.id,
prompt,
limitationIds: selected.limitationIds,
},
});
};
return (
<div className="mx-auto max-w-7xl px-6 py-12 text-white">
<div className="mb-6 rounded-3xl border border-indigo-500/20 bg-gradient-to-br from-indigo-950/40 via-black to-teal-950/30 p-8">
<div className="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
<div>
<div className="mb-4 inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-4 py-2 text-xs font-bold uppercase tracking-[0.24em] text-indigo-200">
<BrainCircuit size={14} /> Mindwalk + OmniParser adapter
</div>
<h2 className="text-4xl font-semibold tracking-tight">Parse UI into LLM language, then steer navigation.</h2>
<p className="mt-3 max-w-3xl text-sm leading-6 text-gray-300">
Mindwalk keeps the navigation graph and touch-state memory; OmniParser is the UI-to-LLM transitioner that converts screen elements into grounded language Nova Act can use for actions and guardrails.
</p>
</div>
<button onClick={injectLimitations} className="rounded-2xl bg-indigo-500 px-5 py-3 text-sm font-bold text-white shadow-lg shadow-indigo-950/40 transition hover:bg-indigo-400">
Inject selected limitations
</button>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-[1.5fr_1fr]">
<div className="relative h-[640px] overflow-hidden rounded-3xl border border-gray-800 bg-[radial-gradient(circle_at_center,_rgba(59,130,246,0.16),_transparent_55%),#030303]">
<svg className="absolute inset-0 h-full w-full" viewBox="0 0 100 100" preserveAspectRatio="none">
{graph.edges.map((edge, index) => {
const source = graph.nodes.find((node) => node.id === edge.source);
const target = graph.nodes.find((node) => node.id === edge.target);
if (!source || !target) return null;
return <line key={`${edge.source}-${edge.target}-${index}`} x1={source.x * 100} y1={source.y * 100} x2={target.x * 100} y2={target.y * 100} stroke="rgba(148,163,184,0.24)" strokeWidth="0.22" />;
})}
</svg>
{graph.nodes.map((node) => (
<button
key={node.id}
onClick={() => setSelected(node)}
className={`absolute -translate-x-1/2 -translate-y-1/2 rounded-2xl border px-3 py-2 text-left text-[11px] shadow-xl transition hover:scale-105 ${stateColor[node.state]} ${selected.id === node.id ? 'ring-2 ring-white/70' : ''}`}
style={{ left: `${node.x * 100}%`, top: `${node.y * 100}%`, opacity: 0.58 + node.intensity * 0.42 }}
>
<div className="font-bold capitalize">{node.label}</div>
<div className="text-[9px] uppercase tracking-widest opacity-70">{node.kind}</div>
</button>
))}
</div>
<div className="space-y-4">
<div className="rounded-3xl border border-gray-800 bg-[#0b0b0b] p-6">
<div className="mb-3 flex items-center gap-2 text-sm font-bold text-teal-300"><GitBranch size={16} /> Selected OmniParser UI-to-LLM node</div>
<h3 className="text-2xl font-bold capitalize">{selected.label}</h3>
<p className="mt-3 text-sm leading-6 text-gray-300">{selected.llmPhrase}</p><p className="mt-2 text-xs leading-5 text-indigo-200">{selected.omniParserPhrase}</p>
<pre className="mt-4 max-h-52 overflow-auto rounded-2xl border border-gray-800 bg-black p-4 text-xs text-indigo-100 whitespace-pre-wrap">{prompt}</pre>
</div>
<div className="rounded-3xl border border-gray-800 bg-[#0b0b0b] p-6">
<div className="mb-3 flex items-center gap-2 text-sm font-bold text-rose-300"><ShieldAlert size={16} /> Limitation functions</div>
<div className="space-y-3">
{limitationFunctions.map((limitation) => (
<div key={limitation.id} className="rounded-2xl border border-gray-800 bg-black/50 p-4">
<div className="flex items-center gap-2 text-sm font-bold"><Sparkles size={14} className="text-rose-300" />{limitation.label}</div>
<p className="mt-2 text-xs leading-5 text-gray-400">{limitation.guardrail}</p>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
};
export default MindwalkGraphView;
|