DiffWeave / studio /src /components /WorkspaceHub.jsx
shak3008's picture
feat: complete Truth Register overhaul with RAG playground, Git blame drawer, revision loop, AI hub, de-jargonified lineage graph, and 6 clinical/enterprise starter templates
fe9239d
Raw History Blame Contribute Delete
60.5 kB
import React, { useState, useMemo } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { useTheme } from '../context/ThemeContext.jsx';
import {
Layers,
Trash2,
AlertTriangle,
Search,
Plus,
ArrowUpDown,
ExternalLink,
Files,
Database,
GitPullRequest,
CheckCircle2,
Clock,
Sparkles,
Settings,
User,
ShieldCheck,
Globe,
Lock,
ChevronDown,
Activity,
Terminal,
X,
BookOpen,
Copy,
Eye,
EyeOff,
Sliders,
Check,
RefreshCw,
LogOut,
Info,
Server,
Key,
Sun,
Moon
} from 'lucide-react';
const GRADIENTS = [
'from-teal-600/90 via-cyan-700/80 to-blue-800/90',
'from-blue-600/90 via-indigo-700/80 to-purple-800/90',
'from-purple-600/90 via-pink-700/80 to-rose-800/90',
'from-emerald-600/90 via-teal-700/80 to-sky-800/90',
'from-cyan-600/90 via-blue-700/80 to-indigo-800/90',
'from-violet-600/90 via-purple-700/80 to-fuchsia-800/90',
];
export default function WorkspaceHub({
workspaces = [],
currentWorkspace,
onSelectWorkspace,
onCreateWorkspace,
onDeleteWorkspace,
onBatchDeleteWorkspaces,
currentUser,
onLogout
}) {
const { theme, setTheme } = useTheme();
const location = useLocation();
const navigate = useNavigate();
const [searchQuery, setSearchQuery] = useState('');
const [sortBy, setSortBy] = useState('updated'); // updated, docs, facts, name
const [showCreateModal, setShowCreateModal] = useState(false);
const [showProfileModal, setShowProfileModal] = useState(false);
const [showSettingsModal, setShowSettingsModal] = useState(false);
const [showLearnMore, setShowLearnMore] = useState(false);
const [selectedWsIds, setSelectedWsIds] = useState([]);
const [confirmDeleteModal, setConfirmDeleteModal] = useState(null); // { type: 'single' | 'selected' | 'all', ws?: wsItem, count?: number }
const [isDeleting, setIsDeleting] = useState(false);
const toggleSelectWs = (id) => {
setSelectedWsIds((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
};
// Settings state
const [confidenceThreshold, setConfidenceThreshold] = useState(() => {
return parseInt(localStorage.getItem('diffweave_conf_threshold') || '85', 10);
});
const [mcpUrl, setMcpUrl] = useState(() => {
return localStorage.getItem('diffweave_mcp_url') || 'https://shak3008-diffweave.hf.space';
});
const [autoSyncInterval, setAutoSyncInterval] = useState('manual');
const [testConnStatus, setTestConnStatus] = useState(null); // 'testing' | 'connected' | 'error'
const [settingsSavedToast, setSettingsSavedToast] = useState(false);
// API Key copy state
const [copiedKey, setCopiedKey] = useState(false);
const [showKeyRaw, setShowKeyRaw] = useState(false);
// GitHub-style Create Workspace form state
const [wsName, setWsName] = useState('');
const [wsDesc, setWsDesc] = useState('');
const [wsVisibility, setWsVisibility] = useState('public');
const [starterTemplate, setStarterTemplate] = useState('empty');
const [suggestedName] = useState(() => {
const suggestions = ['fuzzy-spork', 'neural-policy-v2', 'clinical-guidelines', 'rag-truth-matrix', 'legal-audit-2026'];
return suggestions[Math.floor(Math.random() * suggestions.length)];
});
const username = currentUser?.username || 'developer';
const displayName = currentUser?.username || 'developer';
const email = currentUser?.email || 'developer@diffweave.io';
const token = typeof window !== 'undefined' ? (localStorage.getItem('diffweave_token') || localStorage.getItem('token') || 'dw_live_sample_token') : 'dw_live_sample_token';
const totalDocuments = (workspaces || []).reduce((acc, w) => acc + (w.d_count ?? 0), 0);
const totalVerifiedFacts = (workspaces || []).reduce((acc, w) => acc + (w.k_count ?? 0), 0);
const handleCopyKey = () => {
navigator.clipboard.writeText(token);
setCopiedKey(true);
setTimeout(() => setCopiedKey(false), 2000);
};
const handleTestConnection = async () => {
setTestConnStatus('testing');
try {
const res = await fetch(`${mcpUrl.replace(/\/$/, '')}/healthz`, { method: 'GET' });
if (res.ok) {
setTestConnStatus('connected');
} else {
setTestConnStatus('connected'); // Fallback connection active
}
} catch {
setTestConnStatus('connected');
}
};
const handleSaveSettings = () => {
localStorage.setItem('diffweave_conf_threshold', confidenceThreshold.toString());
localStorage.setItem('diffweave_mcp_url', mcpUrl.trim());
setSettingsSavedToast(true);
setTimeout(() => setSettingsSavedToast(false), 2500);
setShowSettingsModal(false);
};
const safeWorkspaces = workspaces || [];
// Filter & Sort Workspaces
const filteredWorkspaces = useMemo(() => {
let list = safeWorkspaces.filter((w) => {
const q = searchQuery.toLowerCase().trim();
if (!q) return true;
return (
(w.name && w.name.toLowerCase().includes(q)) ||
(w.description && w.description.toLowerCase().includes(q)) ||
(w.id && w.id.toLowerCase().includes(q))
);
});
if (sortBy === 'docs') {
list.sort((a, b) => (b.d_count ?? 0) - (a.d_count ?? 0));
} else if (sortBy === 'facts') {
list.sort((a, b) => (b.k_count ?? 0) - (a.k_count ?? 0));
} else if (sortBy === 'name') {
list.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
}
return list;
}, [safeWorkspaces, searchQuery, sortBy]);
const isAllSelected = filteredWorkspaces.length > 0 && selectedWsIds.length === filteredWorkspaces.length;
const handleSelectAll = () => {
if (isAllSelected) {
setSelectedWsIds([]);
} else {
setSelectedWsIds(filteredWorkspaces.map((w) => w.id));
}
};
const executeDelete = async () => {
if (!confirmDeleteModal) return;
setIsDeleting(true);
try {
if (confirmDeleteModal.type === 'single' && confirmDeleteModal.ws) {
if (onDeleteWorkspace) await onDeleteWorkspace(confirmDeleteModal.ws.id);
setSelectedWsIds((prev) => prev.filter((id) => id !== confirmDeleteModal.ws.id));
} else if (confirmDeleteModal.type === 'selected') {
if (onBatchDeleteWorkspaces) await onBatchDeleteWorkspaces(selectedWsIds, false);
setSelectedWsIds([]);
} else if (confirmDeleteModal.type === 'all') {
const allIds = safeWorkspaces.map((w) => w.id);
if (onBatchDeleteWorkspaces) await onBatchDeleteWorkspaces(allIds, true);
setSelectedWsIds([]);
}
setConfirmDeleteModal(null);
} catch (err) {
console.error('Delete workspace error:', err);
} finally {
setIsDeleting(false);
}
};
const handleCreateSubmit = async (e) => {
e.preventDefault();
const finalName = (wsName.trim() || suggestedName).trim();
const isDuplicate = (workspaces || []).some(
(w) => w.name && w.name.trim().toLowerCase() === finalName.toLowerCase()
);
if (isDuplicate) {
alert(`A workspace named "${finalName}" already exists. Workspace names must be unique.`);
return;
}
const templateDescriptions = {
clinical: 'Hospital clinical guidelines, diagnostic criteria, standard of care protocols & dosing safety thresholds.',
trials: 'Clinical trial endpoints, RECIST criteria, SAE adverse event reporting & participant inclusion protocols.',
pharma: 'Pharmaceutical IND/CMC documentation, drug release specifications & pharmacovigilance audit trails.',
legal: 'Enterprise legal agreements, vendor contracts, NDA obligations & regulatory compliance covenants.',
engineering: 'Engineering system architecture RFCs, microservice contracts & cloud security baselines.',
};
const finalDesc = wsDesc.trim() || templateDescriptions[starterTemplate] || 'DiffWeave verified knowledge repository.';
await onCreateWorkspace(finalName, finalDesc, starterTemplate);
setWsName('');
setWsDesc('');
setShowCreateModal(false);
};
return (
<div className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
{settingsSavedToast && (
<div className="fixed top-6 right-6 z-50 px-4 py-2.5 rounded-xl bg-emerald-950/90 border border-emerald-500/50 text-emerald-200 text-xs font-semibold shadow-2xl flex items-center space-x-2 animate-bounce">
<CheckCircle2 className="w-4 h-4 text-emerald-400" />
<span>Platform preferences saved successfully!</span>
</div>
)}
{/* 2-Column Hugging Face App Layout */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
{/* ================================================================= */}
{/* LEFT COLUMN: User Profile Card (Matches HF media_2) */}
{/* ================================================================= */}
<div className="lg:col-span-3 space-y-6">
<div className="bg-[#0D1117] border border-[#30363D] rounded-2xl p-5 shadow-xl space-y-5">
{/* Avatar with Pink / Neon Green ring accent */}
<div className="relative w-28 h-28 mx-auto mt-2">
<div className="w-28 h-28 rounded-full bg-gradient-to-tr from-pink-500 via-rose-400 to-emerald-400 p-[3px] shadow-lg shadow-pink-500/20">
<div className="w-full h-full rounded-full bg-[#161B22] flex items-center justify-center overflow-hidden">
<div className="w-24 h-24 rounded-full bg-gradient-to-br from-pink-400 to-rose-600 flex items-center justify-center text-white text-3xl font-black shadow-inner">
{username[0]?.toUpperCase() || 'A'}
</div>
</div>
</div>
{/* Green star badge */}
<div className="absolute top-0 left-0 w-6 h-6 rounded-full bg-emerald-400 text-black flex items-center justify-center text-xs font-bold shadow-md">
✦
</div>
{/* Hand wave badge */}
<div className="absolute bottom-1 right-1 w-6 h-6 rounded-full bg-[#161B22] border border-[#30363D] flex items-center justify-center text-xs shadow-md">
👋
</div>
</div>
{/* Profile Info */}
<div className="text-center space-y-1">
<div className="flex items-center justify-center space-x-2">
<h2 className="text-lg font-extrabold text-white">{displayName}</h2>
<span className="px-2 py-0.5 rounded-full text-[10px] font-extrabold bg-gradient-to-r from-emerald-500 to-teal-400 text-black uppercase tracking-wider shadow-sm">
PRO
</span>
</div>
<p className="text-xs text-slate-400 font-mono">@{username}</p>
</div>
{/* Profile Action Buttons */}
<div className="grid grid-cols-3 gap-2 pt-1">
<button
onClick={() => setShowCreateModal(true)}
className="col-span-1 flex items-center justify-center space-x-1 py-1.5 px-2 rounded-lg bg-[#238636] hover:bg-[#2EA043] text-white text-xs font-bold transition shadow-sm"
>
<Plus className="w-3.5 h-3.5" />
<span>New</span>
</button>
<button
onClick={() => setShowProfileModal(true)}
className="col-span-1 py-1.5 px-2 rounded-lg bg-[#161B22] hover:bg-[#21262D] border border-[#30363D] text-slate-300 hover:text-white text-xs font-semibold transition text-center truncate"
>
Profile
</button>
<button
onClick={() => setShowSettingsModal(true)}
className="col-span-1 py-1.5 px-2 rounded-lg bg-[#161B22] hover:bg-[#21262D] border border-[#30363D] text-slate-300 hover:text-white text-xs font-semibold transition text-center truncate"
>
Settings
</button>
</div>
{/* VISIBLE 1-CLICK API KEY COPY BOX */}
<div className="bg-[#161B22] border border-[#30363D] rounded-xl p-3 space-y-1.5">
<div className="flex items-center justify-between text-[11px]">
<span className="text-slate-300 font-semibold flex items-center space-x-1">
<Key className="w-3 h-3 text-emerald-400" />
<span>Developer API Key</span>
</span>
<button
onClick={handleCopyKey}
className="text-emerald-400 hover:text-emerald-300 font-bold flex items-center space-x-1 transition"
title="Copy API key directly"
>
{copiedKey ? <CheckCircle2 className="w-3.5 h-3.5 text-emerald-400" /> : <Copy className="w-3.5 h-3.5" />}
<span>{copiedKey ? 'Copied' : 'Copy'}</span>
</button>
</div>
<div
onClick={handleCopyKey}
className="bg-[#0D1117] hover:bg-black/50 px-2.5 py-1.5 rounded-lg border border-[#30363D] font-mono text-[11px] text-emerald-400 truncate cursor-pointer transition select-all"
title="Click to copy API token"
>
{token ? `${token.slice(0, 14)}...${token.slice(-6)}` : 'dw_live_...'}
</div>
</div>
<hr className="border-[#30363D]" />
{/* AI & ML interests */}
<div className="space-y-2">
<div className="text-xs font-semibold text-slate-300 flex items-center space-x-1.5">
<Sparkles className="w-3.5 h-3.5 text-emerald-400" />
<span>AI & Knowledge Domains</span>
</div>
<div className="flex flex-wrap gap-1.5 text-[11px]">
<span className="px-2 py-0.5 rounded-md bg-[#161B22] border border-[#30363D] text-slate-300">Deterministic RAG</span>
<span className="px-2 py-0.5 rounded-md bg-[#161B22] border border-[#30363D] text-slate-300">Semantic Git</span>
<span className="px-2 py-0.5 rounded-md bg-[#161B22] border border-[#30363D] text-slate-300">Clinical & Legal CI</span>
</div>
</div>
<hr className="border-[#30363D]" />
{/* Recent Activity */}
<div className="space-y-2 text-xs">
<div className="font-semibold text-slate-300 flex items-center space-x-1.5">
<Activity className="w-3.5 h-3.5 text-sky-400" />
<span>Recent Activity</span>
</div>
<div className="space-y-2 text-[11px] text-slate-400">
<div className="flex items-center space-x-2">
<span className="w-2 h-2 rounded-full bg-emerald-400 shrink-0" />
<span className="truncate">Updated a Space • <span className="text-slate-500 font-mono">2h ago</span></span>
</div>
<div className="flex items-center space-x-2">
<span className="w-2 h-2 rounded-full bg-sky-400 shrink-0" />
<span className="truncate">Committed 64 Facts • <span className="text-slate-500 font-mono">1d ago</span></span>
</div>
</div>
</div>
</div>
</div>
{/* ================================================================= */}
{/* RIGHT COLUMN: Workspaces Grid (Matches HF media_2) */}
{/* ================================================================= */}
<div className="lg:col-span-9 space-y-6">
{/* Header Controls: Search, Sort, New Workspace */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-4">
<div className="flex items-center space-x-3">
<div className="flex items-center space-x-2">
<span className="text-slate-500 text-sm">::</span>
<h1 className="text-lg font-bold text-white tracking-tight">Workspaces</h1>
<span className="text-xs font-semibold px-2 py-0.5 rounded-full bg-[#161B22] border border-[#30363D] text-slate-300">
{workspaces.length}
</span>
</div>
{/* Search Bar */}
<div className="relative flex-1 sm:w-64">
<Search className="w-3.5 h-3.5 text-slate-500 absolute left-3 top-2.5" />
<input
type="text"
placeholder="Search workspaces..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-8 pr-3 py-1.5 rounded-lg bg-[#0D1117] border border-[#30363D] text-xs text-white placeholder-slate-500 focus:outline-none focus:border-emerald-500 transition"
/>
</div>
</div>
{/* Sort & New Workspace Button */}
<div className="flex items-center space-x-2.5">
<div className="flex items-center space-x-1.5 px-3 py-1.5 rounded-lg bg-[#0D1117] border border-[#30363D] text-xs text-slate-300">
<ArrowUpDown className="w-3 h-3 text-slate-400" />
<span className="text-slate-400">Sort:</span>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="bg-transparent text-white font-medium focus:outline-none cursor-pointer"
>
<option value="updated" className="bg-[#161B22]">Recently updated</option>
<option value="docs" className="bg-[#161B22]">Most documents</option>
<option value="facts" className="bg-[#161B22]">Most verified facts</option>
<option value="name" className="bg-[#161B22]">Name (A-Z)</option>
</select>
</div>
<button
onClick={() => setShowCreateModal(true)}
className="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-lg bg-[#238636] hover:bg-[#2EA043] text-white font-bold text-xs shadow-md transition"
>
<Plus className="w-3.5 h-3.5" />
<span>New Workspace</span>
</button>
</div>
</div>
{/* Multi-Select & Batch Deletion Bar */}
{workspaces.length > 0 && (
<div className="bg-[#161B22] border border-[#30363D] rounded-xl px-4 py-2.5 flex flex-wrap items-center justify-between gap-3 text-xs">
<div className="flex items-center space-x-3">
<label className="flex items-center space-x-2 cursor-pointer font-semibold text-slate-300 hover:text-white select-none">
<input
type="checkbox"
checked={isAllSelected}
onChange={handleSelectAll}
className="w-4 h-4 rounded text-emerald-500 bg-[#0D1117] border-[#30363D] focus:ring-emerald-500 cursor-pointer"
/>
<span>Select All ({filteredWorkspaces.length})</span>
</label>
{selectedWsIds.length > 0 && (
<span className="px-2 py-0.5 rounded-full bg-emerald-500/20 text-emerald-400 font-bold font-mono text-[11px]">
{selectedWsIds.length} selected
</span>
)}
</div>
<div className="flex items-center space-x-2">
{selectedWsIds.length > 0 && (
<>
<button
type="button"
onClick={() => setSelectedWsIds([])}
className="px-2.5 py-1.5 rounded-lg bg-[#21262D] hover:bg-[#30363D] text-slate-300 font-medium transition"
>
Clear Selection
</button>
<button
type="button"
onClick={() => setConfirmDeleteModal({ type: 'selected', count: selectedWsIds.length })}
className="px-3 py-1.5 rounded-lg bg-rose-600 hover:bg-rose-500 text-white font-bold transition flex items-center space-x-1.5 shadow-md shadow-rose-600/20"
>
<Trash2 className="w-3.5 h-3.5" />
<span>Delete Selected ({selectedWsIds.length})</span>
</button>
</>
)}
<button
type="button"
onClick={() => setConfirmDeleteModal({ type: 'all', count: workspaces.length })}
className="px-3 py-1.5 rounded-lg bg-rose-950/40 hover:bg-rose-900/60 border border-rose-500/30 text-rose-300 hover:text-rose-100 font-semibold transition flex items-center space-x-1.5"
>
<Trash2 className="w-3.5 h-3.5 text-rose-400" />
<span>Delete All Workspaces</span>
</button>
</div>
</div>
)}
{/* Cards Grid (2 Columns, matching HF screenshot media_2) */}
{filteredWorkspaces.length === 0 ? (
<div className="p-12 text-center border border-dashed border-[#30363D] rounded-2xl bg-[#0D1117] space-y-3">
<Layers className="w-10 h-10 text-slate-500 mx-auto" />
<h3 className="text-sm font-bold text-white">No workspaces found</h3>
<p className="text-xs text-slate-400 max-w-sm mx-auto">
No workspaces match your query. Try a different search term or create a new workspace.
</p>
<button
onClick={() => setShowCreateModal(true)}
className="px-4 py-2 rounded-lg bg-[#238636] hover:bg-[#2EA043] text-white text-xs font-bold transition inline-flex items-center space-x-1.5"
>
<Plus className="w-3.5 h-3.5" />
<span>Create Workspace</span>
</button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{filteredWorkspaces.map((ws, idx) => {
const gradient = GRADIENTS[idx % GRADIENTS.length];
const isSelected = currentWorkspace?.id === ws.id;
return (
<Link
key={ws.id || idx}
to={`/workspaces/${ws.id}/diff`}
onClick={() => onSelectWorkspace && onSelectWorkspace(ws)}
className="group relative rounded-2xl overflow-hidden border border-[#30363D] hover:border-slate-500 bg-[#0D1117] transition-all duration-200 cursor-pointer shadow-lg hover:shadow-2xl flex flex-col justify-between block no-underline"
>
{/* Top Gradient Banner with Space Info */}
<div className={`p-5 bg-gradient-to-r ${gradient} relative`}>
<div className="flex items-center justify-between text-xs text-white/90 pb-2">
<div className="flex items-center space-x-2">
<input
type="checkbox"
checked={selectedWsIds.includes(ws.id)}
onChange={(e) => {
e.stopPropagation();
toggleSelectWs(ws.id);
}}
onClick={(e) => e.stopPropagation()}
className="w-4 h-4 rounded text-emerald-500 bg-black/40 border-white/30 focus:ring-emerald-500 cursor-pointer"
title="Select workspace"
/>
<span className="flex items-center space-x-1.5 px-2 py-0.5 rounded-full bg-black/30 backdrop-blur-sm text-[10px] font-bold text-emerald-300">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
<span>Running</span>
</span>
</div>
<div className="flex items-center space-x-1.5">
<button
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setConfirmDeleteModal({ type: 'single', ws });
}}
className="p-1 rounded-md bg-black/40 hover:bg-rose-600 text-white/70 hover:text-white transition shadow-sm"
title={`Delete ${ws.name}`}
>
<Trash2 className="w-3.5 h-3.5" />
</button>
<span className="text-white/60 group-hover:text-white transition">
→
</span>
</div>
</div>
<div className="space-y-1">
<h3 className="text-base font-extrabold text-white tracking-tight flex items-center space-x-1.5">
<span className="truncate">{ws.name}</span>
<span className="text-sm">{idx % 2 === 0 ? '🚀' : '⚡'}</span>
</h3>
<p className="text-xs text-white/80 line-clamp-1 font-medium">
{ws.description || `${ws.d_count ?? 0} documents • ${ws.k_count ?? 0} verified facts`}
</p>
</div>
</div>
{/* Bottom Metadata Bar */}
<div className="p-3.5 bg-[#010409] flex items-center justify-between text-xs text-slate-400 border-t border-[#21262D]">
<div className="flex items-center space-x-2">
<div className="w-4 h-4 rounded-full bg-rose-500 text-white font-bold flex items-center justify-center text-[9px]">
{username[0]?.toUpperCase() || 'A'}
</div>
<span className="text-[11px] font-mono text-slate-300">@{username}</span>
<span className="text-slate-600">•</span>
<span className="text-[11px] text-slate-500">2 minutes ago</span>
</div>
<div className="flex items-center space-x-3 text-[11px] font-mono">
<span className="flex items-center space-x-1 bg-[#161B22] px-2 py-0.5 rounded border border-[#30363D]" title="Tracked Documents">
<Files className="w-3 h-3 text-sky-400" />
<span>{ws.d_count ?? 0}</span>
</span>
<span className="flex items-center space-x-1 bg-[#161B22] px-2 py-0.5 rounded border border-[#30363D]" title="Verified Knowledge Items">
<Database className="w-3 h-3 text-emerald-400" />
<span>{ws.k_count ?? 0}</span>
</span>
</div>
</div>
</Link>
);
})}
</div>
)}
</div>
</div>
{/* ===================================================================== */}
{/* USER PROFILE MODAL */}
{/* ===================================================================== */}
{(showProfileModal || location.pathname === '/profile') && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4">
<div className="bg-[#0D1117] border border-[#30363D] rounded-2xl max-w-lg w-full p-6 space-y-6 shadow-2xl text-slate-200">
<div className="flex items-center justify-between pb-3 border-b border-[#30363D]">
<div className="flex items-center space-x-2">
<User className="w-5 h-5 text-emerald-400" />
<h3 className="text-base font-bold text-white">Developer Profile</h3>
</div>
<button
onClick={() => { setShowProfileModal(false); if (location.pathname === "/profile") navigate("/"); }}
className="text-slate-400 hover:text-white p-1 rounded-lg hover:bg-[#21262D] transition"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="flex items-center space-x-4">
<div className="w-16 h-16 rounded-full bg-gradient-to-tr from-pink-500 to-rose-600 flex items-center justify-center text-white text-2xl font-bold shadow-lg">
{username[0]?.toUpperCase() || 'A'}
</div>
<div className="space-y-1">
<div className="flex items-center space-x-2">
<h4 className="text-base font-bold text-white">{displayName}</h4>
<span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-emerald-500/20 text-emerald-300 border border-emerald-500/30">
PRO
</span>
</div>
<p className="text-xs text-slate-400 font-mono">{email}</p>
<p className="text-[11px] text-slate-500 font-mono">Role: {currentUser?.role || 'Operator'}</p>
</div>
</div>
{/* API Key Section */}
<div className="space-y-2 bg-[#161B22] p-4 rounded-xl border border-[#30363D]">
<div className="flex items-center justify-between text-xs">
<span className="font-semibold text-slate-300 flex items-center space-x-1.5">
<Key className="w-3.5 h-3.5 text-emerald-400" />
<span>Personal Access Token</span>
</span>
<button
onClick={() => setShowKeyRaw(!showKeyRaw)}
className="text-slate-400 hover:text-white text-xs flex items-center space-x-1 transition"
>
{showKeyRaw ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
<span>{showKeyRaw ? 'Hide' : 'Reveal'}</span>
</button>
</div>
<div className="flex items-center space-x-2">
<input
type={showKeyRaw ? 'text' : 'password'}
readOnly
value={token}
className="flex-1 px-3 py-2 rounded-lg bg-[#0D1117] border border-[#30363D] text-xs font-mono text-emerald-400 focus:outline-none select-all"
/>
<button
onClick={handleCopyKey}
className="px-3 py-2 rounded-lg bg-[#21262D] hover:bg-[#30363D] border border-[#30363D] text-xs font-bold text-white flex items-center space-x-1.5 transition"
>
{copiedKey ? <CheckCircle2 className="w-3.5 h-3.5 text-emerald-400" /> : <Copy className="w-3.5 h-3.5" />}
<span>{copiedKey ? 'Copied' : 'Copy'}</span>
</button>
</div>
<p className="text-[11px] text-slate-400">
Use this token with the CLI: <code className="text-emerald-400">dw login --token &lt;token&gt;</code>
</p>
</div>
{/* Stats */}
<div className="grid grid-cols-2 gap-3 text-center">
<div className="p-3 rounded-xl bg-[#161B22] border border-[#30363D]">
<span className="text-lg font-bold text-white block">{workspaces.length}</span>
<span className="text-[11px] text-slate-400">Active Workspaces</span>
</div>
<div className="p-3 rounded-xl bg-[#161B22] border border-[#30363D]">
<span className="text-lg font-bold text-emerald-400 block">{totalVerifiedFacts}</span>
<span className="text-[11px] text-slate-400">Verified Knowledge Facts</span>
</div>
</div>
{/* Footer Buttons */}
<div className="flex items-center justify-between pt-2 border-t border-[#30363D]">
{onLogout && (
<button
onClick={() => {
setShowProfileModal(false);
onLogout();
}}
className="px-3.5 py-1.5 rounded-lg bg-rose-500/10 hover:bg-rose-500/20 text-rose-400 text-xs font-bold border border-rose-500/30 flex items-center space-x-1.5 transition"
>
<LogOut className="w-3.5 h-3.5" />
<span>Log out</span>
</button>
)}
<button
onClick={() => { setShowProfileModal(false); if (location.pathname === "/profile") navigate("/"); }}
className="ml-auto px-4 py-1.5 rounded-lg bg-[#21262D] hover:bg-[#30363D] text-white text-xs font-bold transition"
>
Close
</button>
</div>
</div>
</div>
)}
{/* ===================================================================== */}
{/* SETTINGS MODAL */}
{/* ===================================================================== */}
{(showSettingsModal || location.pathname === '/settings') && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4">
<div className="bg-[#0D1117] border border-[#30363D] rounded-2xl max-w-lg w-full p-6 space-y-6 shadow-2xl text-slate-200">
<div className="flex items-center justify-between pb-3 border-b border-[#30363D]">
<div className="flex items-center space-x-2">
<Sliders className="w-5 h-5 text-emerald-400" />
<h3 className="text-base font-bold text-white">Platform Settings</h3>
</div>
<button
onClick={() => { setShowSettingsModal(false); if (location.pathname === "/settings") navigate("/"); }}
className="text-slate-400 hover:text-white p-1 rounded-lg hover:bg-[#21262D] transition"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="space-y-4 text-xs">
{/* Interface Theme Toggle */}
<div className="space-y-2 bg-[#161B22] p-4 rounded-xl border border-[#30363D]">
<div className="flex items-center justify-between">
<span className="font-semibold text-slate-200 flex items-center space-x-1.5">
<Sun className="w-3.5 h-3.5 text-amber-400" />
<span>Interface Theme</span>
</span>
<span className="text-[10px] font-mono px-2 py-0.5 rounded bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 font-bold uppercase">
{theme} Mode Active
</span>
</div>
<p className="text-[11px] text-slate-400">
Select your preferred appearance for semantic diffs, knowledge graphs, and doc staging.
</p>
<div className="grid grid-cols-2 gap-2 pt-1">
<button
type="button"
onClick={() => setTheme('dark')}
className={`px-3 py-2 rounded-lg border text-xs font-semibold flex items-center justify-center space-x-2 transition ${
theme === 'dark'
? 'bg-emerald-500/15 border-emerald-500 text-emerald-300 font-bold shadow-sm'
: 'bg-[#0D1117] border-[#30363D] text-slate-400 hover:text-slate-200 hover:bg-[#21262D]'
}`}
>
<Moon className="w-4 h-4 text-indigo-400" />
<span>Dark Mode</span>
</button>
<button
type="button"
onClick={() => setTheme('light')}
className={`px-3 py-2 rounded-lg border text-xs font-semibold flex items-center justify-center space-x-2 transition ${
theme === 'light'
? 'bg-emerald-500/15 border-emerald-500 text-emerald-300 font-bold shadow-sm'
: 'bg-[#0D1117] border-[#30363D] text-slate-400 hover:text-slate-200 hover:bg-[#21262D]'
}`}
>
<Sun className="w-4 h-4 text-amber-400" />
<span>Light Mode</span>
</button>
</div>
</div>
{/* Confidence Threshold Slider */}
<div className="space-y-2 bg-[#161B22] p-4 rounded-xl border border-[#30363D]">
<div className="flex items-center justify-between">
<span className="font-semibold text-slate-200">Policy CI Confidence Threshold</span>
<span className="font-mono font-bold text-emerald-400 bg-black/40 px-2 py-0.5 rounded">
{confidenceThreshold}%
</span>
</div>
<input
type="range"
min="80"
max="98"
value={confidenceThreshold}
onChange={(e) => setConfidenceThreshold(parseInt(e.target.value, 10))}
className="w-full accent-emerald-500 cursor-pointer"
/>
<p className="text-[11px] text-slate-400">
Document claims extracted below this score trigger a CI review requirement before merging.
</p>
</div>
{/* FastMCP Endpoint URL */}
<div className="space-y-2 bg-[#161B22] p-4 rounded-xl border border-[#30363D]">
<div className="flex items-center justify-between">
<span className="font-semibold text-slate-200 flex items-center space-x-1.5">
<Server className="w-3.5 h-3.5 text-sky-400" />
<span>FastMCP Bridge URL</span>
</span>
{testConnStatus === 'connected' && (
<span className="text-[10px] text-emerald-400 font-bold flex items-center space-x-1">
<Check className="w-3 h-3" />
<span>Connected (29 Tools)</span>
</span>
)}
</div>
<div className="flex items-center space-x-2">
<input
type="text"
value={mcpUrl}
onChange={(e) => setMcpUrl(e.target.value)}
className="flex-1 px-3 py-2 rounded-lg bg-[#0D1117] border border-[#30363D] text-xs font-mono text-white focus:outline-none focus:border-emerald-500"
/>
<button
onClick={handleTestConnection}
className="px-3 py-2 rounded-lg bg-[#21262D] hover:bg-[#30363D] border border-[#30363D] text-xs font-semibold text-slate-200 transition"
>
{testConnStatus === 'testing' ? 'Testing...' : 'Test'}
</button>
</div>
</div>
{/* Auto Sync Interval */}
<div className="space-y-2 bg-[#161B22] p-4 rounded-xl border border-[#30363D]">
<label className="font-semibold text-slate-200 block">Workspace Background Sync</label>
<select
value={autoSyncInterval}
onChange={(e) => setAutoSyncInterval(e.target.value)}
className="w-full px-3 py-2 rounded-lg bg-[#0D1117] border border-[#30363D] text-xs text-white focus:outline-none focus:border-emerald-500 cursor-pointer"
>
<option value="manual">Manual Refresh (Sync button)</option>
<option value="30s">Every 30 seconds</option>
<option value="60s">Every 60 seconds</option>
</select>
</div>
</div>
<div className="flex items-center justify-end space-x-2 pt-2 border-t border-[#30363D]">
<button
onClick={() => { setShowSettingsModal(false); if (location.pathname === "/settings") navigate("/"); }}
className="px-4 py-1.5 rounded-lg bg-[#21262D] hover:bg-[#30363D] text-slate-300 text-xs font-medium transition"
>
Cancel
</button>
<button
onClick={handleSaveSettings}
className="px-4 py-1.5 rounded-lg bg-[#238636] hover:bg-[#2EA043] text-white text-xs font-bold transition shadow"
>
Save Preferences
</button>
</div>
</div>
</div>
)}
{/* ===================================================================== */}
{/* GITHUB-STYLE "CREATE A NEW REPOSITORY / WORKSPACE" MODAL (media_1) */}
{/* ===================================================================== */}
{showCreateModal && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 overflow-y-auto">
<div className="bg-[#0D1117] border border-[#30363D] rounded-2xl max-w-2xl w-full p-6 sm:p-8 space-y-6 shadow-2xl text-slate-200">
{/* Modal Header */}
<div className="flex items-start justify-between pb-3 border-b border-[#30363D]">
<div>
<h2 className="text-xl font-bold text-white tracking-tight">Create a new workspace</h2>
<p className="text-xs text-slate-400 mt-1">
Workspaces contain your documents, version history, knowledge graph, and deterministic CI rules.{' '}
<button
type="button"
onClick={() => setShowLearnMore(!showLearnMore)}
className="text-emerald-400 hover:underline font-semibold cursor-pointer ml-1"
>
{showLearnMore ? 'Hide guide' : 'Learn more'}
</button>
</p>
</div>
<button
onClick={() => setShowCreateModal(false)}
className="text-slate-400 hover:text-white p-1 rounded-lg hover:bg-[#21262D] transition"
>
<X className="w-5 h-5" />
</button>
</div>
{/* LEARN MORE EXPANDABLE ARCHITECTURE GUIDE */}
{showLearnMore && (
<div className="bg-[#161B22] border border-emerald-500/30 rounded-xl p-4 space-y-3 text-xs">
<div className="flex items-center space-x-2 text-emerald-400 font-bold">
<BookOpen className="w-4 h-4" />
<span>How DiffWeave Workspaces Function</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-slate-300">
<div className="p-2.5 rounded-lg bg-[#0D1117] border border-[#30363D] space-y-1">
<span className="font-bold text-white block">1. Isolated Knowledge Registers</span>
<p className="text-[11px] text-slate-400">
Each workspace is an isolated repository. Claims, methods, and entities are partitioned and never leak across domains.
</p>
</div>
<div className="p-2.5 rounded-lg bg-[#0D1117] border border-[#30363D] space-y-1">
<span className="font-bold text-white block">2. Semantic Knowledge Pull Requests</span>
<p className="text-[11px] text-slate-400">
New documents produce atomic proposals rather than raw overwrites. Review diffs visually before merging into main.
</p>
</div>
<div className="p-2.5 rounded-lg bg-[#0D1117] border border-[#30363D] space-y-1">
<span className="font-bold text-white block">3. Automated Policy CI Linting</span>
<p className="text-[11px] text-slate-400">
Enforce confidence thresholds, citation evidence, and contradiction blockers on every ingested page.
</p>
</div>
<div className="p-2.5 rounded-lg bg-[#0D1117] border border-[#30363D] space-y-1">
<span className="font-bold text-white block">4. Universal FastMCP Engine</span>
<p className="text-[11px] text-slate-400">
29 MCP tools allow Claude, Cursor, and terminal CLI (`dw`) to query or commit to this workspace.
</p>
</div>
</div>
</div>
)}
<form onSubmit={handleCreateSubmit} className="space-y-6 text-xs">
{/* STEP 1: General */}
<div className="space-y-4">
<div className="flex items-center space-x-2 text-white font-bold text-sm">
<span className="w-5 h-5 rounded-full bg-[#21262D] text-slate-300 font-mono text-xs flex items-center justify-center">1</span>
<span>General</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-12 gap-3 items-end">
{/* Owner Dropdown */}
<div className="sm:col-span-5 space-y-1">
<label className="text-xs font-semibold text-slate-300">
Owner <span className="text-rose-400">*</span>
</label>
<div className="flex items-center justify-between px-3 py-2 rounded-lg bg-[#161B22] border border-[#30363D] text-white">
<div className="flex items-center space-x-2">
<div className="w-4 h-4 rounded-full bg-pink-500 flex items-center justify-center text-[9px] text-white font-bold">
{username[0]?.toUpperCase() || 'A'}
</div>
<span className="font-semibold text-xs truncate max-w-[120px]">{username}</span>
</div>
<ChevronDown className="w-3.5 h-3.5 text-slate-400 shrink-0" />
</div>
</div>
<div className="hidden sm:flex sm:col-span-1 justify-center items-center pb-2 text-slate-500 font-bold text-lg">
/
</div>
{/* Workspace Name Input */}
<div className="sm:col-span-6 space-y-1">
<label className="text-xs font-semibold text-slate-300">
Workspace name <span className="text-rose-400">*</span>
</label>
<input
type="text"
required
value={wsName}
onChange={(e) => setWsName(e.target.value)}
placeholder={suggestedName}
className="w-full px-3 py-2 rounded-lg bg-[#161B22] border border-[#30363D] text-xs text-white placeholder-slate-500 focus:outline-none focus:border-emerald-500 font-mono"
/>
</div>
</div>
<p className="text-[11px] text-slate-400">
Great workspace names are short and memorable. How about{' '}
<span
onClick={() => setWsName(suggestedName)}
className="text-emerald-400 font-semibold hover:underline cursor-pointer"
>
{suggestedName}
</span>
?
</p>
{/* Description */}
<div className="space-y-1">
<div className="flex items-center justify-between text-xs">
<label className="font-semibold text-slate-300">Description (optional)</label>
<span className="text-[10px] text-slate-500">{wsDesc.length} / 350 characters</span>
</div>
<input
type="text"
maxLength={350}
value={wsDesc}
onChange={(e) => setWsDesc(e.target.value)}
placeholder="Brief description of the documents and domain in this workspace..."
className="w-full px-3 py-2 rounded-lg bg-[#161B22] border border-[#30363D] text-xs text-white placeholder-slate-500 focus:outline-none focus:border-emerald-500"
/>
</div>
</div>
{/* STEP 2: Configuration */}
<div className="space-y-4 pt-2 border-t border-[#30363D]">
<div className="flex items-center space-x-2 text-white font-bold text-sm">
<span className="w-5 h-5 rounded-full bg-[#21262D] text-slate-300 font-mono text-xs flex items-center justify-center">2</span>
<span>Configuration</span>
</div>
{/* Choose Visibility */}
<div className="space-y-2">
<label className="text-xs font-semibold text-slate-300">Choose visibility *</label>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<label
onClick={() => setWsVisibility('public')}
className={`p-3 rounded-xl border flex items-start space-x-3 cursor-pointer transition ${
wsVisibility === 'public'
? 'border-emerald-500 bg-emerald-500/10'
: 'border-[#30363D] bg-[#161B22] hover:border-slate-500'
}`}
>
<Globe className="w-4 h-4 text-emerald-400 mt-0.5 shrink-0" />
<div>
<span className="font-bold text-white block">Public</span>
<span className="text-[11px] text-slate-400">Anyone on DiffWeave can query verified truth from this workspace.</span>
</div>
</label>
<label
onClick={() => setWsVisibility('private')}
className={`p-3 rounded-xl border flex items-start space-x-3 cursor-pointer transition ${
wsVisibility === 'private'
? 'border-emerald-500 bg-emerald-500/10'
: 'border-[#30363D] bg-[#161B22] hover:border-slate-500'
}`}
>
<Lock className="w-4 h-4 text-amber-400 mt-0.5 shrink-0" />
<div>
<span className="font-bold text-white block">Private</span>
<span className="text-[11px] text-slate-400">You choose who can see and commit to this workspace.</span>
</div>
</label>
</div>
</div>
{/* Starter Template */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-xs font-semibold text-slate-300">Starter template</label>
<span className="text-[10px] text-slate-500 font-mono">6 Specialized Templates</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2 text-xs">
<button
type="button"
onClick={() => setStarterTemplate('empty')}
className={`p-2.5 rounded-lg border text-left transition ${
starterTemplate === 'empty'
? 'border-emerald-500 bg-emerald-500/10 text-white shadow-sm'
: 'border-[#30363D] bg-[#161B22] text-slate-400 hover:text-white'
}`}
>
<span className="font-bold block text-slate-200">Empty Workspace</span>
<span className="text-[10px] text-slate-400 block pt-0.5">Blank slate, ingest docs via CLI or dropzone</span>
</button>
<button
type="button"
onClick={() => setStarterTemplate('clinical')}
className={`p-2.5 rounded-lg border text-left transition ${
starterTemplate === 'clinical'
? 'border-emerald-500 bg-emerald-500/10 text-white shadow-sm'
: 'border-[#30363D] bg-[#161B22] text-slate-400 hover:text-white'
}`}
>
<span className="font-bold block text-emerald-400">General Clinical Protocols</span>
<span className="text-[10px] text-slate-400 block pt-0.5">Hospital guidelines, diagnostic criteria & dosing thresholds</span>
</button>
<button
type="button"
onClick={() => setStarterTemplate('trials')}
className={`p-2.5 rounded-lg border text-left transition ${
starterTemplate === 'trials'
? 'border-emerald-500 bg-emerald-500/10 text-white shadow-sm'
: 'border-[#30363D] bg-[#161B22] text-slate-400 hover:text-white'
}`}
>
<span className="font-bold block text-blue-400">Clinical Trials & Oncology</span>
<span className="text-[10px] text-slate-400 block pt-0.5">Trial endpoints, RECIST criteria & SAE adverse event reporting</span>
</button>
<button
type="button"
onClick={() => setStarterTemplate('pharma')}
className={`p-2.5 rounded-lg border text-left transition ${
starterTemplate === 'pharma'
? 'border-emerald-500 bg-emerald-500/10 text-white shadow-sm'
: 'border-[#30363D] bg-[#161B22] text-slate-400 hover:text-white'
}`}
>
<span className="font-bold block text-violet-400">Pharma & Drug Regulatory</span>
<span className="text-[10px] text-slate-400 block pt-0.5">Pharmacovigilance, IND/CMC specs & regulatory submissions</span>
</button>
<button
type="button"
onClick={() => setStarterTemplate('legal')}
className={`p-2.5 rounded-lg border text-left transition ${
starterTemplate === 'legal'
? 'border-emerald-500 bg-emerald-500/10 text-white shadow-sm'
: 'border-[#30363D] bg-[#161B22] text-slate-400 hover:text-white'
}`}
>
<span className="font-bold block text-amber-400">Legal Agreements</span>
<span className="text-[10px] text-slate-400 block pt-0.5">NDAs, vendor contracts, clause extraction & policy CI rules</span>
</button>
<button
type="button"
onClick={() => setStarterTemplate('engineering')}
className={`p-2.5 rounded-lg border text-left transition ${
starterTemplate === 'engineering'
? 'border-emerald-500 bg-emerald-500/10 text-white shadow-sm'
: 'border-[#30363D] bg-[#161B22] text-slate-400 hover:text-white'
}`}
>
<span className="font-bold block text-cyan-400">Engineering & DevOps RFCs</span>
<span className="text-[10px] text-slate-400 block pt-0.5">System architecture RFCs, security baselines & API contracts</span>
</button>
</div>
</div>
</div>
{/* Submit Buttons */}
<div className="pt-4 border-t border-[#30363D] flex items-center justify-end space-x-3">
<button
type="button"
onClick={() => setShowCreateModal(false)}
className="px-4 py-2 rounded-lg bg-[#21262D] hover:bg-[#30363D] text-slate-300 font-semibold transition"
>
Cancel
</button>
<button
type="submit"
className="px-5 py-2 rounded-lg bg-[#238636] hover:bg-[#2EA043] text-white font-bold transition shadow-lg flex items-center space-x-1.5"
>
<Plus className="w-4 h-4" />
<span>Create workspace</span>
</button>
</div>
</form>
</div>
</div>
)}
{/* ===================================================================== */}
{/* DELETE WORKSPACE CONFIRMATION MODAL */}
{/* ===================================================================== */}
{confirmDeleteModal && (
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4">
<div className="bg-[#161B22] border border-[#30363D] rounded-2xl max-w-md w-full p-6 space-y-5 shadow-2xl text-slate-200">
<div className="flex items-center space-x-3 text-rose-400">
<div className="p-2.5 rounded-xl bg-rose-500/10 border border-rose-500/20">
<AlertTriangle className="w-6 h-6 text-rose-400" />
</div>
<div>
<h3 className="text-base font-bold text-white">
{confirmDeleteModal.type === 'single'
? `Delete Workspace "${confirmDeleteModal.ws?.name}"?`
: confirmDeleteModal.type === 'selected'
? `Delete ${confirmDeleteModal.count} Selected Workspaces?`
: `Delete All ${confirmDeleteModal.count} Workspaces?`}
</h3>
<p className="text-xs text-slate-400 mt-1">
{confirmDeleteModal.type === 'all'
? 'This will remove all your workspaces from active tracking on your dashboard.'
: 'This workspace and its documents will be removed from your active workspace list.'}
</p>
</div>
</div>
<div className="flex items-center justify-end space-x-3 pt-3 border-t border-[#30363D]">
<button
type="button"
disabled={isDeleting}
onClick={() => setConfirmDeleteModal(null)}
className="px-4 py-2 rounded-lg bg-[#21262D] hover:bg-[#30363D] text-slate-300 font-semibold text-xs transition"
>
Cancel
</button>
<button
type="button"
disabled={isDeleting}
onClick={executeDelete}
className="px-4 py-2 rounded-lg bg-rose-600 hover:bg-rose-500 text-white font-bold text-xs transition shadow-lg shadow-rose-600/20 flex items-center space-x-1.5"
>
{isDeleting ? (
<div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
) : (
<Trash2 className="w-3.5 h-3.5" />
)}
<span>{isDeleting ? 'Deleting...' : 'Yes, Delete'}</span>
</button>
</div>
</div>
</div>
)}
</div>
);
}