@opentrace/components 0.1.1-rc.30 → 0.1.1-rc.44

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.
@@ -0,0 +1,990 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { useState, useRef, useEffect, useMemo } from "react";
3
+ function detectProvider(url) {
4
+ const lower = url.toLowerCase();
5
+ if (lower.includes("github")) return "github";
6
+ if (lower.includes("gitlab")) return "gitlab";
7
+ if (lower.includes("bitbucket")) return "bitbucket";
8
+ if (lower.includes("dev.azure.com") || lower.includes("visualstudio.com"))
9
+ return "azuredevops";
10
+ return null;
11
+ }
12
+ const HISTORY_KEY = "ot_repo_history";
13
+ const MAX_HISTORY = 5;
14
+ function loadHistory() {
15
+ try {
16
+ const raw = localStorage.getItem(HISTORY_KEY);
17
+ if (!raw) return [];
18
+ const parsed = JSON.parse(raw);
19
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
20
+ } catch {
21
+ return [];
22
+ }
23
+ }
24
+ function saveToHistory(url) {
25
+ const history = loadHistory().filter((u) => u !== url);
26
+ history.unshift(url);
27
+ localStorage.setItem(
28
+ HISTORY_KEY,
29
+ JSON.stringify(history.slice(0, MAX_HISTORY))
30
+ );
31
+ }
32
+ function removeFromHistory(url) {
33
+ const updated = loadHistory().filter((u) => u !== url);
34
+ localStorage.setItem(HISTORY_KEY, JSON.stringify(updated));
35
+ return updated;
36
+ }
37
+ const PROVIDER_ORDER = [
38
+ "github",
39
+ "gitlab",
40
+ "bitbucket",
41
+ "azuredevops"
42
+ ];
43
+ const QUADRANT_CLIPS = {
44
+ github: {
45
+ // top triangle
46
+ quarter: "polygon(0% 0%, 100% 0%, 50% 50%, 50% 50%)",
47
+ full: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
48
+ hidden: "polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%)"
49
+ },
50
+ gitlab: {
51
+ // right triangle
52
+ quarter: "polygon(50% 50%, 100% 0%, 100% 100%, 50% 50%)",
53
+ full: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
54
+ hidden: "polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%)"
55
+ },
56
+ bitbucket: {
57
+ // bottom triangle
58
+ quarter: "polygon(50% 50%, 50% 50%, 100% 100%, 0% 100%)",
59
+ full: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
60
+ hidden: "polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%)"
61
+ },
62
+ azuredevops: {
63
+ // left triangle
64
+ quarter: "polygon(0% 0%, 50% 50%, 50% 50%, 0% 100%)",
65
+ full: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
66
+ hidden: "polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%)"
67
+ }
68
+ };
69
+ const PROVIDER_FULL_SVG = {
70
+ github: () => /* @__PURE__ */ jsx("svg", { width: "100%", height: "100%", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" }) }),
71
+ gitlab: () => /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 380 380", fill: "currentColor", children: [
72
+ /* @__PURE__ */ jsx("path", { d: "M190 353.9L131.1 172.8h117.8L190 353.9z", opacity: "0.85" }),
73
+ /* @__PURE__ */ jsx("path", { d: "M190 353.9L131.1 172.8H15.6L190 353.9z", opacity: "0.7" }),
74
+ /* @__PURE__ */ jsx(
75
+ "path",
76
+ {
77
+ d: "M15.6 172.8L0.4 219.5c-1.4 4.3 0.1 9 3.8 11.7L190 353.9 15.6 172.8z",
78
+ opacity: "0.55"
79
+ }
80
+ ),
81
+ /* @__PURE__ */ jsx(
82
+ "path",
83
+ {
84
+ d: "M15.6 172.8h115.5L87.6 26.5c-1.6-4.9-8.5-4.9-10.1 0L15.6 172.8z",
85
+ opacity: "0.85"
86
+ }
87
+ ),
88
+ /* @__PURE__ */ jsx("path", { d: "M190 353.9l58.9-181.1h115.5L190 353.9z", opacity: "0.7" }),
89
+ /* @__PURE__ */ jsx(
90
+ "path",
91
+ {
92
+ d: "M364.4 172.8l15.2 46.7c1.4 4.3-0.1 9-3.8 11.7L190 353.9l174.4-181.1z",
93
+ opacity: "0.55"
94
+ }
95
+ ),
96
+ /* @__PURE__ */ jsx(
97
+ "path",
98
+ {
99
+ d: "M364.4 172.8H248.9l43.5-146.3c1.6-4.9 8.5-4.9 10.1 0l61.9 146.3z",
100
+ opacity: "0.85"
101
+ }
102
+ )
103
+ ] }),
104
+ bitbucket: () => /* @__PURE__ */ jsx("svg", { width: "100%", height: "100%", viewBox: "0 0 32 32", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M2.278 2.133a1.07 1.07 0 00-1.07 1.236l4.058 24.637a1.45 1.45 0 001.417 1.195h19.1a1.07 1.07 0 001.07-.903l4.058-24.93a1.07 1.07 0 00-1.07-1.236zm16.7 17.757h-6.1l-1.647-8.613h9.2z" }) }),
105
+ azuredevops: () => /* @__PURE__ */ jsx("svg", { width: "100%", height: "100%", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M15 3.622v8.512L11.5 15l-5.425-1.975v1.958L3.004 10.97l8.951.7V4.005L15 3.622zm-2.984.428L6.994 1v2.001L2.382 4.356 1 6.13v4.029l1.978.873V5.869l9.038-1.819z" }) })
106
+ };
107
+ function ProviderIconStrip({ selected }) {
108
+ return /* @__PURE__ */ jsxs("div", { className: "hero-icon" + (selected ? " hero-icon--provider" : ""), children: [
109
+ /* @__PURE__ */ jsxs(
110
+ "svg",
111
+ {
112
+ className: `quad-dividers${selected ? " quad-dividers--hidden" : ""}`,
113
+ width: "100%",
114
+ height: "100%",
115
+ viewBox: "0 0 64 64",
116
+ children: [
117
+ /* @__PURE__ */ jsx(
118
+ "line",
119
+ {
120
+ x1: "0",
121
+ y1: "0",
122
+ x2: "64",
123
+ y2: "64",
124
+ stroke: "currentColor",
125
+ strokeWidth: "1.5",
126
+ opacity: "0.2"
127
+ }
128
+ ),
129
+ /* @__PURE__ */ jsx(
130
+ "line",
131
+ {
132
+ x1: "64",
133
+ y1: "0",
134
+ x2: "0",
135
+ y2: "64",
136
+ stroke: "currentColor",
137
+ strokeWidth: "1.5",
138
+ opacity: "0.2"
139
+ }
140
+ )
141
+ ]
142
+ }
143
+ ),
144
+ PROVIDER_ORDER.map((key) => {
145
+ const Icon = PROVIDER_FULL_SVG[key];
146
+ const clips = QUADRANT_CLIPS[key];
147
+ const clip = !selected ? clips.quarter : key === selected ? clips.full : clips.hidden;
148
+ return /* @__PURE__ */ jsx("div", { className: "quad-layer", style: { clipPath: clip }, children: /* @__PURE__ */ jsx(Icon, {}) }, key);
149
+ })
150
+ ] });
151
+ }
152
+ function FolderIcon() {
153
+ return /* @__PURE__ */ jsx(
154
+ "svg",
155
+ {
156
+ width: "38",
157
+ height: "38",
158
+ viewBox: "0 0 24 24",
159
+ fill: "none",
160
+ stroke: "currentColor",
161
+ strokeWidth: "1.5",
162
+ strokeLinecap: "round",
163
+ strokeLinejoin: "round",
164
+ children: /* @__PURE__ */ jsx("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" })
165
+ }
166
+ );
167
+ }
168
+ const PROVIDER_DISPLAY_NAME = {
169
+ github: "GitHub",
170
+ gitlab: "GitLab",
171
+ bitbucket: "Bitbucket",
172
+ azuredevops: "Azure DevOps"
173
+ };
174
+ const EXAMPLE_REPOS = [
175
+ {
176
+ name: "OpenTrace",
177
+ url: "https://github.com/opentrace/opentrace",
178
+ description: "Knowledge graph for system architecture and code structure",
179
+ size: "M"
180
+ },
181
+ {
182
+ name: "OpenTelemetry Demo",
183
+ url: "https://github.com/open-telemetry/opentelemetry-demo",
184
+ description: "Microservices demo with OTel instrumentation",
185
+ size: "M"
186
+ },
187
+ {
188
+ name: "Express.js",
189
+ url: "https://github.com/expressjs/express",
190
+ description: "Fast, minimalist web framework for Node.js",
191
+ size: "S"
192
+ },
193
+ {
194
+ name: "Podinfo",
195
+ url: "https://github.com/stefanprodan/podinfo",
196
+ description: "Go microservice template for Kubernetes",
197
+ size: "S"
198
+ },
199
+ {
200
+ name: "Grafana",
201
+ url: "https://github.com/grafana/grafana",
202
+ description: "Open-source observability and monitoring platform",
203
+ size: "L"
204
+ },
205
+ {
206
+ name: "Linux",
207
+ url: "https://github.com/torvalds/linux",
208
+ description: "Linux kernel source tree",
209
+ size: "L"
210
+ }
211
+ ];
212
+ function AddRepoModal({
213
+ onClose,
214
+ onSubmit,
215
+ dismissable = true,
216
+ onValidate
217
+ }) {
218
+ const [source, setSource] = useState("url");
219
+ const [repoUrl, setRepoUrl] = useState("");
220
+ const [history, setHistory] = useState(loadHistory);
221
+ const [showHistory, setShowHistory] = useState(false);
222
+ const [ref, setRef] = useState("");
223
+ const [pat, setPat] = useState("");
224
+ const [showPat, setShowPat] = useState(false);
225
+ const [loading, setLoading] = useState(false);
226
+ const [error, setError] = useState(null);
227
+ const [selectedFiles, setSelectedFiles] = useState(null);
228
+ const fileInputRef = useRef(null);
229
+ const urlInputRef = useRef(null);
230
+ const dropdownRef = useRef(null);
231
+ useEffect(() => {
232
+ if (source === "url") {
233
+ urlInputRef.current?.focus();
234
+ }
235
+ }, [source]);
236
+ useEffect(() => {
237
+ function handleClick(e) {
238
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target) && e.target !== urlInputRef.current) {
239
+ setShowHistory(false);
240
+ }
241
+ }
242
+ document.addEventListener("mousedown", handleClick);
243
+ return () => document.removeEventListener("mousedown", handleClick);
244
+ }, []);
245
+ const filteredHistory = history.filter(
246
+ (url) => !repoUrl || url.toLowerCase().includes(repoUrl.toLowerCase())
247
+ );
248
+ const provider = source === "url" ? detectProvider(repoUrl) : null;
249
+ const patStorageKey = provider ? `ot_${provider}_pat` : null;
250
+ const validationMessage = useMemo(() => {
251
+ if (source !== "url" || !repoUrl.trim() || !onValidate) return null;
252
+ return onValidate(repoUrl);
253
+ }, [source, repoUrl, onValidate]);
254
+ const directoryName = selectedFiles?.[0]?.webkitRelativePath?.split("/")[0] ?? "";
255
+ useEffect(() => {
256
+ if (!provider || !patStorageKey) return;
257
+ const saved = localStorage.getItem(patStorageKey);
258
+ if (saved) setPat(saved);
259
+ else setPat("");
260
+ }, [provider, patStorageKey]);
261
+ useEffect(() => {
262
+ setError(null);
263
+ }, [source]);
264
+ function handleSubmit(e) {
265
+ e.preventDefault();
266
+ if (source === "directory") {
267
+ if (!selectedFiles || selectedFiles.length === 0) {
268
+ setError("Select a directory first.");
269
+ return;
270
+ }
271
+ setError(null);
272
+ setLoading(true);
273
+ onSubmit({
274
+ type: "index-directory",
275
+ files: selectedFiles,
276
+ name: directoryName || "local"
277
+ });
278
+ return;
279
+ }
280
+ if (!provider) {
281
+ setError(
282
+ "Enter a GitHub, GitLab, Bitbucket, or Azure DevOps repository URL."
283
+ );
284
+ return;
285
+ }
286
+ if (validationMessage) {
287
+ return;
288
+ }
289
+ setError(null);
290
+ setLoading(true);
291
+ if (patStorageKey) {
292
+ if (pat) localStorage.setItem(patStorageKey, pat);
293
+ else localStorage.removeItem(patStorageKey);
294
+ }
295
+ saveToHistory(repoUrl);
296
+ onSubmit({
297
+ type: "index-repo",
298
+ repoUrl,
299
+ token: pat || void 0,
300
+ ref: ref || void 0
301
+ });
302
+ }
303
+ const heroIcon = source === "directory" ? /* @__PURE__ */ jsx("div", { className: "hero-icon hero-icon--provider", children: /* @__PURE__ */ jsx(FolderIcon, {}) }) : /* @__PURE__ */ jsx(ProviderIconStrip, { selected: provider });
304
+ const title = source === "directory" ? "Add Local Directory" : provider ? `Add from ${PROVIDER_DISPLAY_NAME[provider]}` : "Add Repository";
305
+ const subtitle = source === "directory" ? "Select a directory to index its structure into the graph" : provider ? `Enter a ${PROVIDER_DISPLAY_NAME[provider]} repository URL to index` : "Enter a repository URL to index its structure into the graph";
306
+ return /* @__PURE__ */ jsx("div", { className: "modal-backdrop", onClick: dismissable ? onClose : void 0, children: /* @__PURE__ */ jsxs("div", { className: "modal-card", onClick: (e) => e.stopPropagation(), children: [
307
+ /* @__PURE__ */ jsx("div", { className: "source-toggle", children: /* @__PURE__ */ jsxs("div", { className: "chip-toggle", children: [
308
+ /* @__PURE__ */ jsx(
309
+ "button",
310
+ {
311
+ type: "button",
312
+ className: `chip-toggle-btn${source === "url" ? " active" : ""}`,
313
+ onClick: () => setSource("url"),
314
+ children: "URL"
315
+ }
316
+ ),
317
+ /* @__PURE__ */ jsx(
318
+ "button",
319
+ {
320
+ type: "button",
321
+ className: `chip-toggle-btn${source === "directory" ? " active" : ""}`,
322
+ onClick: () => setSource("directory"),
323
+ children: "Directory"
324
+ }
325
+ )
326
+ ] }) }),
327
+ /* @__PURE__ */ jsxs("div", { className: "form-hero", children: [
328
+ heroIcon,
329
+ /* @__PURE__ */ jsx("h2", { children: title }),
330
+ /* @__PURE__ */ jsx("p", { className: "hero-subtitle", children: subtitle })
331
+ ] }),
332
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
333
+ /* @__PURE__ */ jsx("div", { className: "form-fields", children: source === "url" ? /* @__PURE__ */ jsxs(Fragment, { children: [
334
+ /* @__PURE__ */ jsxs("div", { className: "form-info", children: [
335
+ /* @__PURE__ */ jsxs(
336
+ "svg",
337
+ {
338
+ width: "14",
339
+ height: "14",
340
+ viewBox: "0 0 24 24",
341
+ fill: "none",
342
+ stroke: "currentColor",
343
+ strokeWidth: "2",
344
+ strokeLinecap: "round",
345
+ strokeLinejoin: "round",
346
+ children: [
347
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
348
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
349
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
350
+ ]
351
+ }
352
+ ),
353
+ /* @__PURE__ */ jsx("span", { children: "Repository archives are fetched through the OpenTrace API server to avoid browser CORS restrictions. Your access token (if provided) is forwarded but never stored on the server." })
354
+ ] }),
355
+ !provider && /* @__PURE__ */ jsxs("div", { className: "example-repos", children: [
356
+ /* @__PURE__ */ jsx("span", { className: "example-repos-label", children: "Examples:" }),
357
+ EXAMPLE_REPOS.map((repo) => /* @__PURE__ */ jsxs(
358
+ "button",
359
+ {
360
+ type: "button",
361
+ className: "example-repo-chip",
362
+ onClick: () => setRepoUrl(repo.url),
363
+ title: repo.description,
364
+ children: [
365
+ repo.name,
366
+ /* @__PURE__ */ jsx(
367
+ "span",
368
+ {
369
+ className: `example-repo-size example-repo-size--${repo.size.toLowerCase()}`,
370
+ children: repo.size
371
+ }
372
+ )
373
+ ]
374
+ },
375
+ repo.url
376
+ ))
377
+ ] }),
378
+ /* @__PURE__ */ jsxs("div", { className: "autocomplete-wrapper", children: [
379
+ /* @__PURE__ */ jsx(
380
+ "input",
381
+ {
382
+ ref: urlInputRef,
383
+ type: "text",
384
+ required: true,
385
+ className: "input-pill",
386
+ placeholder: "https://github.com/owner/repo or https://bitbucket.org/ws/repo",
387
+ value: repoUrl,
388
+ onChange: (e) => {
389
+ setRepoUrl(e.target.value);
390
+ if (!showHistory) setShowHistory(true);
391
+ },
392
+ onFocus: () => setShowHistory(true),
393
+ onKeyDown: (e) => {
394
+ if (e.key === "Escape") setShowHistory(false);
395
+ },
396
+ autoFocus: true,
397
+ autoComplete: "off",
398
+ "data-1p-ignore": true,
399
+ "data-lpignore": "true",
400
+ "data-testid": "repo-url-input"
401
+ }
402
+ ),
403
+ showHistory && filteredHistory.length > 0 && /* @__PURE__ */ jsxs("div", { ref: dropdownRef, className: "autocomplete-dropdown", children: [
404
+ /* @__PURE__ */ jsx("div", { className: "autocomplete-label", children: "Recent" }),
405
+ filteredHistory.map((url) => /* @__PURE__ */ jsxs(
406
+ "div",
407
+ {
408
+ className: "autocomplete-item",
409
+ role: "option",
410
+ onMouseDown: (e) => {
411
+ e.preventDefault();
412
+ setRepoUrl(url);
413
+ setShowHistory(false);
414
+ urlInputRef.current?.focus();
415
+ },
416
+ children: [
417
+ /* @__PURE__ */ jsxs(
418
+ "svg",
419
+ {
420
+ width: "14",
421
+ height: "14",
422
+ viewBox: "0 0 24 24",
423
+ fill: "none",
424
+ stroke: "currentColor",
425
+ strokeWidth: "2",
426
+ strokeLinecap: "round",
427
+ strokeLinejoin: "round",
428
+ children: [
429
+ /* @__PURE__ */ jsx("polyline", { points: "1 4 1 10 7 10" }),
430
+ /* @__PURE__ */ jsx("path", { d: "M3.51 15a9 9 0 1 0 2.13-9.36L1 10" })
431
+ ]
432
+ }
433
+ ),
434
+ /* @__PURE__ */ jsx("span", { className: "autocomplete-item-text", children: url.replace(/^https?:\/\/(www\.)?/, "").replace(/\.git$/, "") }),
435
+ /* @__PURE__ */ jsx(
436
+ "button",
437
+ {
438
+ type: "button",
439
+ className: "autocomplete-item-remove",
440
+ onMouseDown: (e) => {
441
+ e.preventDefault();
442
+ e.stopPropagation();
443
+ setHistory(removeFromHistory(url));
444
+ },
445
+ "aria-label": "Remove from history",
446
+ children: /* @__PURE__ */ jsxs(
447
+ "svg",
448
+ {
449
+ width: "12",
450
+ height: "12",
451
+ viewBox: "0 0 24 24",
452
+ fill: "none",
453
+ stroke: "currentColor",
454
+ strokeWidth: "2",
455
+ strokeLinecap: "round",
456
+ strokeLinejoin: "round",
457
+ children: [
458
+ /* @__PURE__ */ jsx("polyline", { points: "3 6 5 6 21 6" }),
459
+ /* @__PURE__ */ jsx("path", { d: "M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" }),
460
+ /* @__PURE__ */ jsx("path", { d: "M10 11v6" }),
461
+ /* @__PURE__ */ jsx("path", { d: "M14 11v6" })
462
+ ]
463
+ }
464
+ )
465
+ }
466
+ )
467
+ ]
468
+ },
469
+ url
470
+ ))
471
+ ] })
472
+ ] }),
473
+ provider && /* @__PURE__ */ jsxs("div", { className: "input-pill-row", children: [
474
+ /* @__PURE__ */ jsxs(
475
+ "svg",
476
+ {
477
+ className: "input-icon",
478
+ width: "16",
479
+ height: "16",
480
+ viewBox: "0 0 24 24",
481
+ fill: "none",
482
+ stroke: "currentColor",
483
+ strokeWidth: "2",
484
+ strokeLinecap: "round",
485
+ strokeLinejoin: "round",
486
+ children: [
487
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
488
+ /* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
489
+ ]
490
+ }
491
+ ),
492
+ /* @__PURE__ */ jsx(
493
+ "input",
494
+ {
495
+ type: showPat ? "text" : "password",
496
+ className: "input-pill input-pill--icon",
497
+ placeholder: `${provider ? PROVIDER_DISPLAY_NAME[provider] : ""} access token (optional, for private repos)`,
498
+ value: pat,
499
+ onChange: (e) => setPat(e.target.value)
500
+ }
501
+ ),
502
+ /* @__PURE__ */ jsx(
503
+ "button",
504
+ {
505
+ type: "button",
506
+ className: "input-toggle",
507
+ onClick: () => setShowPat(!showPat),
508
+ tabIndex: -1,
509
+ "aria-label": showPat ? "Hide token" : "Show token",
510
+ children: showPat ? /* @__PURE__ */ jsxs(
511
+ "svg",
512
+ {
513
+ width: "16",
514
+ height: "16",
515
+ viewBox: "0 0 24 24",
516
+ fill: "none",
517
+ stroke: "currentColor",
518
+ strokeWidth: "2",
519
+ strokeLinecap: "round",
520
+ strokeLinejoin: "round",
521
+ children: [
522
+ /* @__PURE__ */ jsx("path", { d: "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94" }),
523
+ /* @__PURE__ */ jsx("path", { d: "M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19" }),
524
+ /* @__PURE__ */ jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23" }),
525
+ /* @__PURE__ */ jsx("path", { d: "M14.12 14.12a3 3 0 1 1-4.24-4.24" })
526
+ ]
527
+ }
528
+ ) : /* @__PURE__ */ jsxs(
529
+ "svg",
530
+ {
531
+ width: "16",
532
+ height: "16",
533
+ viewBox: "0 0 24 24",
534
+ fill: "none",
535
+ stroke: "currentColor",
536
+ strokeWidth: "2",
537
+ strokeLinecap: "round",
538
+ strokeLinejoin: "round",
539
+ children: [
540
+ /* @__PURE__ */ jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
541
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
542
+ ]
543
+ }
544
+ )
545
+ }
546
+ )
547
+ ] })
548
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "directory-picker", children: [
549
+ /* @__PURE__ */ jsx(
550
+ "input",
551
+ {
552
+ ref: fileInputRef,
553
+ type: "file",
554
+ webkitdirectory: "",
555
+ directory: "",
556
+ multiple: true,
557
+ className: "directory-input",
558
+ onChange: (e) => setSelectedFiles(e.target.files)
559
+ }
560
+ ),
561
+ /* @__PURE__ */ jsxs(
562
+ "button",
563
+ {
564
+ type: "button",
565
+ className: "input-pill directory-browse-btn",
566
+ onClick: () => fileInputRef.current?.click(),
567
+ children: [
568
+ /* @__PURE__ */ jsx(
569
+ "svg",
570
+ {
571
+ width: "16",
572
+ height: "16",
573
+ viewBox: "0 0 24 24",
574
+ fill: "none",
575
+ stroke: "currentColor",
576
+ strokeWidth: "2",
577
+ strokeLinecap: "round",
578
+ strokeLinejoin: "round",
579
+ children: /* @__PURE__ */ jsx("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" })
580
+ }
581
+ ),
582
+ directoryName ? /* @__PURE__ */ jsxs("span", { className: "directory-name", children: [
583
+ directoryName,
584
+ " ",
585
+ /* @__PURE__ */ jsxs("span", { className: "directory-count", children: [
586
+ "(",
587
+ selectedFiles?.length ?? 0,
588
+ " files)"
589
+ ] })
590
+ ] }) : "Choose Directory..."
591
+ ]
592
+ }
593
+ )
594
+ ] }) }),
595
+ error && /* @__PURE__ */ jsxs("div", { className: "form-error", children: [
596
+ /* @__PURE__ */ jsxs(
597
+ "svg",
598
+ {
599
+ width: "14",
600
+ height: "14",
601
+ viewBox: "0 0 24 24",
602
+ fill: "none",
603
+ stroke: "currentColor",
604
+ strokeWidth: "2",
605
+ strokeLinecap: "round",
606
+ strokeLinejoin: "round",
607
+ children: [
608
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
609
+ /* @__PURE__ */ jsx("line", { x1: "15", y1: "9", x2: "9", y2: "15" }),
610
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "9", x2: "15", y2: "15" })
611
+ ]
612
+ }
613
+ ),
614
+ /* @__PURE__ */ jsx("span", { children: error })
615
+ ] }),
616
+ validationMessage && /* @__PURE__ */ jsxs("div", { className: "form-indexed", children: [
617
+ /* @__PURE__ */ jsxs(
618
+ "svg",
619
+ {
620
+ width: "14",
621
+ height: "14",
622
+ viewBox: "0 0 24 24",
623
+ fill: "none",
624
+ stroke: "currentColor",
625
+ strokeWidth: "2",
626
+ strokeLinecap: "round",
627
+ strokeLinejoin: "round",
628
+ children: [
629
+ /* @__PURE__ */ jsx("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
630
+ /* @__PURE__ */ jsx("polyline", { points: "22 4 12 14.01 9 11.01" })
631
+ ]
632
+ }
633
+ ),
634
+ /* @__PURE__ */ jsx("span", { children: validationMessage })
635
+ ] }),
636
+ /* @__PURE__ */ jsx(
637
+ "button",
638
+ {
639
+ type: "submit",
640
+ className: "btn-cta",
641
+ disabled: loading || !!validationMessage,
642
+ children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
643
+ /* @__PURE__ */ jsx("span", { className: "btn-spinner" }),
644
+ "Indexing..."
645
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
646
+ "Add & Index",
647
+ /* @__PURE__ */ jsxs(
648
+ "svg",
649
+ {
650
+ width: "16",
651
+ height: "16",
652
+ viewBox: "0 0 24 24",
653
+ fill: "none",
654
+ stroke: "currentColor",
655
+ strokeWidth: "2.5",
656
+ strokeLinecap: "round",
657
+ strokeLinejoin: "round",
658
+ children: [
659
+ /* @__PURE__ */ jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" }),
660
+ /* @__PURE__ */ jsx("polyline", { points: "12 5 19 12 12 19" })
661
+ ]
662
+ }
663
+ )
664
+ ] })
665
+ }
666
+ ),
667
+ /* @__PURE__ */ jsxs("div", { className: "form-chips", children: [
668
+ source === "url" && /* @__PURE__ */ jsxs("div", { className: "chip", children: [
669
+ /* @__PURE__ */ jsxs(
670
+ "svg",
671
+ {
672
+ width: "12",
673
+ height: "12",
674
+ viewBox: "0 0 24 24",
675
+ fill: "none",
676
+ stroke: "currentColor",
677
+ strokeWidth: "2",
678
+ strokeLinecap: "round",
679
+ strokeLinejoin: "round",
680
+ children: [
681
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "3", x2: "6", y2: "15" }),
682
+ /* @__PURE__ */ jsx("circle", { cx: "18", cy: "6", r: "3" }),
683
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "18", r: "3" }),
684
+ /* @__PURE__ */ jsx("path", { d: "M18 9a9 9 0 0 1-9 9" })
685
+ ]
686
+ }
687
+ ),
688
+ /* @__PURE__ */ jsx(
689
+ "input",
690
+ {
691
+ type: "text",
692
+ className: "chip-input",
693
+ placeholder: "Branch: main",
694
+ value: ref,
695
+ onChange: (e) => setRef(e.target.value),
696
+ autoComplete: "off",
697
+ "data-1p-ignore": true,
698
+ "data-lpignore": "true"
699
+ }
700
+ )
701
+ ] }),
702
+ dismissable && /* @__PURE__ */ jsx("button", { type: "button", className: "chip", onClick: onClose, children: "Cancel" })
703
+ ] })
704
+ ] })
705
+ ] }) });
706
+ }
707
+ function formatMB(bytes) {
708
+ return (bytes / (1024 * 1024)).toFixed(1) + " MB";
709
+ }
710
+ function StageProgressRow({
711
+ label,
712
+ stage,
713
+ removing
714
+ }) {
715
+ const isCompleted = stage.status === "completed";
716
+ const isActive = stage.status === "active";
717
+ const indeterminate = isActive && stage.total === 0;
718
+ const pct = stage.total > 0 ? Math.min(100, stage.current / stage.total * 100) : 0;
719
+ const isBytes = stage.format === "bytes";
720
+ let cls = "stage-row";
721
+ if (removing) cls += " stage-row--removing";
722
+ else if (isCompleted) cls += " stage-row--completed";
723
+ else if (isActive) cls += " stage-row--active";
724
+ const detail = isCompleted ? stage.message : isActive && stage.message ? stage.message : "";
725
+ return /* @__PURE__ */ jsxs("div", { className: cls, children: [
726
+ /* @__PURE__ */ jsxs("div", { className: "stage-header", children: [
727
+ /* @__PURE__ */ jsx("span", { className: "stage-label", children: label }),
728
+ /* @__PURE__ */ jsx("span", { className: "stage-count", children: stage.total > 0 ? isBytes ? `${formatMB(stage.current)} / ${formatMB(stage.total)}` : `${stage.current}/${stage.total}` : stage.current > 0 ? isBytes ? formatMB(stage.current) : `${stage.current}` : "" })
729
+ ] }),
730
+ detail && /* @__PURE__ */ jsx("span", { className: "stage-detail", children: detail }),
731
+ /* @__PURE__ */ jsx(
732
+ "span",
733
+ {
734
+ className: `stage-bar${indeterminate ? " stage-bar--indeterminate" : ""}`,
735
+ children: /* @__PURE__ */ jsx(
736
+ "span",
737
+ {
738
+ className: "stage-bar-fill",
739
+ style: indeterminate ? void 0 : { width: `${isCompleted ? 100 : pct}%` }
740
+ }
741
+ )
742
+ }
743
+ )
744
+ ] });
745
+ }
746
+ function MultiStageProgress({
747
+ stages,
748
+ stageConfig
749
+ }) {
750
+ const entries = stageConfig.map(({ key, label }) => ({
751
+ key,
752
+ label,
753
+ stage: stages[key]
754
+ })).filter((e) => !!e.stage);
755
+ let lastCompletedIdx = -1;
756
+ for (let i = entries.length - 1; i >= 0; i--) {
757
+ if (entries[i].stage.status === "completed") {
758
+ lastCompletedIdx = i;
759
+ break;
760
+ }
761
+ }
762
+ return /* @__PURE__ */ jsx("div", { className: "multi-stage-progress", children: entries.map(({ key, label, stage }, i) => /* @__PURE__ */ jsx(
763
+ StageProgressRow,
764
+ {
765
+ label,
766
+ stage,
767
+ removing: stage.status === "completed" && i < lastCompletedIdx
768
+ },
769
+ key
770
+ )) });
771
+ }
772
+ function StatsGrid({
773
+ nodes,
774
+ relationships
775
+ }) {
776
+ return /* @__PURE__ */ jsxs("div", { className: "indexing-stats-grid", children: [
777
+ /* @__PURE__ */ jsxs("div", { className: "stat-card", children: [
778
+ /* @__PURE__ */ jsx("span", { className: "stat-value", children: nodes }),
779
+ /* @__PURE__ */ jsx("span", { className: "stat-label", children: "Nodes" })
780
+ ] }),
781
+ /* @__PURE__ */ jsxs("div", { className: "stat-card", children: [
782
+ /* @__PURE__ */ jsx("span", { className: "stat-value", children: relationships }),
783
+ /* @__PURE__ */ jsx("span", { className: "stat-label", children: "Edges" })
784
+ ] })
785
+ ] });
786
+ }
787
+ function IndexingProgress({
788
+ state,
789
+ stages: stageConfig,
790
+ icon,
791
+ onClose,
792
+ onCancel,
793
+ onMinimize,
794
+ title,
795
+ message
796
+ }) {
797
+ if (state.status === "error") {
798
+ return /* @__PURE__ */ jsx("div", { className: "modal-backdrop", children: /* @__PURE__ */ jsx(
799
+ "div",
800
+ {
801
+ className: "modal-card modal-card-wide",
802
+ onClick: (e) => e.stopPropagation(),
803
+ children: /* @__PURE__ */ jsxs("div", { className: "indexing-progress", children: [
804
+ /* @__PURE__ */ jsx(
805
+ MultiStageProgress,
806
+ {
807
+ stages: state.stages,
808
+ stageConfig
809
+ }
810
+ ),
811
+ /* @__PURE__ */ jsxs("div", { className: "failed-content", children: [
812
+ /* @__PURE__ */ jsx("div", { className: "failed-icon", children: /* @__PURE__ */ jsxs("svg", { width: "32", height: "32", viewBox: "0 0 32 32", fill: "none", children: [
813
+ /* @__PURE__ */ jsx(
814
+ "circle",
815
+ {
816
+ cx: "16",
817
+ cy: "16",
818
+ r: "14",
819
+ stroke: "currentColor",
820
+ strokeWidth: "2",
821
+ fill: "color-mix(in oklch, currentColor 10%, transparent)"
822
+ }
823
+ ),
824
+ /* @__PURE__ */ jsx(
825
+ "line",
826
+ {
827
+ x1: "11",
828
+ y1: "11",
829
+ x2: "21",
830
+ y2: "21",
831
+ stroke: "currentColor",
832
+ strokeWidth: "2.5",
833
+ strokeLinecap: "round"
834
+ }
835
+ ),
836
+ /* @__PURE__ */ jsx(
837
+ "line",
838
+ {
839
+ x1: "21",
840
+ y1: "11",
841
+ x2: "11",
842
+ y2: "21",
843
+ stroke: "currentColor",
844
+ strokeWidth: "2.5",
845
+ strokeLinecap: "round"
846
+ }
847
+ )
848
+ ] }) }),
849
+ /* @__PURE__ */ jsx("h2", { children: title ?? "Indexing Failed" }),
850
+ state.error && /* @__PURE__ */ jsx("p", { className: "failed-message", children: state.error })
851
+ ] }),
852
+ (state.nodesCreated > 0 || state.relationshipsCreated > 0) && /* @__PURE__ */ jsx(
853
+ StatsGrid,
854
+ {
855
+ nodes: state.nodesCreated,
856
+ relationships: state.relationshipsCreated
857
+ }
858
+ ),
859
+ /* @__PURE__ */ jsx("button", { className: "btn-cta btn-cta--secondary", onClick: onClose, children: "Close" })
860
+ ] })
861
+ }
862
+ ) });
863
+ }
864
+ if (state.status === "done") {
865
+ return /* @__PURE__ */ jsx("div", { className: "modal-backdrop", children: /* @__PURE__ */ jsx(
866
+ "div",
867
+ {
868
+ className: "modal-card modal-card-wide",
869
+ onClick: (e) => e.stopPropagation(),
870
+ children: /* @__PURE__ */ jsxs("div", { className: "indexing-progress", children: [
871
+ /* @__PURE__ */ jsx(
872
+ MultiStageProgress,
873
+ {
874
+ stages: state.stages,
875
+ stageConfig
876
+ }
877
+ ),
878
+ /* @__PURE__ */ jsxs("div", { className: "done-content", children: [
879
+ /* @__PURE__ */ jsx("div", { className: "done-checkmark", children: /* @__PURE__ */ jsxs("svg", { width: "32", height: "32", viewBox: "0 0 32 32", fill: "none", children: [
880
+ /* @__PURE__ */ jsx(
881
+ "circle",
882
+ {
883
+ cx: "16",
884
+ cy: "16",
885
+ r: "14",
886
+ stroke: "currentColor",
887
+ strokeWidth: "2",
888
+ fill: "color-mix(in oklch, currentColor 15%, transparent)"
889
+ }
890
+ ),
891
+ /* @__PURE__ */ jsx(
892
+ "polyline",
893
+ {
894
+ className: "done-check-path",
895
+ points: "10,16.5 14,20.5 22,12",
896
+ stroke: "currentColor",
897
+ strokeWidth: "2.5",
898
+ strokeLinecap: "round",
899
+ strokeLinejoin: "round",
900
+ fill: "none"
901
+ }
902
+ )
903
+ ] }) }),
904
+ /* @__PURE__ */ jsx("h2", { children: title ?? "Complete" })
905
+ ] }),
906
+ /* @__PURE__ */ jsx(
907
+ StatsGrid,
908
+ {
909
+ nodes: state.nodesCreated,
910
+ relationships: state.relationshipsCreated
911
+ }
912
+ ),
913
+ message && /* @__PURE__ */ jsx("p", { className: "indexing-message", children: message }),
914
+ /* @__PURE__ */ jsx(
915
+ "button",
916
+ {
917
+ className: "btn-cta btn-cta--secondary",
918
+ onClick: onMinimize ?? onClose,
919
+ children: onMinimize ? "Minimize" : "Close"
920
+ }
921
+ )
922
+ ] })
923
+ }
924
+ ) });
925
+ }
926
+ return /* @__PURE__ */ jsx("div", { className: "modal-backdrop", children: /* @__PURE__ */ jsxs(
927
+ "div",
928
+ {
929
+ className: "modal-card modal-card-wide",
930
+ onClick: (e) => e.stopPropagation(),
931
+ children: [
932
+ /* @__PURE__ */ jsxs("div", { className: "indexing-header", children: [
933
+ icon && /* @__PURE__ */ jsx("span", { className: "indexing-header-icon", children: icon }),
934
+ /* @__PURE__ */ jsx("h2", { children: title ?? "Indexing Repository" })
935
+ ] }),
936
+ /* @__PURE__ */ jsxs("div", { className: "indexing-progress", children: [
937
+ /* @__PURE__ */ jsx(
938
+ MultiStageProgress,
939
+ {
940
+ stages: state.stages,
941
+ stageConfig
942
+ }
943
+ ),
944
+ /* @__PURE__ */ jsx(
945
+ StatsGrid,
946
+ {
947
+ nodes: state.nodesCreated,
948
+ relationships: state.relationshipsCreated
949
+ }
950
+ ),
951
+ message && /* @__PURE__ */ jsx("p", { className: "indexing-message", children: message }),
952
+ /* @__PURE__ */ jsx(
953
+ "button",
954
+ {
955
+ className: "btn-cta btn-cta--secondary",
956
+ onClick: onMinimize ?? onCancel,
957
+ children: onMinimize ? "Minimize" : "Cancel"
958
+ }
959
+ )
960
+ ] })
961
+ ]
962
+ }
963
+ ) });
964
+ }
965
+ function normalizeRepoUrl(raw) {
966
+ const trimmed = raw.trim();
967
+ const scpMatch = trimmed.match(/^[\w.-]+@([^:]+):(.+)$/);
968
+ if (scpMatch) {
969
+ const host = scpMatch[1];
970
+ const path = stripDotGit(scpMatch[2]);
971
+ return `https://${host}/${path}`;
972
+ }
973
+ const sshMatch = trimmed.match(/^ssh:\/\/[\w.-]+@([^/]+)\/(.+)$/);
974
+ if (sshMatch) {
975
+ const host = sshMatch[1];
976
+ const path = stripDotGit(sshMatch[2]);
977
+ return `https://${host}/${path}`;
978
+ }
979
+ return stripDotGit(trimmed);
980
+ }
981
+ function stripDotGit(s) {
982
+ return s.endsWith(".git") ? s.slice(0, -4) : s;
983
+ }
984
+ export {
985
+ AddRepoModal as A,
986
+ IndexingProgress as I,
987
+ detectProvider as d,
988
+ normalizeRepoUrl as n
989
+ };
990
+ //# sourceMappingURL=urlNormalize-KY4ngwCQ.js.map