Skip to content

Commit cff071b

Browse files
authored
Merge pull request #557 from Hawksight-AI/feat/ui-redesign-semantica-explorer
# feat: Redesign all workspace UIs with consistent design system + bug fixes
2 parents d3ffbad + e14b372 commit cff071b

14 files changed

Lines changed: 2003 additions & 1825 deletions

File tree

explorer/src/App.tsx

Lines changed: 1011 additions & 495 deletions
Large diffs are not rendered by default.

explorer/src/workspaces/DecisionWorkspace/DecisionWorkspace.tsx

Lines changed: 155 additions & 306 deletions
Large diffs are not rendered by default.
Lines changed: 90 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,121 @@
1-
/**
2-
* src/workspaces/DiffMergeWorkspace/DiffMergeWorkspace.tsx
3-
*/
41
import { useState } from "react";
2+
import { GitMerge, ArrowRight, CheckCircle2, AlertCircle, Loader2 } from "lucide-react";
53
import { logEvent } from "../../store/registryStore";
64

7-
const THEME_CSS = `
8-
.glass-panel {
9-
background: linear-gradient(135deg, rgba(13,17,23,0.75), rgba(22,27,34,0.6));
10-
backdrop-filter: blur(16px) saturate(1.2);
11-
-webkit-backdrop-filter: blur(16px) saturate(1.2);
12-
border: 1px solid rgba(88,166,255,0.2);
13-
box-shadow: 0 8px 32px rgba(0,0,0,0.5), inset 1px 1px 0 rgba(255,255,255,0.05);
14-
}
15-
`;
5+
interface FieldRow { label: string; primary: string; duplicate: string; differs: boolean }
6+
7+
const MOCK_FIELDS: FieldRow[] = [
8+
{ label: "Name", primary: "Sample Company Inc.", duplicate: "Sample Company", differs: true },
9+
{ label: "Founded", primary: "2004-05-12", duplicate: "2004-05-12", differs: false },
10+
{ label: "Type", primary: "Organization", duplicate: "Organisation", differs: true },
11+
{ label: "Country", primary: "US", duplicate: "US", differs: false },
12+
];
1613

