@ltm-blueverse/alpha-semantic-hub 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import {
3
3
  Database, Plug, CheckCircle2, AlertCircle, Loader2, FileText, Globe,
4
- Boxes, Cloud, Server, Play, ChevronDown, KeyRound, Save, Trash2, Lock,
4
+ Boxes, Cloud, Server, Play, KeyRound, Save, Trash2, Lock,
5
5
  } from "lucide-react";
6
6
 
7
7
  type ConnField = {
@@ -47,7 +47,8 @@ function nestedSet(obj: Record<string, unknown>, path: string, value: unknown) {
47
47
  export function IngestWorkspace() {
48
48
  const [categories, setCategories] = useState<Category[]>([]);
49
49
  const [active, setActive] = useState<Connector | null>(null);
50
- const [pickerOpen, setPickerOpen] = useState(false);
50
+ const [activeCategory, setActiveCategory] = useState<string>("");
51
+ const [search, setSearch] = useState("");
51
52
  const [credentials, setCredentials] = useState<Credential[]>([]);
52
53
  const [credentialId, setCredentialId] = useState("");
53
54
  const [values, setValues] = useState<FieldValues>({});
@@ -80,7 +81,9 @@ export function IngestWorkspace() {
80
81
  fetch("/api/ingest/sources").then((r) => r.json()).then((d) => {
81
82
  const cats: Category[] = d.categories ?? [];
82
83
  setCategories(cats);
83
- const first = cats[0]?.connectors[0];
84
+ const firstCat = cats[0];
85
+ if (firstCat) setActiveCategory((c) => c || firstCat.category);
86
+ const first = firstCat?.connectors[0];
84
87
  if (first) setActive(first);
85
88
  }).catch(() => {});
86
89
  loadCredentials();
@@ -89,7 +92,6 @@ export function IngestWorkspace() {
89
92
 
90
93
  function selectConnector(c: Connector) {
91
94
  setActive(c);
92
- setPickerOpen(false);
93
95
  setValues({});
94
96
  setSourceRef("");
95
97
  setValidation(null);
@@ -207,7 +209,6 @@ export function IngestWorkspace() {
207
209
  }
208
210
  }
209
211
 
210
- const ActiveIcon = active ? (KIND_ICONS[active.kind] ?? Database) : Database;
211
212
  const vaultForKind = credentials.filter((c) => c.id.startsWith(active?.kind ?? "___"));
212
213
 
213
214
  return (
@@ -227,48 +228,72 @@ export function IngestWorkspace() {
227
228
  </div>
228
229
  </div>
229
230
 
230
- {/* Connector picker */}
231
- <div className="ws-card" style={{ padding: 18, position: "relative" }}>
232
- <div className="ws-label">Connector</div>
233
- <button className="ws-btn" onClick={() => setPickerOpen((v) => !v)}
234
- style={{ width: "100%", justifyContent: "space-between", fontSize: 13 }}>
235
- <span style={{ display: "flex", alignItems: "center", gap: 8 }}>
236
- <ActiveIcon size={15} /> {active?.label ?? "Select a connector…"}
231
+ {/* Connector browser: category sidebar + searchable connector grid.
232
+ Deliberately NOT an absolutely-positioned dropdown — .ws-card sets
233
+ overflow:hidden, which clipped the overlay to one visible row. */}
234
+ <div className="ws-card" style={{ padding: 18, display: "flex", flexDirection: "column", gap: 14 }}>
235
+ <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
236
+ <div className="ws-label" style={{ margin: 0 }}>Connector</div>
237
+ <input className="ws-input" style={{ flex: 1, minWidth: 200 }}
238
+ placeholder="Search connectors…" value={search}
239
+ onChange={(e) => setSearch(e.target.value)} />
240
+ <span style={{ fontSize: 12, color: "var(--ws-text-muted)" }}>
241
+ {search
242
+ ? `${categories.flatMap((c) => c.connectors).filter((c) => c.label.toLowerCase().includes(search.toLowerCase())).length} match`
243
+ : `${categories.reduce((n, c) => n + c.connectors.length, 0)} total`}
237
244
  </span>
238
- <ChevronDown size={15} />
239
- </button>
240
-
241
- {pickerOpen ? (
242
- <div style={{
243
- position: "absolute", top: "100%", left: 18, right: 18, zIndex: 50, marginTop: 6,
244
- background: "#fff", border: "1px solid var(--ws-border)", borderRadius: "var(--ws-radius)",
245
- boxShadow: "0 12px 32px rgba(0,0,0,0.18)", maxHeight: 420, overflowY: "auto", padding: 10,
246
- }}>
247
- {categories.map((cat) => (
248
- <div key={cat.category} style={{ marginBottom: 8 }}>
249
- <div className="ws-label" style={{ padding: "6px 8px", margin: 0, opacity: 0.75 }}>{cat.category}</div>
250
- {cat.connectors.map((c) => {
251
- const Icon = KIND_ICONS[c.kind] ?? Database;
252
- return (
253
- <button key={c.kind} className="ws-btn" data-active={active?.kind === c.kind}
254
- onClick={() => selectConnector(c)}
255
- style={{ width: "100%", justifyContent: "flex-start", fontSize: 12.5, marginBottom: 2 }}>
256
- <Icon size={14} />
257
- <span style={{ flex: 1, textAlign: "left" }}>{c.label}</span>
258
- {c.extra_dependency ? (
259
- <span title={`Extra dependency: ${c.extra_dependency}`}
260
- style={{ fontSize: 10, opacity: 0.6, color: "#c9a227" }}>dep</span>
261
- ) : null}
262
- </button>
263
- );
264
- })}
265
- </div>
266
- ))}
245
+ </div>
246
+
247
+ <div style={{ display: "grid", gridTemplateColumns: "230px 1fr", gap: 14, alignItems: "start" }}>
248
+ {/* Category sidebar — always visible, never clipped */}
249
+ <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
250
+ {categories.map((cat) => {
251
+ const isActiveCat = !search && activeCategory === cat.category;
252
+ const matches = search
253
+ ? cat.connectors.filter((c) => c.label.toLowerCase().includes(search.toLowerCase())).length
254
+ : cat.connectors.length;
255
+ if (search && matches === 0) return null;
256
+ return (
257
+ <button key={cat.category} className="ws-btn" data-active={isActiveCat}
258
+ onClick={() => { setActiveCategory(cat.category); setSearch(""); }}
259
+ style={{ justifyContent: "space-between", fontSize: 12.5 }}>
260
+ <span>{cat.category}</span>
261
+ <span style={{ opacity: 0.6, fontSize: 11 }}>{matches}</span>
262
+ </button>
263
+ );
264
+ })}
267
265
  </div>
268
- ) : null}
266
+
267
+ {/* Connector grid — shows the active category, or search results */}
268
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(215px, 1fr))", gap: 8 }}>
269
+ {(search
270
+ ? categories.flatMap((c) => c.connectors)
271
+ .filter((c) => c.label.toLowerCase().includes(search.toLowerCase()))
272
+ : categories.find((c) => c.category === activeCategory)?.connectors ?? []
273
+ ).map((c) => {
274
+ const Icon = KIND_ICONS[c.kind] ?? Database;
275
+ const isSel = active?.kind === c.kind;
276
+ return (
277
+ <button key={c.kind} className="ws-btn" data-active={isSel}
278
+ onClick={() => selectConnector(c)}
279
+ style={{ justifyContent: "flex-start", fontSize: 12.5, textAlign: "left" }}>
280
+ <Icon size={14} />
281
+ <span style={{ flex: 1 }}>{c.label}</span>
282
+ {c.extra_dependency ? (
283
+ <span title={`Needs: ${c.extra_dependency}`}
284
+ style={{ fontSize: 10, color: "#c9a227" }}>dep</span>
285
+ ) : null}
286
+ </button>
287
+ );
288
+ })}
289
+ </div>
290
+ </div>
269
291
 
270
292
  {active ? (
271
- <div style={{ marginTop: 12, display: "flex", flexDirection: "column", gap: 4 }}>
293
+ <div style={{ display: "flex", flexDirection: "column", gap: 4, paddingTop: 10, borderTop: "1px solid var(--ws-border)" }}>
294
+ <div style={{ fontSize: 12.5, fontWeight: 700, color: "var(--ws-text)" }}>
295
+ Selected: {active.label}
296
+ </div>
272
297
  {active.credential_hint ? (
273
298
  <div className="ws-body" style={{ fontSize: 12 }}>
274
299
  <KeyRound size={12} style={{ verticalAlign: -2, marginRight: 6 }} />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ltm-blueverse/alpha-semantic-hub",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Alpha Semantic Hub \u2014 Graph-Native Infrastructure for Context and Accountable AI Systems",
5
5
  "keywords": [
6
6
  "alpha-semantic-hub",