1714
export function DiffMergeWorkspace() {
18-
const [primaryId, setPrimaryId] = useState("n-primary-1");
15+
const [primaryId, setPrimaryId] = useState("n-primary-1");
1916
const [duplicateId, setDuplicateId] = useState("n-dup-2");
17+
const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
18+
const [msg, setMsg] = useState("");
2019

21-
const [msg, setMsg] = useState("");
22-
23-
const handleMerge = async () => {
20+
async function handleMerge() {
21+
setStatus("loading");
22+
setMsg("");
2423
try {
2524
const res = await fetch("/api/enrich/merge", {
2625
method: "POST",
2726
headers: { "Content-Type": "application/json" },
28-
body: JSON.stringify({ primary_id: primaryId, duplicate_ids: [duplicateId] })
27+
body: JSON.stringify({ primary_id: primaryId, duplicate_ids: [duplicateId] }),
2928
});
3029
const data = await res.json();
3130
if (data.merged_into) {
32-
setMsg(`Merge success: redirected ${data.edges_updated} edges to ${data.merged_into}`);
33-
logEvent("merge", `Merged ${duplicateId}${data.merged_into} · ${data.edges_updated} edges redirected`, {
34-
primary: data.merged_into,
35-
duplicate: duplicateId,
36-
edgesUpdated: data.edges_updated,
31+
setStatus("success");
32+
setMsg(`Merged → ${data.merged_into} · ${data.edges_updated ?? 0} edges redirected`);
33+
logEvent("merge", `Merged ${duplicateId}${data.merged_into} · ${data.edges_updated ?? 0} edges redirected`, {
34+
primary: data.merged_into, duplicate: duplicateId, edgesUpdated: data.edges_updated,
3735
});
3836
} else {
39-
setMsg("Merge failed...");
37+
throw new Error(data.detail || "Unexpected response");
4038
}
41-
} catch (err) {
42-
setMsg("Error calling merge endpoint.");
39+
} catch (e: unknown) {
40+
setStatus("error");
41+
setMsg(e instanceof Error ? e.message : "Merge failed");
4342
}
44-
};
43+
}
4544

4645
return (
47-
<div style={{ display: "flex", flexDirection: "column", width: "100%", height: "100%", background: "#0d1117", padding: 32, gap: 24, boxSizing: "border-box" }}>
48-
<style>{THEME_CSS}</style>
49-
<div>
50-
<h1 style={{ margin: "0 0 8px 0", color: "#fff" }}>Entity Diff & Merge</h1>
51-
<p style={{ margin: 0, color: "#8b949e" }}>Compare suspected duplicate entities and reconcile them.</p>
52-
</div>
53-
54-
<div style={{ display: "flex", gap: 24, flex: 1 }}>
55-
{/* Primary View */}
56-
<div className="glass-panel" style={{ flex: 1, borderRadius: 12, padding: 24 }}>
57-
<h3 style={{ color: "#58a6ff", margin: "0 0 16px 0", borderBottom: "1px solid rgba(88,166,255,0.2)", paddingBottom: 8 }}>Primary Entity</h3>
58-
<label style={{ display: "block", color: "#c9d1d9", marginBottom: 8, fontSize: 13 }}>Primary Node ID</label>
59-
<input
60-
value={primaryId} onChange={e => setPrimaryId(e.target.value)}
61-
style={{ width: "100%", background: "rgba(0,0,0,0.3)", border: "1px solid rgba(255,255,255,0.1)", color: "#fff", padding: "8px 12px", borderRadius: 6, marginBottom: 24 }}
62-
/>
63-
64-
<div style={{ background: "rgba(0,0,0,0.2)", padding: 16, borderRadius: 6 }}>
65-
<div style={{ color: "#8b949e", fontSize: 12, marginBottom: 4 }}>Name</div>
66-
<div style={{ color: "#fff", fontSize: 14 }}>Sample Company Inc.</div>
67-
68-
<div style={{ color: "#8b949e", fontSize: 12, marginTop: 16, marginBottom: 4 }}>Founded</div>
69-
<div style={{ color: "#fff", fontSize: 14 }}>2004-05-12</div>
46+
<div className="ws-page ws-scroll">
47+
<div className="ws-padded" style={{ display: "flex", flexDirection: "column", gap: 22, maxWidth: 1000, margin: "0 auto", width: "100%" }}>
48+
{/* Header */}
49+
<div style={{ display: "flex", alignItems: "center", gap: 14 }}>
50+
<div style={{ width: 42, height: 42, borderRadius: 13, background: "var(--ws-purple-soft)", border: "1px solid rgba(192,132,252,0.3)", display: "grid", placeItems: "center", color: "var(--ws-purple)", flexShrink: 0 }}>
51+
<GitMerge size={20} />
52+
</div>
53+
<div>
54+
<h2 className="ws-title" style={{ fontSize: 18 }}>Entity Diff &amp; Merge</h2>
55+
<div className="ws-body" style={{ marginTop: 2 }}>Compare suspected duplicates side-by-side and reconcile them into a single canonical entity.</div>
7056
</div>
7157
</div>
7258

73-
{/* Duplicate View */}
74-
<div className="glass-panel" style={{ flex: 1, borderRadius: 12, padding: 24 }}>
75-
<h3 style={{ color: "#ff7b72", margin: "0 0 16px 0", borderBottom: "1px solid rgba(255,123,114,0.2)", paddingBottom: 8 }}>Duplicate Entity</h3>
76-
<label style={{ display: "block", color: "#c9d1d9", marginBottom: 8, fontSize: 13 }}>Duplicate Node ID</label>
77-
<input
78-
value={duplicateId} onChange={e => setDuplicateId(e.target.value)}
79-
style={{ width: "100%", background: "rgba(0,0,0,0.3)", border: "1px solid rgba(255,255,255,0.1)", color: "#fff", padding: "8px 12px", borderRadius: 6, marginBottom: 24 }}
80-
/>
81-
82-
<div style={{ background: "rgba(0,0,0,0.2)", padding: 16, borderRadius: 6 }}>
83-
<div style={{ color: "#d2a8ff", fontSize: 12, marginBottom: 4 }}>Name</div>
84-
{/* Amber highlight for differing values */}
85-
<div style={{ color: "#d29922", fontSize: 14, fontWeight: "bold" }}>Sample Company</div>
59+
{/* ID inputs */}
60+
<div style={{ display: "grid", gridTemplateColumns: "1fr auto 1fr", gap: 12, alignItems: "end" }}>
61+
<div>
62+
<label className="ws-label">Primary Node ID (keep)</label>
63+
<input className="ws-input" value={primaryId} onChange={(e) => { setPrimaryId(e.target.value); setStatus("idle"); }} placeholder="e.g. n-primary-1" />
64+
</div>
65+
<div style={{ paddingBottom: 2, color: "var(--ws-text-dim)" }}>
66+
<ArrowRight size={18} />
67+
</div>
68+
<div>
69+
<label className="ws-label">Duplicate Node ID (remove)</label>
70+
<input className="ws-input" value={duplicateId} onChange={(e) => { setDuplicateId(e.target.value); setStatus("idle"); }} placeholder="e.g. n-dup-2" />
71+
</div>
72+
</div>
8673

87-
<div style={{ color: "#8b949e", fontSize: 12, marginTop: 16, marginBottom: 4 }}>Founded</div>
88-
<div style={{ color: "#fff", fontSize: 14 }}>2004-05-12</div>
74+
{/* Diff table */}
75+
<div className="ws-card" style={{ padding: 0, overflow: "hidden" }}>
76+
<div style={{ padding: "6px 16px", background: "rgba(242,182,109,0.06)", borderBottom: "1px solid rgba(242,182,109,0.15)", display: "flex", alignItems: "center", gap: 6 }}>
77+
<span style={{ fontSize: 10, fontWeight: 700, color: "var(--ws-amber)", letterSpacing: "0.08em", textTransform: "uppercase" }}>Sample preview</span>
78+
<span style={{ fontSize: 11, color: "var(--ws-text-dim)" }}>— field comparison will load from the graph once the backend is connected</span>
8979
</div>
80+
<div style={{ display: "grid", gridTemplateColumns: "140px 1fr 1fr", background: "rgba(0,0,0,0.28)", borderBottom: "1px solid var(--ws-border)" }}>
81+
<div style={{ padding: "10px 16px", fontSize: 11, fontWeight: 700, color: "var(--ws-text-dim)", letterSpacing: "0.08em", textTransform: "uppercase" }}>Field</div>
82+
<div style={{ padding: "10px 16px", fontSize: 11, fontWeight: 700, color: "var(--ws-accent)", letterSpacing: "0.08em", textTransform: "uppercase", borderLeft: "1px solid var(--ws-border)" }}>Primary (keep)</div>
83+
<div style={{ padding: "10px 16px", fontSize: 11, fontWeight: 700, color: "#fca5a5", letterSpacing: "0.08em", textTransform: "uppercase", borderLeft: "1px solid var(--ws-border)" }}>Duplicate (remove)</div>
84+
</div>
85+
{MOCK_FIELDS.map((row) => (
86+
<div key={row.label} style={{ display: "grid", gridTemplateColumns: "140px 1fr 1fr", borderBottom: "1px solid rgba(74,163,255,0.06)", background: row.differs ? "rgba(242,182,109,0.03)" : "transparent" }}>
87+
<div style={{ padding: "12px 16px", fontSize: 12, fontWeight: 600, color: "var(--ws-text-dim)" }}>{row.label}</div>
88+
<div style={{ padding: "12px 16px", fontSize: 13, color: "var(--ws-text)", borderLeft: "1px solid var(--ws-border)" }}>{row.primary}</div>
89+
<div style={{ padding: "12px 16px", fontSize: 13, color: row.differs ? "#fbbf24" : "var(--ws-text)", fontWeight: row.differs ? 700 : 400, borderLeft: "1px solid var(--ws-border)" }}>
90+
{row.duplicate}
91+
{row.differs && <span className="ws-pill ws-pill--amber" style={{ marginLeft: 8, fontSize: 9 }}>diff</span>}
92+
</div>
93+
</div>
94+
))}
9095
</div>
91-
</div>
9296

93-
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
94-
<div style={{ color: "#58a6ff" }}>{msg}</div>
95-
<button
96-
onClick={handleMerge}
97-
style={{ background: "#238636", color: "#fff", border: "none", padding: "10px 24px", borderRadius: 6, fontWeight: 600, cursor: "pointer", fontSize: 16 }}
98-
>
99-
Confirm Merge
100-
</button>
97+
{/* Status & action */}
98+
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
99+
{status === "success" && (
100+
<div style={{ display: "flex", alignItems: "center", gap: 7, color: "#6ee7b7", fontSize: 13 }}>
101+
<CheckCircle2 size={15} />{msg}
102+
</div>
103+
)}
104+
{status === "error" && (
105+
<div style={{ display: "flex", alignItems: "center", gap: 7, color: "#fca5a5", fontSize: 13 }}>
106+
<AlertCircle size={15} />{msg}
107+
</div>
108+
)}
109+
<button
110+
className="ws-btn ws-btn--primary"
111+
onClick={handleMerge}
112+
disabled={status === "loading" || !primaryId || !duplicateId}
113+
style={{ marginLeft: "auto" }}
114+
>
115+
{status === "loading" ? <><Loader2 size={14} className="ws-spin" />Merging…</> : <><GitMerge size={14} />Confirm Merge</>}
116+
</button>
117+
</div>
101118
</div>
102-
103119
</div>
104120
);
105121
}

0 commit comments

Comments
 (0)