hale-commenting-system 1.0.7 → 2.0.0

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.
package/dist/index.mjs CHANGED
@@ -4,431 +4,36 @@ import { useLocation } from "react-router-dom";
4
4
 
5
5
  // src/contexts/CommentContext.tsx
6
6
  import * as React from "react";
7
-
8
- // src/services/githubAdapter.ts
9
- import axios from "axios";
10
- var getApiBase = () => {
11
- if (typeof window === "undefined") return "/api/github-api";
12
- const isNetlify = window.location.hostname.includes("netlify.app");
13
- const apiBase = isNetlify ? "/.netlify/functions/github-api" : "/api/github-api";
14
- console.log("\u{1F50D} API detection:", { hostname: window.location.hostname, isNetlify, apiBase });
15
- return apiBase;
16
- };
17
- var getStoredToken = () => {
18
- if (typeof window === "undefined") return null;
19
- const token = localStorage.getItem("github_access_token");
20
- console.log("\u{1F511} getStoredToken:", token ? "Token found" : "No token found");
21
- return token;
22
- };
23
- var getStoredUser = () => {
24
- if (typeof window === "undefined") return null;
25
- const userStr = localStorage.getItem("github_user");
26
- return userStr ? JSON.parse(userStr) : null;
27
- };
28
- var storeGitHubAuth = (token, login, avatar) => {
29
- localStorage.setItem("github_access_token", token);
30
- localStorage.setItem("github_user", JSON.stringify({ login, avatar }));
31
- };
32
- var clearGitHubAuth = () => {
33
- localStorage.removeItem("github_access_token");
34
- localStorage.removeItem("github_user");
35
- };
36
- var getAuthenticatedUser = () => {
37
- return getStoredUser();
38
- };
39
- var isGitHubConfigured = () => {
40
- const token = getStoredToken();
41
- const owner = process.env.VITE_GITHUB_OWNER;
42
- const repo = process.env.VITE_GITHUB_REPO;
43
- console.log("\u{1F50D} isGitHubConfigured check:", { hasToken: !!token, owner, repo });
44
- return !!(token && owner && repo);
45
- };
46
- async function makeGitHubRequest(method, endpoint, data) {
47
- const token = getStoredToken();
48
- if (!token) {
49
- throw new Error("Not authenticated with GitHub");
50
- }
51
- const response = await axios.post(getApiBase(), {
52
- token,
53
- method,
54
- endpoint,
55
- data
56
- });
57
- return response.data;
58
- }
59
- var githubAdapter = {
60
- /**
61
- * Create a new GitHub Issue for a comment thread
62
- */
63
- async createIssue(title, body, route, x, y, version) {
64
- console.log("\u{1F535} createIssue called", { title, route, x, y, version });
65
- if (!isGitHubConfigured()) {
66
- console.warn("\u26A0\uFE0F GitHub not configured. Skipping issue creation.");
67
- return { success: false, error: "Please sign in with GitHub" };
68
- }
69
- const owner = process.env.VITE_GITHUB_OWNER;
70
- const repo = process.env.VITE_GITHUB_REPO;
71
- console.log("\u{1F535} GitHub config:", { owner, repo, hasToken: !!getStoredToken() });
72
- try {
73
- const metadata = [
74
- `- Route: \`${route}\``,
75
- version ? `- Version: \`${version}\`` : null,
76
- `- Coordinates: \`(${Math.round(x)}, ${Math.round(y)})\``
77
- ].filter(Boolean).join("\n");
78
- const issueBody = {
79
- title,
80
- body: `${body}
81
-
82
- ---
83
- **Metadata:**
84
- ${metadata}`
85
- };
86
- console.log("\u{1F535} Calling makeGitHubRequest...");
87
- const issueData = await makeGitHubRequest("POST", `/repos/${owner}/${repo}/issues`, issueBody);
88
- console.log("\u2705 Created GitHub Issue #", issueData.number);
89
- try {
90
- const labels = [
91
- "apollo-comment",
92
- `route:${route}`,
93
- `coords:${Math.round(x)},${Math.round(y)}`
94
- ];
95
- if (version) labels.push(`version:${version}`);
96
- await makeGitHubRequest(
97
- "POST",
98
- `/repos/${owner}/${repo}/issues/${issueData.number}/labels`,
99
- { labels }
100
- );
101
- console.log("\u2705 Added labels to Issue #", issueData.number);
102
- } catch (labelError) {
103
- console.warn("\u26A0\uFE0F Could not add labels (labels may not exist in repo)");
104
- }
105
- return { success: true, data: issueData };
106
- } catch (error) {
107
- const errorMessage = error?.response?.data?.message || error?.message || "Failed to create issue";
108
- console.error("\u274C Failed to create GitHub Issue:", {
109
- message: errorMessage,
110
- error: error?.response?.data || error,
111
- status: error?.response?.status
112
- });
113
- return { success: false, error: errorMessage };
114
- }
115
- },
116
- /**
117
- * Add a comment to an existing GitHub Issue
118
- */
119
- async createComment(issueNumber, body) {
120
- if (!isGitHubConfigured()) {
121
- return { success: false, error: "Please sign in with GitHub" };
122
- }
123
- const owner = process.env.VITE_GITHUB_OWNER;
124
- const repo = process.env.VITE_GITHUB_REPO;
125
- try {
126
- const commentData = await makeGitHubRequest(
127
- "POST",
128
- `/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
129
- { body }
130
- );
131
- console.log(`\u2705 Added comment to Issue #${issueNumber}`);
132
- return { success: true, data: commentData };
133
- } catch (error) {
134
- const errorMessage = error?.response?.data?.message || error?.message || "Failed to create comment";
135
- console.error(`\u274C Failed to add comment to Issue #${issueNumber}:`, errorMessage);
136
- return { success: false, error: errorMessage };
137
- }
138
- },
139
- /**
140
- * Fetch all issues for a specific route
141
- */
142
- async fetchIssues(route) {
143
- if (!isGitHubConfigured()) {
144
- console.warn("GitHub not configured. Skipping issue fetch.");
145
- return [];
146
- }
147
- const owner = process.env.VITE_GITHUB_OWNER;
148
- const repo = process.env.VITE_GITHUB_REPO;
149
- try {
150
- const issues = await makeGitHubRequest(
151
- "GET",
152
- `/repos/${owner}/${repo}/issues?state=open`
153
- );
154
- const filteredIssues = issues.filter((issue) => {
155
- if (issue.body && issue.body.includes(`Route: \`${route}\``)) {
156
- return true;
157
- }
158
- if (issue.labels && issue.labels.some((l) => {
159
- const labelName = typeof l === "string" ? l : l.name;
160
- return labelName === `route:${route}`;
161
- })) {
162
- return true;
163
- }
164
- return false;
165
- });
166
- console.log(`\u2705 Fetched ${filteredIssues.length} issues for route: ${route}`);
167
- return filteredIssues;
168
- } catch (error) {
169
- console.error(`\u274C Failed to fetch issues for route ${route}:`, error);
170
- return [];
171
- }
172
- },
173
- /**
174
- * Fetch all comments for a specific issue
175
- */
176
- async fetchComments(issueNumber) {
177
- if (!isGitHubConfigured()) {
178
- return [];
179
- }
180
- const owner = process.env.VITE_GITHUB_OWNER;
181
- const repo = process.env.VITE_GITHUB_REPO;
182
- try {
183
- const comments = await makeGitHubRequest(
184
- "GET",
185
- `/repos/${owner}/${repo}/issues/${issueNumber}/comments`
186
- );
187
- console.log(`\u2705 Fetched ${comments.length} comments for Issue #${issueNumber}`);
188
- return comments;
189
- } catch (error) {
190
- console.error(`\u274C Failed to fetch comments for Issue #${issueNumber}:`, error);
191
- return [];
192
- }
193
- },
194
- /**
195
- * Close a GitHub Issue (when deleting a thread)
196
- */
197
- async closeIssue(issueNumber) {
198
- if (!isGitHubConfigured()) {
199
- return { success: false, error: "Please sign in with GitHub" };
200
- }
201
- const owner = process.env.VITE_GITHUB_OWNER;
202
- const repo = process.env.VITE_GITHUB_REPO;
203
- try {
204
- const issueData = await makeGitHubRequest(
205
- "PATCH",
206
- `/repos/${owner}/${repo}/issues/${issueNumber}`,
207
- { state: "closed" }
208
- );
209
- console.log(`\u2705 Closed Issue #${issueNumber}`);
210
- return { success: true, data: issueData };
211
- } catch (error) {
212
- const errorMessage = error?.response?.data?.message || error?.message || "Failed to close issue";
213
- console.error(`\u274C Failed to close Issue #${issueNumber}:`, errorMessage);
214
- return { success: false, error: errorMessage };
215
- }
216
- },
217
- /**
218
- * Update an existing comment on a GitHub Issue
219
- */
220
- async updateComment(commentId, body) {
221
- if (!isGitHubConfigured()) {
222
- return { success: false, error: "Please sign in with GitHub" };
223
- }
224
- const owner = process.env.VITE_GITHUB_OWNER;
225
- const repo = process.env.VITE_GITHUB_REPO;
226
- try {
227
- const commentData = await makeGitHubRequest(
228
- "PATCH",
229
- `/repos/${owner}/${repo}/issues/comments/${commentId}`,
230
- { body }
231
- );
232
- console.log(`\u2705 Updated comment #${commentId}`);
233
- return { success: true, data: commentData };
234
- } catch (error) {
235
- const errorMessage = error?.response?.data?.message || error?.message || "Failed to update comment";
236
- console.error(`\u274C Failed to update comment #${commentId}:`, errorMessage);
237
- return { success: false, error: errorMessage };
238
- }
239
- },
240
- /**
241
- * Delete a comment on a GitHub Issue
242
- */
243
- async deleteComment(commentId) {
244
- if (!isGitHubConfigured()) {
245
- return { success: false, error: "Please sign in with GitHub" };
246
- }
247
- const owner = process.env.VITE_GITHUB_OWNER;
248
- const repo = process.env.VITE_GITHUB_REPO;
249
- try {
250
- await makeGitHubRequest(
251
- "DELETE",
252
- `/repos/${owner}/${repo}/issues/comments/${commentId}`
253
- );
254
- console.log(`\u2705 Deleted comment #${commentId}`);
255
- return { success: true };
256
- } catch (error) {
257
- const errorMessage = error?.response?.data?.message || error?.message || "Failed to delete comment";
258
- console.error(`\u274C Failed to delete comment #${commentId}:`, errorMessage);
259
- return { success: false, error: errorMessage };
260
- }
261
- }
262
- };
263
-
264
- // src/services/gitlabAdapter.ts
265
- import axios2 from "axios";
266
- var getApiBase2 = () => {
267
- if (typeof window === "undefined") return "/api/gitlab-api";
268
- const isNetlify = window.location.hostname.includes("netlify.app");
269
- return isNetlify ? "/.netlify/functions/gitlab-api" : "/api/gitlab-api";
270
- };
271
- var getStoredToken2 = () => {
272
- if (typeof window === "undefined") return null;
273
- return localStorage.getItem("gitlab_access_token");
274
- };
275
- var isGitLabConfigured = () => {
276
- const token = getStoredToken2();
277
- const projectPath = process.env.VITE_GITLAB_PROJECT_PATH;
278
- const baseUrl = process.env.VITE_GITLAB_BASE_URL || "https://gitlab.com";
279
- return !!(token && projectPath && baseUrl);
280
- };
281
- async function makeGitLabRequest(method, endpoint, data) {
282
- const token = getStoredToken2();
283
- if (!token) {
284
- throw new Error("Not authenticated with GitLab");
285
- }
286
- const baseUrl = process.env.VITE_GITLAB_BASE_URL || "https://gitlab.com";
287
- const response = await axios2.post(getApiBase2(), {
288
- token,
289
- method,
290
- endpoint,
291
- data,
292
- baseUrl
293
- });
294
- return response.data;
295
- }
296
- var encodeProject = (p) => encodeURIComponent(p);
297
- var gitlabAdapter = {
298
- async createIssue(title, body, route, x, y, version) {
299
- if (!isGitLabConfigured()) {
300
- return { success: false, error: "Please sign in with GitLab" };
301
- }
302
- try {
303
- const projectPath = process.env.VITE_GITLAB_PROJECT_PATH;
304
- const labels = ["apollo-comment", `route:${route}`, `coords:${Math.round(x)},${Math.round(y)}`];
305
- if (version) labels.push(`version:${version}`);
306
- const issue = await makeGitLabRequest(
307
- "POST",
308
- `/projects/${encodeProject(projectPath)}/issues`,
309
- { title, description: body, labels: labels.join(",") }
310
- );
311
- return { success: true, data: { ...issue, number: issue.iid } };
312
- } catch (error) {
313
- const message = error?.response?.data?.message || error?.message || "Failed to create GitLab issue";
314
- return { success: false, error: message };
315
- }
316
- },
317
- async createComment(issueNumber, body) {
318
- if (!isGitLabConfigured()) {
319
- return { success: false, error: "Please sign in with GitLab" };
320
- }
321
- try {
322
- const projectPath = process.env.VITE_GITLAB_PROJECT_PATH;
323
- const note = await makeGitLabRequest(
324
- "POST",
325
- `/projects/${encodeProject(projectPath)}/issues/${issueNumber}/notes`,
326
- { body }
327
- );
328
- return { success: true, data: note };
329
- } catch (error) {
330
- const message = error?.response?.data?.message || error?.message || "Failed to create GitLab comment";
331
- return { success: false, error: message };
332
- }
333
- },
334
- async updateComment(commentId, body) {
335
- if (!isGitLabConfigured()) {
336
- return { success: false, error: "Please sign in with GitLab" };
337
- }
338
- try {
339
- const projectPath = process.env.VITE_GITLAB_PROJECT_PATH;
340
- const note = await makeGitLabRequest(
341
- "PUT",
342
- `/projects/${encodeProject(projectPath)}/notes/${commentId}`,
343
- { body }
344
- );
345
- return { success: true, data: note };
346
- } catch (error) {
347
- const message = error?.response?.data?.message || error?.message || "Failed to update GitLab comment";
348
- return { success: false, error: message };
349
- }
350
- },
351
- async deleteComment(commentId) {
352
- if (!isGitLabConfigured()) {
353
- return { success: false, error: "Please sign in with GitLab" };
354
- }
355
- try {
356
- const projectPath = process.env.VITE_GITLAB_PROJECT_PATH;
357
- await makeGitLabRequest(
358
- "DELETE",
359
- `/projects/${encodeProject(projectPath)}/notes/${commentId}`
360
- );
361
- return { success: true };
362
- } catch (error) {
363
- const message = error?.response?.data?.message || error?.message || "Failed to delete GitLab comment";
364
- return { success: false, error: message };
365
- }
366
- },
367
- async closeIssue(issueNumber) {
368
- if (!isGitLabConfigured()) {
369
- return { success: false, error: "Please sign in with GitLab" };
370
- }
371
- try {
372
- const projectPath = process.env.VITE_GITLAB_PROJECT_PATH;
373
- const issue = await makeGitLabRequest(
374
- "PUT",
375
- `/projects/${encodeProject(projectPath)}/issues/${issueNumber}`,
376
- { state_event: "close" }
377
- );
378
- return { success: true, data: issue };
379
- } catch (error) {
380
- const message = error?.response?.data?.message || error?.message || "Failed to close GitLab issue";
381
- return { success: false, error: message };
382
- }
383
- },
384
- async fetchIssues(route) {
385
- if (!isGitLabConfigured()) {
386
- return [];
387
- }
388
- try {
389
- const projectPath = process.env.VITE_GITLAB_PROJECT_PATH;
390
- const issues = await makeGitLabRequest(
391
- "GET",
392
- `/projects/${encodeProject(projectPath)}/issues?state=opened&per_page=100`
393
- );
394
- return (issues || []).filter((issue) => {
395
- const labels = issue.labels || [];
396
- const routeLabel = labels.some((l) => l === `route:${route}`);
397
- const inBody = (issue.description || "").includes(`Route: \`${route}\``);
398
- return routeLabel || inBody;
399
- });
400
- } catch {
401
- return [];
402
- }
403
- },
404
- async fetchComments(issueNumber) {
405
- if (!isGitLabConfigured()) {
406
- return [];
407
- }
408
- try {
409
- const projectPath = process.env.VITE_GITLAB_PROJECT_PATH;
410
- const notes = await makeGitLabRequest(
411
- "GET",
412
- `/projects/${encodeProject(projectPath)}/issues/${issueNumber}/notes?per_page=100`
413
- );
414
- return notes || [];
415
- } catch {
416
- return [];
417
- }
418
- }
419
- };
420
-
421
- // src/contexts/CommentContext.tsx
422
7
  import { jsx } from "react/jsx-runtime";
423
8
  var CommentContext = React.createContext(void 0);
424
- var STORAGE_KEY = "apollo-threads";
425
- var SHOW_PINS_KEY = "apollo-show-pins";
426
- var ENABLE_COMMENTING_KEY = "apollo-enable-commenting";
9
+ var STORAGE_KEY = "hale-threads";
10
+ var SHOW_PINS_KEY = "hale-show-pins";
11
+ var ENABLE_COMMENTING_KEY = "hale-enable-commenting";
427
12
  var migrateOldComments = () => {
428
13
  try {
429
- const oldComments = localStorage.getItem("apollo-comments");
430
- if (oldComments) {
431
- const parsed = JSON.parse(oldComments);
14
+ const oldThreadsKey = localStorage.getItem("apollo-threads");
15
+ const oldCommentsKey = localStorage.getItem("apollo-comments");
16
+ if (oldThreadsKey) {
17
+ const parsed = JSON.parse(oldThreadsKey);
18
+ const cleanThreads = parsed.map((t) => ({
19
+ id: t.id,
20
+ x: t.x,
21
+ y: t.y,
22
+ route: t.route,
23
+ comments: t.comments.map((c) => ({
24
+ id: c.id,
25
+ text: c.text || "",
26
+ createdAt: c.createdAt,
27
+ author: c.author
28
+ })),
29
+ version: t.version
30
+ }));
31
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(cleanThreads));
32
+ localStorage.removeItem("apollo-threads");
33
+ return cleanThreads;
34
+ }
35
+ if (oldCommentsKey) {
36
+ const parsed = JSON.parse(oldCommentsKey);
432
37
  const threads = parsed.map((oldComment) => ({
433
38
  id: oldComment.id,
434
39
  x: oldComment.x,
@@ -467,7 +72,9 @@ var CommentProvider = ({ children }) => {
467
72
  const [showPins, setShowPins] = React.useState(() => {
468
73
  try {
469
74
  const stored = localStorage.getItem(SHOW_PINS_KEY);
470
- return stored === "true";
75
+ if (stored !== null) return stored === "true";
76
+ const oldKey = localStorage.getItem("apollo-show-pins");
77
+ return oldKey === "true";
471
78
  } catch (error) {
472
79
  return false;
473
80
  }
@@ -475,12 +82,13 @@ var CommentProvider = ({ children }) => {
475
82
  const [enableCommenting, setEnableCommenting] = React.useState(() => {
476
83
  try {
477
84
  const stored = localStorage.getItem(ENABLE_COMMENTING_KEY);
478
- return stored === "true";
85
+ if (stored !== null) return stored === "true";
86
+ const oldKey = localStorage.getItem("apollo-enable-commenting");
87
+ return oldKey === "true";
479
88
  } catch (error) {
480
89
  return false;
481
90
  }
482
91
  });
483
- const [isSyncing, setIsSyncing] = React.useState(false);
484
92
  React.useEffect(() => {
485
93
  try {
486
94
  localStorage.setItem(STORAGE_KEY, JSON.stringify(threads));
@@ -516,247 +124,36 @@ var CommentProvider = ({ children }) => {
516
124
  y,
517
125
  route,
518
126
  comments: [],
519
- // Start with no comments
520
- syncStatus: "local",
521
127
  version
522
128
  };
523
129
  setThreads((prev) => [...prev, newThread]);
524
- if (isGitHubConfigured() || isGitLabConfigured()) {
525
- (async () => {
526
- setThreads((prev) => prev.map(
527
- (t) => t.id === threadId ? { ...t, syncStatus: "syncing" } : t
528
- ));
529
- const pageName = route === "/" ? "Home page" : route.split("/").filter(Boolean).join(" > ") || "Page";
530
- const versionStr = version ? ` [v${version}]` : "";
531
- let createdProvider;
532
- let createdNumber;
533
- let createError;
534
- if (isGitHubConfigured()) {
535
- const issue = await githubAdapter.createIssue(
536
- `\u{1F4AC} ${pageName} comment${versionStr}`,
537
- `Thread created on route: ${route}
538
-
539
- Coordinates: (${Math.round(x)}, ${Math.round(y)})
540
-
541
- **Version:** ${version || "N/A"}
542
-
543
- (Initial comment will be added as a reply)`,
544
- route,
545
- x,
546
- y,
547
- version
548
- );
549
- if (issue.success && issue.data) {
550
- createdProvider = "github";
551
- createdNumber = issue.data.number;
552
- } else {
553
- createError = issue.error;
554
- }
555
- } else if (isGitLabConfigured()) {
556
- const issue = await gitlabAdapter.createIssue(
557
- `\u{1F4AC} ${pageName} comment${versionStr}`,
558
- `Thread created on route: ${route}
559
-
560
- Coordinates: (${Math.round(x)}, ${Math.round(y)})
561
-
562
- **Version:** ${version || "N/A"}
563
-
564
- (Initial comment will be added as a reply)`,
565
- route,
566
- x,
567
- y,
568
- version
569
- );
570
- if (issue.success && issue.data) {
571
- createdProvider = "gitlab";
572
- createdNumber = issue.data.number;
573
- } else {
574
- createError = issue.error;
575
- }
576
- }
577
- if (createdNumber && createdProvider) {
578
- setThreads((prev) => prev.map(
579
- (t) => t.id === threadId ? { ...t, issueNumber: createdNumber, provider: createdProvider, syncStatus: "synced" } : t
580
- ));
581
- } else if (createError) {
582
- setThreads((prev) => prev.map(
583
- (t) => t.id === threadId ? { ...t, syncStatus: "error", syncError: createError } : t
584
- ));
585
- }
586
- })();
587
- }
588
130
  return threadId;
589
131
  }, []);
590
- const addReply = React.useCallback(async (threadId, text) => {
132
+ const addReply = React.useCallback((threadId, text) => {
591
133
  const commentId = `${threadId}-comment-${Date.now()}`;
592
134
  const newComment = {
593
135
  id: commentId,
594
136
  text,
595
137
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
596
138
  };
597
- const thread = threads.find((t) => t.id === threadId);
598
139
  setThreads(
599
140
  (prev) => prev.map((t) => {
600
141
  if (t.id === threadId) {
601
142
  return {
602
143
  ...t,
603
- comments: [...t.comments, newComment],
604
- syncStatus: "pending"
605
- // Mark as pending sync
144
+ comments: [...t.comments, newComment]
606
145
  };
607
146
  }
608
147
  return t;
609
148
  })
610
149
  );
611
- if (thread) {
612
- if (!thread.issueNumber) {
613
- console.log("\u{1F535} Thread has no issue number, creating remote issue first...");
614
- setThreads((prev) => prev.map(
615
- (t) => t.id === threadId ? { ...t, syncStatus: "syncing" } : t
616
- ));
617
- const pageName = thread.route === "/" ? "Home page" : thread.route.split("/").filter(Boolean).join(" > ") || "Page";
618
- const versionStr = thread.version ? ` [v${thread.version}]` : "";
619
- let createdProvider;
620
- let createdNumber;
621
- const providerPref = thread.provider || (isGitHubConfigured() ? "github" : isGitLabConfigured() ? "gitlab" : void 0);
622
- if (providerPref === "github" && isGitHubConfigured()) {
623
- const issue = await githubAdapter.createIssue(
624
- `\u{1F4AC} ${pageName} comment${versionStr}`,
625
- `Thread created on route: ${thread.route}
626
-
627
- Coordinates: (${Math.round(thread.x)}, ${Math.round(thread.y)})
628
-
629
- **Version:** ${thread.version || "N/A"}`,
630
- thread.route,
631
- thread.x,
632
- thread.y,
633
- thread.version
634
- );
635
- if (issue.success && issue.data) {
636
- createdProvider = "github";
637
- createdNumber = issue.data.number;
638
- } else {
639
- console.error("\u274C Failed to create GitHub issue:", issue.error);
640
- }
641
- } else if (providerPref === "gitlab" && isGitLabConfigured()) {
642
- const issue = await gitlabAdapter.createIssue(
643
- `\u{1F4AC} ${pageName} comment${versionStr}`,
644
- `Thread created on route: ${thread.route}
645
-
646
- Coordinates: (${Math.round(thread.x)}, ${Math.round(thread.y)})
647
-
648
- **Version:** ${thread.version || "N/A"}`,
649
- thread.route,
650
- thread.x,
651
- thread.y,
652
- thread.version
653
- );
654
- if (issue.success && issue.data) {
655
- createdProvider = "gitlab";
656
- createdNumber = issue.data.number;
657
- } else {
658
- console.error("\u274C Failed to create GitLab issue:", issue.error);
659
- }
660
- }
661
- if (createdNumber && createdProvider) {
662
- console.log("\u2705 Created remote issue #", createdNumber);
663
- setThreads((prev) => prev.map(
664
- (t) => t.id === threadId ? { ...t, issueNumber: createdNumber, provider: createdProvider, syncStatus: "pending" } : t
665
- ));
666
- const updatedThread = threads.find((t) => t.id === threadId);
667
- if (updatedThread) {
668
- for (const comment of updatedThread.comments) {
669
- if (!comment.githubCommentId) {
670
- const commentResult = createdProvider === "github" ? await githubAdapter.createComment(createdNumber, comment.text) : await gitlabAdapter.createComment(createdNumber, comment.text);
671
- if (commentResult.success && commentResult.data) {
672
- setThreads((prev) => prev.map((t) => {
673
- if (t.id === threadId) {
674
- return {
675
- ...t,
676
- comments: t.comments.map(
677
- (c) => c.id === comment.id ? { ...c, githubCommentId: commentResult.data.id } : c
678
- )
679
- };
680
- }
681
- return t;
682
- }));
683
- }
684
- }
685
- }
686
- }
687
- const result = createdProvider === "github" ? await githubAdapter.createComment(createdNumber, text) : await gitlabAdapter.createComment(createdNumber, text);
688
- if (result.success && result.data) {
689
- setThreads(
690
- (prev) => prev.map((t) => {
691
- if (t.id === threadId) {
692
- return {
693
- ...t,
694
- syncStatus: "synced",
695
- comments: t.comments.map(
696
- (c) => c.id === commentId ? { ...c, githubCommentId: result.data.id } : c
697
- )
698
- };
699
- }
700
- return t;
701
- })
702
- );
703
- } else {
704
- setThreads(
705
- (prev) => prev.map(
706
- (t) => t.id === threadId ? { ...t, syncStatus: "error", syncError: result.error } : t
707
- )
708
- );
709
- }
710
- } else {
711
- setThreads((prev) => prev.map(
712
- (t) => t.id === threadId ? { ...t, syncStatus: "error", syncError: "Failed to create remote issue" } : t
713
- ));
714
- }
715
- } else {
716
- const isGitHub = thread.provider === "github";
717
- const isGitLab = thread.provider === "gitlab";
718
- let result;
719
- if (isGitHub && isGitHubConfigured()) {
720
- result = await githubAdapter.createComment(thread.issueNumber, text);
721
- } else if (isGitLab && isGitLabConfigured()) {
722
- result = await gitlabAdapter.createComment(thread.issueNumber, text);
723
- } else {
724
- return;
725
- }
726
- if (result.success && result.data) {
727
- setThreads(
728
- (prev) => prev.map((t) => {
729
- if (t.id === threadId) {
730
- return {
731
- ...t,
732
- syncStatus: "synced",
733
- comments: t.comments.map(
734
- (c) => c.id === commentId ? { ...c, githubCommentId: result.data.id } : c
735
- )
736
- };
737
- }
738
- return t;
739
- })
740
- );
741
- } else {
742
- setThreads(
743
- (prev) => prev.map(
744
- (t) => t.id === threadId ? { ...t, syncStatus: "error", syncError: result.error } : t
745
- )
746
- );
747
- }
748
- }
749
- }
750
- }, [threads]);
751
- const updateComment = React.useCallback(async (threadId, commentId, text) => {
752
- const thread = threads.find((t) => t.id === threadId);
753
- const comment = thread?.comments.find((c) => c.id === commentId);
150
+ }, []);
151
+ const updateComment = React.useCallback((threadId, commentId, text) => {
754
152
  setThreads(
755
153
  (prev) => prev.map((t) => {
756
154
  if (t.id === threadId) {
757
155
  return {
758
156
  ...t,
759
- syncStatus: "pending",
760
157
  comments: t.comments.map(
761
158
  (c) => c.id === commentId ? { ...c, text } : c
762
159
  )
@@ -765,82 +162,23 @@ Coordinates: (${Math.round(thread.x)}, ${Math.round(thread.y)})
765
162
  return t;
766
163
  })
767
164
  );
768
- if (comment?.githubCommentId) {
769
- let ok = false;
770
- if (thread?.provider === "github" && isGitHubConfigured()) {
771
- const result = await githubAdapter.updateComment(comment.githubCommentId, text);
772
- ok = !!result.success;
773
- } else if (thread?.provider === "gitlab" && isGitLabConfigured()) {
774
- const result = await gitlabAdapter.updateComment(comment.githubCommentId, text);
775
- ok = !!result.success;
776
- }
777
- if (ok) {
778
- setThreads(
779
- (prev) => prev.map((t) => t.id === threadId ? { ...t, syncStatus: "synced" } : t)
780
- );
781
- } else {
782
- setThreads(
783
- (prev) => prev.map(
784
- (t) => t.id === threadId ? { ...t, syncStatus: "error", syncError: "Update failed" } : t
785
- )
786
- );
787
- }
788
- }
789
- }, [threads]);
790
- const deleteComment = React.useCallback(async (threadId, commentId) => {
791
- const thread = threads.find((t) => t.id === threadId);
792
- const comment = thread?.comments.find((c) => c.id === commentId);
165
+ }, []);
166
+ const deleteComment = React.useCallback((threadId, commentId) => {
793
167
  setThreads(
794
168
  (prev) => prev.map((t) => {
795
169
  if (t.id === threadId) {
796
170
  return {
797
171
  ...t,
798
- syncStatus: "pending",
799
172
  comments: t.comments.filter((c) => c.id !== commentId)
800
173
  };
801
174
  }
802
175
  return t;
803
176
  })
804
177
  );
805
- if (comment?.githubCommentId) {
806
- let ok = false;
807
- if (thread?.provider === "github" && isGitHubConfigured()) {
808
- const result = await githubAdapter.deleteComment(comment.githubCommentId);
809
- ok = !!result.success;
810
- } else if (thread?.provider === "gitlab" && isGitLabConfigured()) {
811
- const result = await gitlabAdapter.deleteComment(comment.githubCommentId);
812
- ok = !!result.success;
813
- }
814
- if (ok) {
815
- setThreads(
816
- (prev) => prev.map((t) => t.id === threadId ? { ...t, syncStatus: "synced" } : t)
817
- );
818
- } else {
819
- setThreads(
820
- (prev) => prev.map(
821
- (t) => t.id === threadId ? { ...t, syncStatus: "error", syncError: "Delete failed" } : t
822
- )
823
- );
824
- }
825
- }
826
- }, [threads]);
827
- const deleteThread = React.useCallback(async (threadId) => {
828
- const thread = threads.find((t) => t.id === threadId);
178
+ }, []);
179
+ const deleteThread = React.useCallback((threadId) => {
829
180
  setThreads((prev) => prev.filter((t) => t.id !== threadId));
830
- if (thread?.issueNumber) {
831
- if (thread.provider === "github" && isGitHubConfigured()) {
832
- const result = await githubAdapter.closeIssue(thread.issueNumber);
833
- if (!result.success) {
834
- console.error("Failed to close GitHub issue:", result.error);
835
- }
836
- } else if (thread.provider === "gitlab" && isGitLabConfigured()) {
837
- const result = await gitlabAdapter.closeIssue(thread.issueNumber);
838
- if (!result.success) {
839
- console.error("Failed to close GitLab issue:", result.error);
840
- }
841
- }
842
- }
843
- }, [threads]);
181
+ }, []);
844
182
  const clearAllThreads = React.useCallback(() => {
845
183
  setThreads([]);
846
184
  }, []);
@@ -852,211 +190,6 @@ Coordinates: (${Math.round(thread.x)}, ${Math.round(thread.y)})
852
190
  return routeMatch && versionMatch;
853
191
  });
854
192
  }, [threads]);
855
- const syncFromGitHub = React.useCallback(async (route) => {
856
- setIsSyncing(true);
857
- console.log(`\u{1F504} Syncing threads from remote providers for route: ${route}`);
858
- try {
859
- const newThreads = [];
860
- if (isGitHubConfigured()) {
861
- const issues = await githubAdapter.fetchIssues(route);
862
- for (const issue of issues) {
863
- let coords = null;
864
- if (issue.body) {
865
- const coordMatch = issue.body.match(/Coordinates: `\((\d+),\s*(\d+)\)`/);
866
- if (coordMatch) {
867
- coords = [parseInt(coordMatch[1]), parseInt(coordMatch[2])];
868
- }
869
- }
870
- if (!coords) {
871
- const coordLabel = issue.labels.find(
872
- (l) => typeof l === "string" ? l.startsWith("coords:") : l.name?.startsWith("coords:")
873
- );
874
- const coordString = typeof coordLabel === "string" ? coordLabel : coordLabel?.name;
875
- if (coordString) {
876
- const coordParts = coordString.replace("coords:", "").split(",").map(Number);
877
- if (coordParts.length === 2) {
878
- coords = coordParts;
879
- }
880
- }
881
- }
882
- if (!coords || coords.length !== 2 || isNaN(coords[0]) || isNaN(coords[1])) {
883
- console.warn(`Skipping issue #${issue.number}: invalid or missing coords`);
884
- continue;
885
- }
886
- const ghComments = await githubAdapter.fetchComments(issue.number);
887
- const comments = [];
888
- if (issue.body) {
889
- comments.push({
890
- id: `issue-${issue.number}-body`,
891
- text: issue.body,
892
- createdAt: issue.created_at,
893
- author: issue.user?.login,
894
- githubCommentId: void 0
895
- // Body is not a comment
896
- });
897
- }
898
- ghComments.forEach((ghComment) => {
899
- comments.push({
900
- id: `comment-${ghComment.id}`,
901
- text: ghComment.body,
902
- createdAt: ghComment.created_at,
903
- author: ghComment.user?.login,
904
- githubCommentId: ghComment.id
905
- });
906
- });
907
- newThreads.push({
908
- id: `github-${issue.number}`,
909
- x: coords[0],
910
- y: coords[1],
911
- route,
912
- comments,
913
- issueNumber: issue.number,
914
- provider: "github",
915
- syncStatus: "synced"
916
- });
917
- }
918
- }
919
- if (isGitLabConfigured()) {
920
- const issues = await gitlabAdapter.fetchIssues(route);
921
- for (const issue of issues) {
922
- let coords = null;
923
- const labels = issue.labels || [];
924
- const coordLabel = labels.find((l) => l.startsWith("coords:"));
925
- if (coordLabel) {
926
- const parts = coordLabel.replace("coords:", "").split(",").map((n) => parseInt(n, 10));
927
- if (parts.length === 2 && !Number.isNaN(parts[0]) && !Number.isNaN(parts[1])) {
928
- coords = parts;
929
- }
930
- }
931
- if (!coords && issue.description) {
932
- const m = issue.description.match(/Coordinates:\s*`?\((\d+),\s*(\d+)\)`?/);
933
- if (m) coords = [parseInt(m[1], 10), parseInt(m[2], 10)];
934
- }
935
- if (!coords) {
936
- console.warn(`Skipping GitLab issue #${issue.iid}: invalid or missing coords`);
937
- continue;
938
- }
939
- const glComments = await gitlabAdapter.fetchComments(issue.iid);
940
- const comments = [];
941
- if (issue.description) {
942
- comments.push({
943
- id: `issue-${issue.iid}-body`,
944
- text: issue.description,
945
- createdAt: issue.created_at,
946
- author: issue.author?.username,
947
- githubCommentId: void 0
948
- });
949
- }
950
- glComments.forEach((note) => {
951
- comments.push({
952
- id: `comment-${note.id}`,
953
- text: note.body,
954
- createdAt: note.created_at,
955
- author: note.author?.username,
956
- githubCommentId: note.id
957
- });
958
- });
959
- newThreads.push({
960
- id: `gitlab-${issue.iid}`,
961
- x: coords[0],
962
- y: coords[1],
963
- route,
964
- comments,
965
- issueNumber: issue.iid,
966
- provider: "gitlab",
967
- syncStatus: "synced"
968
- });
969
- }
970
- }
971
- setThreads((prev) => {
972
- const localOnlyThreads = prev.filter((t) => t.route === route && !t.issueNumber);
973
- const mergedThreads = [
974
- ...prev.filter((t) => t.route !== route),
975
- // Keep threads from other routes
976
- ...newThreads,
977
- // Add synced threads
978
- ...localOnlyThreads
979
- // Keep local-only threads
980
- ];
981
- return mergedThreads;
982
- });
983
- console.log(`\u2705 Synced ${newThreads.length} threads from providers`);
984
- } catch (error) {
985
- console.error("Failed to sync from providers:", error);
986
- } finally {
987
- setIsSyncing(false);
988
- }
989
- }, []);
990
- const retrySync = React.useCallback(async () => {
991
- console.log("\u{1F504} Retrying sync for pending/error threads...");
992
- const threadsToSync = threads.filter(
993
- (t) => (t.syncStatus === "pending" || t.syncStatus === "error") && !t.issueNumber
994
- );
995
- if (threadsToSync.length === 0) {
996
- console.log("No threads to sync");
997
- return;
998
- }
999
- if (!isGitHubConfigured()) {
1000
- console.warn("GitHub not configured. Cannot retry sync.");
1001
- return;
1002
- }
1003
- for (const thread of threadsToSync) {
1004
- console.log(`\u{1F535} Syncing thread ${thread.id}...`);
1005
- setThreads((prev) => prev.map(
1006
- (t) => t.id === thread.id ? { ...t, syncStatus: "syncing" } : t
1007
- ));
1008
- const pageName = thread.route === "/" ? "Home page" : thread.route.split("/").filter(Boolean).join(" > ") || "Page";
1009
- const versionStr = thread.version ? ` [v${thread.version}]` : "";
1010
- const issue = await githubAdapter.createIssue(
1011
- `\u{1F4AC} ${pageName} comment${versionStr}`,
1012
- `Thread created on route: ${thread.route}
1013
-
1014
- Coordinates: (${Math.round(thread.x)}, ${Math.round(thread.y)})
1015
-
1016
- **Version:** ${thread.version || "N/A"}`,
1017
- thread.route,
1018
- thread.x,
1019
- thread.y,
1020
- thread.version
1021
- );
1022
- if (issue.success && issue.data) {
1023
- console.log("\u2705 Created GitHub issue #", issue.data.number);
1024
- setThreads((prev) => prev.map(
1025
- (t) => t.id === thread.id ? { ...t, issueNumber: issue.data.number } : t
1026
- ));
1027
- for (const comment of thread.comments) {
1028
- if (!comment.githubCommentId) {
1029
- const commentResult = await githubAdapter.createComment(issue.data.number, comment.text);
1030
- if (commentResult.success && commentResult.data) {
1031
- setThreads((prev) => prev.map((t) => {
1032
- if (t.id === thread.id) {
1033
- return {
1034
- ...t,
1035
- comments: t.comments.map(
1036
- (c) => c.id === comment.id ? { ...c, githubCommentId: commentResult.data.id } : c
1037
- )
1038
- };
1039
- }
1040
- return t;
1041
- }));
1042
- }
1043
- }
1044
- }
1045
- setThreads((prev) => prev.map(
1046
- (t) => t.id === thread.id ? { ...t, syncStatus: "synced" } : t
1047
- ));
1048
- } else {
1049
- console.error("\u274C Failed to create GitHub issue:", issue.error);
1050
- setThreads((prev) => prev.map(
1051
- (t) => t.id === thread.id ? { ...t, syncStatus: "error", syncError: issue.error } : t
1052
- ));
1053
- }
1054
- }
1055
- console.log("\u2705 Retry sync complete");
1056
- }, [threads]);
1057
- const hasPendingSync = React.useMemo(() => {
1058
- return threads.some((t) => t.syncStatus === "pending" || t.syncStatus === "error");
1059
- }, [threads]);
1060
193
  const value = React.useMemo(
1061
194
  () => ({
1062
195
  threads,
@@ -1070,13 +203,9 @@ Coordinates: (${Math.round(thread.x)}, ${Math.round(thread.y)})
1070
203
  deleteComment,
1071
204
  deleteThread,
1072
205
  clearAllThreads,
1073
- getThreadsForRoute,
1074
- syncFromGitHub,
1075
- retrySync,
1076
- isSyncing,
1077
- hasPendingSync
206
+ getThreadsForRoute
1078
207
  }),
1079
- [threads, showPins, enableCommenting, toggleShowPins, toggleEnableCommenting, addThread, addReply, updateComment, deleteComment, deleteThread, clearAllThreads, getThreadsForRoute, syncFromGitHub, retrySync, isSyncing, hasPendingSync]
208
+ [threads, showPins, enableCommenting, toggleShowPins, toggleEnableCommenting, addThread, addReply, updateComment, deleteComment, deleteThread, clearAllThreads, getThreadsForRoute]
1080
209
  );
1081
210
  return /* @__PURE__ */ jsx(CommentContext.Provider, { value, children });
1082
211
  };
@@ -1092,7 +221,7 @@ var useComments = () => {
1092
221
  import * as React2 from "react";
1093
222
  import { jsx as jsx2 } from "react/jsx-runtime";
1094
223
  var VersionContext = React2.createContext(void 0);
1095
- var VERSION_STORAGE_KEY = "apollo-current-version";
224
+ var VERSION_STORAGE_KEY = "hale-current-version";
1096
225
  var VersionProvider = ({ children }) => {
1097
226
  const [currentVersion, setCurrentVersionState] = React2.useState(() => {
1098
227
  try {
@@ -1132,7 +261,7 @@ var useVersion = () => {
1132
261
 
1133
262
  // src/components/CommentPin.tsx
1134
263
  import { Button } from "@patternfly/react-core";
1135
- import { CommentIcon, ExclamationTriangleIcon } from "@patternfly/react-icons";
264
+ import { CommentIcon } from "@patternfly/react-icons";
1136
265
  import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
1137
266
  var CommentPin = ({
1138
267
  thread,
@@ -1140,11 +269,6 @@ var CommentPin = ({
1140
269
  isSelected = false
1141
270
  }) => {
1142
271
  const commentCount = thread.comments.length;
1143
- const isPending = thread.syncStatus === "pending" || thread.syncStatus === "syncing";
1144
- const isError = thread.syncStatus === "error";
1145
- const pulseAnimation = isPending ? {
1146
- animation: "pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite"
1147
- } : {};
1148
272
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1149
273
  /* @__PURE__ */ jsx3("style", { children: `
1150
274
  @keyframes pulse {
@@ -1157,7 +281,7 @@ var CommentPin = ({
1157
281
  {
1158
282
  id: `comment-pin-${thread.id}`,
1159
283
  variant: "plain",
1160
- "aria-label": `Comment thread with ${commentCount} ${commentCount === 1 ? "comment" : "comments"}${isError ? " - sync error" : ""}${isPending ? " - syncing" : ""}`,
284
+ "aria-label": `Comment thread with ${commentCount} ${commentCount === 1 ? "comment" : "comments"}`,
1161
285
  onClick: (e) => {
1162
286
  e.stopPropagation();
1163
287
  onPinClick();
@@ -1170,7 +294,7 @@ var CommentPin = ({
1170
294
  width: "32px",
1171
295
  height: "32px",
1172
296
  borderRadius: "50%",
1173
- backgroundColor: isError ? "#A30000" : isPending ? "#F0AB00" : "#C9190B",
297
+ backgroundColor: "#C9190B",
1174
298
  color: "white",
1175
299
  border: isSelected ? "3px solid #0066CC" : "2px solid white",
1176
300
  boxShadow: isSelected ? "0 0 0 2px #0066CC, 0 4px 12px rgba(0, 0, 0, 0.4)" : "0 2px 8px rgba(0, 0, 0, 0.3)",
@@ -1181,10 +305,9 @@ var CommentPin = ({
1181
305
  cursor: "pointer",
1182
306
  zIndex: isSelected ? 1001 : 1e3,
1183
307
  transition: "all 0.2s ease",
1184
- fontSize: commentCount > 1 ? "0.7rem" : void 0,
1185
- ...pulseAnimation
308
+ fontSize: commentCount > 1 ? "0.7rem" : void 0
1186
309
  },
1187
- children: isError ? /* @__PURE__ */ jsx3(ExclamationTriangleIcon, { style: { fontSize: "1rem" } }) : commentCount === 0 ? /* @__PURE__ */ jsx3("span", { style: { fontWeight: "bold", fontSize: "0.75rem" }, children: "0" }) : commentCount === 1 ? /* @__PURE__ */ jsx3(CommentIcon, {}) : /* @__PURE__ */ jsx3("span", { style: { fontWeight: "bold" }, children: commentCount })
310
+ children: commentCount === 0 ? /* @__PURE__ */ jsx3("span", { style: { fontWeight: "bold", fontSize: "0.75rem" }, children: "0" }) : commentCount === 1 ? /* @__PURE__ */ jsx3(CommentIcon, {}) : /* @__PURE__ */ jsx3("span", { style: { fontWeight: "bold" }, children: commentCount })
1188
311
  }
1189
312
  )
1190
313
  ] });
@@ -1257,7 +380,7 @@ var CommentOverlay = ({
1257
380
  };
1258
381
 
1259
382
  // src/components/CommentDrawer.tsx
1260
- import * as React6 from "react";
383
+ import * as React5 from "react";
1261
384
  import {
1262
385
  Drawer,
1263
386
  DrawerContent,
@@ -1275,91 +398,29 @@ import {
1275
398
  EmptyState,
1276
399
  EmptyStateBody,
1277
400
  Divider,
1278
- Label,
1279
- Spinner,
1280
401
  ExpandableSection,
1281
402
  Alert
1282
403
  } from "@patternfly/react-core";
1283
- import { CommentIcon as CommentIcon2, TimesIcon, PlusCircleIcon, SyncAltIcon, GithubIcon, ExternalLinkAltIcon, RedoIcon, MagicIcon } from "@patternfly/react-icons";
404
+ import { CommentIcon as CommentIcon2, TimesIcon, PlusCircleIcon, MagicIcon } from "@patternfly/react-icons";
1284
405
  import { useLocation as useLocation2 } from "react-router-dom";
1285
406
 
1286
407
  // src/contexts/GitLabAuthContext.tsx
1287
408
  import * as React4 from "react";
1288
409
  import { jsx as jsx5 } from "react/jsx-runtime";
1289
410
  var GitLabAuthContext = React4.createContext(void 0);
1290
- var GITLAB_TOKEN_KEY = "gitlab_access_token";
1291
- var GITLAB_USER_KEY = "gitlab_user";
1292
411
  var GitLabAuthProvider = ({ children }) => {
1293
- const [user, setUser] = React4.useState(null);
1294
- React4.useEffect(() => {
1295
- try {
1296
- const token = localStorage.getItem(GITLAB_TOKEN_KEY);
1297
- const userStr = localStorage.getItem(GITLAB_USER_KEY);
1298
- if (token && userStr) {
1299
- setUser(JSON.parse(userStr));
1300
- }
1301
- } catch {
1302
- }
1303
- }, []);
1304
- React4.useEffect(() => {
1305
- const handleCallback = () => {
1306
- const hash = window.location.hash;
1307
- if (hash.includes("#/auth-callback")) {
1308
- const params = new URLSearchParams(hash.split("?")[1]);
1309
- const token = params.get("gitlab_token");
1310
- const username = params.get("gitlab_username") || void 0;
1311
- const avatar = params.get("gitlab_avatar") || void 0;
1312
- if (token) {
1313
- localStorage.setItem(GITLAB_TOKEN_KEY, token);
1314
- const u = { username, avatar };
1315
- localStorage.setItem(GITLAB_USER_KEY, JSON.stringify(u));
1316
- setUser(u);
1317
- window.location.hash = "/";
1318
- }
1319
- }
1320
- };
1321
- handleCallback();
1322
- }, []);
1323
- const getToken = React4.useCallback(() => {
1324
- try {
1325
- return localStorage.getItem(GITLAB_TOKEN_KEY);
1326
- } catch {
1327
- return null;
1328
- }
1329
- }, []);
1330
- const login = () => {
1331
- const clientId = process.env.VITE_GITLAB_CLIENT_ID;
1332
- const baseUrl = process.env.VITE_GITLAB_BASE_URL || "https://gitlab.com";
1333
- if (!clientId) {
1334
- alert("GitLab login is not configured (missing VITE_GITLAB_CLIENT_ID).");
1335
- return;
1336
- }
1337
- const redirectUri = `${window.location.origin}/api/auth/callback`;
1338
- const scope = encodeURIComponent("read_user api");
1339
- const url = `${baseUrl}/oauth/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${scope}`;
1340
- window.location.href = url;
1341
- };
1342
- const logout = () => {
1343
- try {
1344
- localStorage.removeItem(GITLAB_TOKEN_KEY);
1345
- localStorage.removeItem(GITLAB_USER_KEY);
1346
- } finally {
1347
- setUser(null);
1348
- }
412
+ const value = {
413
+ user: null,
414
+ isAuthenticated: false,
415
+ login: () => {
416
+ console.log("GitLab login not available in local mode");
417
+ },
418
+ logout: () => {
419
+ console.log("GitLab logout not available in local mode");
420
+ },
421
+ getToken: () => null
1349
422
  };
1350
- return /* @__PURE__ */ jsx5(
1351
- GitLabAuthContext.Provider,
1352
- {
1353
- value: {
1354
- user,
1355
- isAuthenticated: !!getToken(),
1356
- login,
1357
- logout,
1358
- getToken
1359
- },
1360
- children
1361
- }
1362
- );
423
+ return /* @__PURE__ */ jsx5(GitLabAuthContext.Provider, { value, children });
1363
424
  };
1364
425
  var useGitLabAuth = () => {
1365
426
  const ctx = React4.useContext(GitLabAuthContext);
@@ -1369,151 +430,8 @@ var useGitLabAuth = () => {
1369
430
  return ctx;
1370
431
  };
1371
432
 
1372
- // src/contexts/AIContext.tsx
1373
- import * as React5 from "react";
1374
- import { jsx as jsx6 } from "react/jsx-runtime";
1375
- var AIContext = React5.createContext(void 0);
1376
- var STORAGE_KEY2 = "apollo-ai-chat-history";
1377
- var VISIBILITY_KEY = "apollo-ai-chatbot-visible";
1378
- var AIProvider = ({ children }) => {
1379
- const [isChatbotVisible, setIsChatbotVisible] = React5.useState(() => {
1380
- try {
1381
- const stored = localStorage.getItem(VISIBILITY_KEY);
1382
- return stored === "true";
1383
- } catch (error) {
1384
- return false;
1385
- }
1386
- });
1387
- const [messages, setMessages] = React5.useState(() => {
1388
- try {
1389
- const stored = localStorage.getItem(STORAGE_KEY2);
1390
- if (stored) {
1391
- return JSON.parse(stored);
1392
- }
1393
- return [];
1394
- } catch (error) {
1395
- console.error("Failed to load AI chat history from localStorage:", error);
1396
- return [];
1397
- }
1398
- });
1399
- const [isLoading, setIsLoading] = React5.useState(false);
1400
- React5.useEffect(() => {
1401
- try {
1402
- localStorage.setItem(VISIBILITY_KEY, String(isChatbotVisible));
1403
- } catch (error) {
1404
- console.error("Failed to save chatbot visibility to localStorage:", error);
1405
- }
1406
- }, [isChatbotVisible]);
1407
- React5.useEffect(() => {
1408
- try {
1409
- localStorage.setItem(STORAGE_KEY2, JSON.stringify(messages));
1410
- } catch (error) {
1411
- console.error("Failed to save AI chat history to localStorage:", error);
1412
- }
1413
- }, [messages]);
1414
- const toggleChatbot = React5.useCallback(() => {
1415
- setIsChatbotVisible((prev) => !prev);
1416
- }, []);
1417
- const sendMessage = React5.useCallback(async (content, commentContext) => {
1418
- if (!content.trim()) return;
1419
- const userMessage = {
1420
- id: `user-${Date.now()}`,
1421
- role: "user",
1422
- content: content.trim(),
1423
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1424
- };
1425
- setMessages((prev) => [...prev, userMessage]);
1426
- setIsLoading(true);
1427
- try {
1428
- if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
1429
- await new Promise((resolve) => setTimeout(resolve, 1500));
1430
- const mockResponse = `**Mock AI Response** (local dev mode)
1431
-
1432
- I analyzed your query: "${content.trim()}"
1433
-
1434
- **Summary:**
1435
- - Found ${commentContext?.threads?.length || 0} comment threads
1436
- - Version: ${commentContext?.version || "unknown"}
1437
- - Page: ${commentContext?.route || "unknown"}
1438
-
1439
- *Note: This is a mock response. Deploy to production to test the real AI.*`;
1440
- const botMessage2 = {
1441
- id: `bot-${Date.now()}`,
1442
- role: "bot",
1443
- content: mockResponse,
1444
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1445
- };
1446
- setMessages((prev) => [...prev, botMessage2]);
1447
- return;
1448
- }
1449
- const response = await fetch("/api/ai-assistant", {
1450
- method: "POST",
1451
- headers: {
1452
- "Content-Type": "application/json"
1453
- },
1454
- body: JSON.stringify({
1455
- query: content.trim(),
1456
- threads: commentContext?.threads || [],
1457
- version: commentContext?.version || "unknown",
1458
- route: commentContext?.route
1459
- })
1460
- });
1461
- if (!response.ok) {
1462
- const errorData = await response.json();
1463
- throw new Error(errorData.message || `API error: ${response.status}`);
1464
- }
1465
- const data = await response.json();
1466
- const botMessage = {
1467
- id: `bot-${Date.now()}`,
1468
- role: "bot",
1469
- content: data.message || "No response received.",
1470
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1471
- };
1472
- setMessages((prev) => [...prev, botMessage]);
1473
- } catch (error) {
1474
- console.error("Failed to send message:", error);
1475
- const errorMessage = {
1476
- id: `bot-error-${Date.now()}`,
1477
- role: "bot",
1478
- content: error instanceof Error ? `Sorry, I encountered an error: ${error.message}` : "Sorry, something went wrong. Please try again.",
1479
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1480
- };
1481
- setMessages((prev) => [...prev, errorMessage]);
1482
- } finally {
1483
- setIsLoading(false);
1484
- }
1485
- }, []);
1486
- const clearHistory = React5.useCallback(() => {
1487
- setMessages([]);
1488
- try {
1489
- localStorage.removeItem(STORAGE_KEY2);
1490
- } catch (error) {
1491
- console.error("Failed to clear chat history from localStorage:", error);
1492
- }
1493
- }, []);
1494
- const value = React5.useMemo(
1495
- () => ({
1496
- isChatbotVisible,
1497
- messages,
1498
- isLoading,
1499
- toggleChatbot,
1500
- sendMessage,
1501
- clearHistory
1502
- }),
1503
- [isChatbotVisible, messages, isLoading, toggleChatbot, sendMessage, clearHistory]
1504
- );
1505
- return /* @__PURE__ */ jsx6(AIContext.Provider, { value, children });
1506
- };
1507
- var useAIContext = () => {
1508
- const context = React5.useContext(AIContext);
1509
- if (!context) {
1510
- throw new Error("useAIContext must be used within an AIProvider");
1511
- }
1512
- return context;
1513
- };
1514
-
1515
433
  // src/components/CommentDrawer.tsx
1516
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
434
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1517
435
  var CommentDrawer = ({
1518
436
  children,
1519
437
  selectedThreadId,
@@ -1526,40 +444,27 @@ var CommentDrawer = ({
1526
444
  updateComment,
1527
445
  deleteComment,
1528
446
  deleteThread,
1529
- enableCommenting,
1530
- syncFromGitHub,
1531
- isSyncing,
1532
- retrySync,
1533
- hasPendingSync
447
+ enableCommenting
1534
448
  } = useComments();
1535
449
  const { currentVersion } = useVersion();
1536
450
  const { isAuthenticated: isGitLabAuthenticated } = useGitLabAuth();
1537
- const { sendMessage, toggleChatbot, isChatbotVisible } = useAIContext();
1538
- const [editingCommentId, setEditingCommentId] = React6.useState(null);
1539
- const [editText, setEditText] = React6.useState("");
1540
- const [replyText, setReplyText] = React6.useState("");
1541
- const replyTextAreaRef = React6.useRef(null);
1542
- const [threadSummaries, setThreadSummaries] = React6.useState({});
1543
- const [loadingSummary, setLoadingSummary] = React6.useState(false);
1544
- const [summaryExpanded, setSummaryExpanded] = React6.useState(true);
451
+ const [editingCommentId, setEditingCommentId] = React5.useState(null);
452
+ const [editText, setEditText] = React5.useState("");
453
+ const [replyText, setReplyText] = React5.useState("");
454
+ const replyTextAreaRef = React5.useRef(null);
455
+ const [threadSummaries, setThreadSummaries] = React5.useState({});
456
+ const [loadingSummary, setLoadingSummary] = React5.useState(false);
457
+ const [summaryExpanded, setSummaryExpanded] = React5.useState(true);
1545
458
  const currentRouteThreads = getThreadsForRoute(location.pathname, currentVersion);
1546
459
  const selectedThread = currentRouteThreads.find((t) => t.id === selectedThreadId);
1547
460
  const isDrawerOpen = selectedThreadId !== null && selectedThread !== void 0;
1548
- const isUserAuthenticated = isGitHubConfigured() || isGitLabAuthenticated;
1549
- React6.useEffect(() => {
461
+ React5.useEffect(() => {
1550
462
  if (!isDrawerOpen || !enableCommenting) return;
1551
463
  const timer = setTimeout(() => {
1552
464
  replyTextAreaRef.current?.focus();
1553
465
  }, 100);
1554
466
  return () => clearTimeout(timer);
1555
467
  }, [isDrawerOpen, enableCommenting, selectedThreadId]);
1556
- React6.useEffect(() => {
1557
- if (isGitHubConfigured() || isGitLabConfigured()) {
1558
- syncFromGitHub(location.pathname).catch((err) => {
1559
- console.error("Failed to sync from providers:", err);
1560
- });
1561
- }
1562
- }, [location.pathname]);
1563
468
  const handleEdit = (commentId, text) => {
1564
469
  setEditingCommentId(commentId);
1565
470
  setEditText(text);
@@ -1581,86 +486,13 @@ var CommentDrawer = ({
1581
486
  }
1582
487
  };
1583
488
  const handleSummarizeThread = async () => {
1584
- if (!selectedThread) return;
1585
- setLoadingSummary(true);
1586
- setSummaryExpanded(true);
1587
- try {
1588
- const response = await fetch("/api/ai-assistant", {
1589
- method: "POST",
1590
- headers: {
1591
- "Content-Type": "application/json"
1592
- },
1593
- body: JSON.stringify({
1594
- query: `Summarize the feedback in this thread (${selectedThread.comments.length} comments)`,
1595
- threads: [selectedThread],
1596
- version: currentVersion || "unknown",
1597
- route: location.pathname
1598
- })
1599
- });
1600
- if (!response.ok) {
1601
- throw new Error(`API error: ${response.status}`);
1602
- }
1603
- const data = await response.json();
1604
- const summary = data.message || "No summary available.";
1605
- setThreadSummaries((prev) => ({
1606
- ...prev,
1607
- [selectedThread.id]: summary
1608
- }));
1609
- } catch (error) {
1610
- console.error("Failed to generate summary:", error);
1611
- setThreadSummaries((prev) => ({
1612
- ...prev,
1613
- [selectedThread.id]: "Failed to generate summary. Please try again."
1614
- }));
1615
- } finally {
1616
- setLoadingSummary(false);
1617
- }
489
+ console.log("AI features not available in local-only mode");
1618
490
  };
1619
491
  const handleDeleteComment = async (threadId, commentId) => {
1620
492
  if (window.confirm("Delete this comment?")) {
1621
493
  await deleteComment(threadId, commentId);
1622
494
  }
1623
495
  };
1624
- const handleSync = async () => {
1625
- await syncFromGitHub(location.pathname);
1626
- };
1627
- const handleRetrySync = async () => {
1628
- await retrySync();
1629
- };
1630
- const getSyncStatusLabel = (status) => {
1631
- switch (status) {
1632
- case "synced":
1633
- return /* @__PURE__ */ jsx7(Label, { color: "green", icon: /* @__PURE__ */ jsx7(GithubIcon, {}), children: "Synced" });
1634
- case "local":
1635
- return /* @__PURE__ */ jsx7(Label, { color: "grey", children: "Local Only" });
1636
- case "pending":
1637
- return /* @__PURE__ */ jsx7(Label, { color: "blue", icon: /* @__PURE__ */ jsx7(Spinner, { size: "sm" }), children: "Pending..." });
1638
- case "syncing":
1639
- return /* @__PURE__ */ jsx7(Label, { color: "blue", icon: /* @__PURE__ */ jsx7(Spinner, { size: "sm" }), children: "Syncing..." });
1640
- case "error":
1641
- return /* @__PURE__ */ jsx7(Label, { color: "red", children: "Sync Error" });
1642
- default:
1643
- return null;
1644
- }
1645
- };
1646
- const getIssueLink = (provider, issueNumber) => {
1647
- if (!issueNumber || !provider) return null;
1648
- try {
1649
- if (provider === "gitlab") {
1650
- const baseUrl = process.env.VITE_GITLAB_BASE_URL || "https://gitlab.com";
1651
- const projectPath = process.env.VITE_GITLAB_PROJECT_PATH;
1652
- if (!projectPath) return null;
1653
- return `${baseUrl}/${projectPath}/-/issues/${issueNumber}`;
1654
- } else {
1655
- const owner = process.env.VITE_GITHUB_OWNER;
1656
- const repo = process.env.VITE_GITHUB_REPO;
1657
- if (!owner || !repo) return null;
1658
- return `https://github.com/${owner}/${repo}/issues/${issueNumber}`;
1659
- }
1660
- } catch (error) {
1661
- return null;
1662
- }
1663
- };
1664
496
  const formatDate = (isoDate) => {
1665
497
  const date = new Date(isoDate);
1666
498
  return date.toLocaleString(void 0, {
@@ -1672,51 +504,20 @@ var CommentDrawer = ({
1672
504
  };
1673
505
  const panelContent = /* @__PURE__ */ jsxs2(DrawerPanelContent, { isResizable: true, defaultSize: "400px", minSize: "300px", children: [
1674
506
  /* @__PURE__ */ jsxs2(DrawerHead, { children: [
1675
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", flex: 1 }, children: [
1676
- /* @__PURE__ */ jsxs2(Title, { headingLevel: "h2", size: "xl", children: [
1677
- /* @__PURE__ */ jsx7(CommentIcon2, { style: { marginRight: "0.5rem", color: "#C9190B" } }),
1678
- "Thread"
1679
- ] }),
1680
- (isGitHubConfigured() || isGitLabConfigured()) && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1681
- /* @__PURE__ */ jsx7(
1682
- Button2,
1683
- {
1684
- id: "sync-github-button",
1685
- variant: "plain",
1686
- size: "sm",
1687
- icon: /* @__PURE__ */ jsx7(SyncAltIcon, {}),
1688
- onClick: handleSync,
1689
- isDisabled: isSyncing,
1690
- "aria-label": "Sync with remote",
1691
- title: "Sync with remote",
1692
- children: isSyncing ? /* @__PURE__ */ jsx7(Spinner, { size: "sm" }) : null
1693
- }
1694
- ),
1695
- hasPendingSync && /* @__PURE__ */ jsx7(
1696
- Button2,
1697
- {
1698
- id: "retry-sync-button",
1699
- variant: "plain",
1700
- size: "sm",
1701
- icon: /* @__PURE__ */ jsx7(RedoIcon, {}),
1702
- onClick: handleRetrySync,
1703
- isDisabled: isSyncing,
1704
- "aria-label": "Retry sync for pending threads",
1705
- title: "Retry sync for pending threads"
1706
- }
1707
- )
1708
- ] })
1709
- ] }),
1710
- /* @__PURE__ */ jsx7(DrawerActions, { children: /* @__PURE__ */ jsx7(DrawerCloseButton, { onClick: () => onThreadSelect(null) }) })
507
+ /* @__PURE__ */ jsx6("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", flex: 1 }, children: /* @__PURE__ */ jsxs2(Title, { headingLevel: "h2", size: "xl", children: [
508
+ /* @__PURE__ */ jsx6(CommentIcon2, { style: { marginRight: "0.5rem", color: "#C9190B" } }),
509
+ "Thread"
510
+ ] }) }),
511
+ /* @__PURE__ */ jsx6(DrawerActions, { children: /* @__PURE__ */ jsx6(DrawerCloseButton, { onClick: () => onThreadSelect(null) }) })
1711
512
  ] }),
1712
- /* @__PURE__ */ jsx7(DrawerContentBody, { style: { padding: "1rem" }, children: !selectedThread ? /* @__PURE__ */ jsxs2(EmptyState, { children: [
1713
- /* @__PURE__ */ jsx7(CommentIcon2, { style: { fontSize: "3rem", color: "var(--pf-v6-global--Color--200)", marginBottom: "1rem" } }),
1714
- /* @__PURE__ */ jsx7(Title, { headingLevel: "h3", size: "lg", children: "No thread selected" }),
1715
- /* @__PURE__ */ jsx7(EmptyStateBody, { children: "Click a pin to view its comments." })
513
+ /* @__PURE__ */ jsx6(DrawerContentBody, { style: { padding: "1rem" }, children: !selectedThread ? /* @__PURE__ */ jsxs2(EmptyState, { children: [
514
+ /* @__PURE__ */ jsx6(CommentIcon2, { style: { fontSize: "3rem", color: "var(--pf-v6-global--Color--200)", marginBottom: "1rem" } }),
515
+ /* @__PURE__ */ jsx6(Title, { headingLevel: "h3", size: "lg", children: "No thread selected" }),
516
+ /* @__PURE__ */ jsx6(EmptyStateBody, { children: "Click a pin to view its comments." })
1716
517
  ] }) : /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: "1rem" }, children: [
1717
- /* @__PURE__ */ jsx7(Card, { isCompact: true, children: /* @__PURE__ */ jsxs2(CardBody, { children: [
518
+ /* @__PURE__ */ jsx6(Card, { isCompact: true, children: /* @__PURE__ */ jsxs2(CardBody, { children: [
1718
519
  /* @__PURE__ */ jsxs2("div", { style: { fontSize: "0.875rem", marginBottom: "0.5rem" }, children: [
1719
- /* @__PURE__ */ jsx7("strong", { children: "Location:" }),
520
+ /* @__PURE__ */ jsx6("strong", { children: "Location:" }),
1720
521
  " (",
1721
522
  Math.round(selectedThread.x),
1722
523
  ", ",
@@ -1724,41 +525,22 @@ var CommentDrawer = ({
1724
525
  ")"
1725
526
  ] }),
1726
527
  selectedThread.version && /* @__PURE__ */ jsxs2("div", { style: { fontSize: "0.875rem", marginBottom: "0.5rem" }, children: [
1727
- /* @__PURE__ */ jsx7("strong", { children: "Version:" }),
528
+ /* @__PURE__ */ jsx6("strong", { children: "Version:" }),
1728
529
  " ",
1729
530
  selectedThread.version
1730
531
  ] }),
1731
532
  /* @__PURE__ */ jsxs2("div", { style: { fontSize: "0.875rem", marginBottom: "0.5rem" }, children: [
1732
- /* @__PURE__ */ jsx7("strong", { children: "Comments:" }),
533
+ /* @__PURE__ */ jsx6("strong", { children: "Comments:" }),
1733
534
  " ",
1734
535
  selectedThread.comments.length
1735
536
  ] }),
1736
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", marginBottom: "0.5rem" }, children: [
1737
- /* @__PURE__ */ jsx7("strong", { style: { fontSize: "0.875rem" }, children: "Status:" }),
1738
- getSyncStatusLabel(selectedThread.syncStatus)
1739
- ] }),
1740
- selectedThread.issueNumber && /* @__PURE__ */ jsx7("div", { style: { fontSize: "0.875rem", marginBottom: "0.5rem" }, children: /* @__PURE__ */ jsxs2(
1741
- "a",
1742
- {
1743
- href: getIssueLink(selectedThread.provider, selectedThread.issueNumber) || "#",
1744
- target: "_blank",
1745
- rel: "noopener noreferrer",
1746
- style: { display: "inline-flex", alignItems: "center", gap: "0.25rem" },
1747
- children: [
1748
- /* @__PURE__ */ jsx7(GithubIcon, {}),
1749
- "Issue #",
1750
- selectedThread.issueNumber,
1751
- /* @__PURE__ */ jsx7(ExternalLinkAltIcon, { style: { fontSize: "0.75rem" } })
1752
- ]
1753
- }
1754
- ) }),
1755
- selectedThread.comments.length > 0 && /* @__PURE__ */ jsx7(
537
+ selectedThread.comments.length > 0 && /* @__PURE__ */ jsx6(
1756
538
  Button2,
1757
539
  {
1758
540
  id: `ai-summarize-thread-${selectedThread.id}`,
1759
541
  variant: "secondary",
1760
542
  size: "sm",
1761
- icon: /* @__PURE__ */ jsx7(MagicIcon, {}),
543
+ icon: /* @__PURE__ */ jsx6(MagicIcon, {}),
1762
544
  onClick: handleSummarizeThread,
1763
545
  isLoading: loadingSummary,
1764
546
  isDisabled: loadingSummary,
@@ -1766,26 +548,26 @@ var CommentDrawer = ({
1766
548
  children: loadingSummary ? "Generating..." : "AI Summarize Thread"
1767
549
  }
1768
550
  ),
1769
- enableCommenting && isUserAuthenticated && /* @__PURE__ */ jsx7(
551
+ enableCommenting && /* @__PURE__ */ jsx6(
1770
552
  Button2,
1771
553
  {
1772
554
  id: `delete-thread-${selectedThread.id}`,
1773
555
  variant: "danger",
1774
556
  size: "sm",
1775
- icon: /* @__PURE__ */ jsx7(TimesIcon, {}),
557
+ icon: /* @__PURE__ */ jsx6(TimesIcon, {}),
1776
558
  onClick: handleDeleteThread,
1777
559
  style: { marginTop: "0.5rem", marginLeft: selectedThread.comments.length > 0 ? "0.5rem" : "0" },
1778
560
  children: "Delete Thread"
1779
561
  }
1780
562
  )
1781
563
  ] }) }),
1782
- threadSummaries[selectedThread.id] && /* @__PURE__ */ jsx7(
564
+ threadSummaries[selectedThread.id] && /* @__PURE__ */ jsx6(
1783
565
  Alert,
1784
566
  {
1785
567
  variant: "info",
1786
568
  isInline: true,
1787
569
  title: "AI Summary",
1788
- actionClose: /* @__PURE__ */ jsx7(
570
+ actionClose: /* @__PURE__ */ jsx6(
1789
571
  Button2,
1790
572
  {
1791
573
  variant: "plain",
@@ -1795,25 +577,25 @@ var CommentDrawer = ({
1795
577
  setThreadSummaries(newSummaries);
1796
578
  },
1797
579
  "aria-label": "Clear summary",
1798
- children: /* @__PURE__ */ jsx7(TimesIcon, {})
580
+ children: /* @__PURE__ */ jsx6(TimesIcon, {})
1799
581
  }
1800
582
  ),
1801
- children: /* @__PURE__ */ jsx7(
583
+ children: /* @__PURE__ */ jsx6(
1802
584
  ExpandableSection,
1803
585
  {
1804
586
  toggleText: summaryExpanded ? "Hide summary" : "Show summary",
1805
587
  onToggle: (_event, isExpanded) => setSummaryExpanded(isExpanded),
1806
588
  isExpanded: summaryExpanded,
1807
589
  isIndented: true,
1808
- children: /* @__PURE__ */ jsx7("div", { style: { fontSize: "0.875rem", lineHeight: "1.5" }, children: threadSummaries[selectedThread.id] })
590
+ children: /* @__PURE__ */ jsx6("div", { style: { fontSize: "0.875rem", lineHeight: "1.5" }, children: threadSummaries[selectedThread.id] })
1809
591
  }
1810
592
  )
1811
593
  }
1812
594
  ),
1813
- /* @__PURE__ */ jsx7(Divider, {}),
1814
- /* @__PURE__ */ jsx7("div", { style: { display: "flex", flexDirection: "column", gap: "1rem" }, children: selectedThread.comments.length === 0 ? /* @__PURE__ */ jsxs2(EmptyState, { children: [
1815
- /* @__PURE__ */ jsx7(Title, { headingLevel: "h4", size: "md", children: "No comments yet" }),
1816
- /* @__PURE__ */ jsx7(EmptyStateBody, { children: enableCommenting ? "Add a reply below to start the conversation." : "Enable commenting to add replies." })
595
+ /* @__PURE__ */ jsx6(Divider, {}),
596
+ /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: "1rem" }, children: selectedThread.comments.length === 0 ? /* @__PURE__ */ jsxs2(EmptyState, { children: [
597
+ /* @__PURE__ */ jsx6(Title, { headingLevel: "h4", size: "md", children: "No comments yet" }),
598
+ /* @__PURE__ */ jsx6(EmptyStateBody, { children: enableCommenting ? "Add a reply below to start the conversation." : "Enable commenting to add replies." })
1817
599
  ] }) : selectedThread.comments.map((comment, index) => /* @__PURE__ */ jsxs2(Card, { isCompact: true, children: [
1818
600
  /* @__PURE__ */ jsxs2(CardTitle, { children: [
1819
601
  "Comment #",
@@ -1826,8 +608,8 @@ var CommentDrawer = ({
1826
608
  formatDate(comment.createdAt)
1827
609
  ] })
1828
610
  ] }),
1829
- /* @__PURE__ */ jsx7(CardBody, { children: editingCommentId === comment.id ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
1830
- /* @__PURE__ */ jsx7(
611
+ /* @__PURE__ */ jsx6(CardBody, { children: editingCommentId === comment.id ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
612
+ /* @__PURE__ */ jsx6(
1831
613
  TextArea,
1832
614
  {
1833
615
  id: `edit-comment-${comment.id}`,
@@ -1847,7 +629,7 @@ var CommentDrawer = ({
1847
629
  }
1848
630
  ),
1849
631
  /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "0.5rem" }, children: [
1850
- /* @__PURE__ */ jsx7(
632
+ /* @__PURE__ */ jsx6(
1851
633
  Button2,
1852
634
  {
1853
635
  id: `save-comment-${comment.id}`,
@@ -1857,7 +639,7 @@ var CommentDrawer = ({
1857
639
  children: "Save"
1858
640
  }
1859
641
  ),
1860
- /* @__PURE__ */ jsx7(
642
+ /* @__PURE__ */ jsx6(
1861
643
  Button2,
1862
644
  {
1863
645
  id: `cancel-edit-${comment.id}`,
@@ -1869,9 +651,9 @@ var CommentDrawer = ({
1869
651
  )
1870
652
  ] })
1871
653
  ] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1872
- /* @__PURE__ */ jsx7("div", { style: { marginBottom: "0.75rem", whiteSpace: "pre-wrap" }, children: comment.text || /* @__PURE__ */ jsx7("em", { style: { color: "var(--pf-v6-global--Color--200)" }, children: "No text" }) }),
1873
- enableCommenting && isUserAuthenticated && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "0.5rem" }, children: [
1874
- /* @__PURE__ */ jsx7(
654
+ /* @__PURE__ */ jsx6("div", { style: { marginBottom: "0.75rem", whiteSpace: "pre-wrap" }, children: comment.text || /* @__PURE__ */ jsx6("em", { style: { color: "var(--pf-v6-global--Color--200)" }, children: "No text" }) }),
655
+ enableCommenting && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "0.5rem" }, children: [
656
+ /* @__PURE__ */ jsx6(
1875
657
  Button2,
1876
658
  {
1877
659
  id: `edit-comment-btn-${comment.id}`,
@@ -1881,13 +663,13 @@ var CommentDrawer = ({
1881
663
  children: "Edit"
1882
664
  }
1883
665
  ),
1884
- /* @__PURE__ */ jsx7(
666
+ /* @__PURE__ */ jsx6(
1885
667
  Button2,
1886
668
  {
1887
669
  id: `delete-comment-btn-${comment.id}`,
1888
670
  variant: "danger",
1889
671
  size: "sm",
1890
- icon: /* @__PURE__ */ jsx7(TimesIcon, {}),
672
+ icon: /* @__PURE__ */ jsx6(TimesIcon, {}),
1891
673
  onClick: () => handleDeleteComment(selectedThread.id, comment.id),
1892
674
  children: "Delete"
1893
675
  }
@@ -1895,15 +677,15 @@ var CommentDrawer = ({
1895
677
  ] })
1896
678
  ] }) })
1897
679
  ] }, comment.id)) }),
1898
- enableCommenting && isUserAuthenticated && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1899
- /* @__PURE__ */ jsx7(Divider, {}),
680
+ enableCommenting && /* @__PURE__ */ jsxs2(Fragment2, { children: [
681
+ /* @__PURE__ */ jsx6(Divider, {}),
1900
682
  /* @__PURE__ */ jsxs2(Card, { isCompact: true, children: [
1901
683
  /* @__PURE__ */ jsxs2(CardTitle, { children: [
1902
- /* @__PURE__ */ jsx7(PlusCircleIcon, { style: { marginRight: "0.5rem" } }),
684
+ /* @__PURE__ */ jsx6(PlusCircleIcon, { style: { marginRight: "0.5rem" } }),
1903
685
  "Add Reply"
1904
686
  ] }),
1905
687
  /* @__PURE__ */ jsxs2(CardBody, { children: [
1906
- /* @__PURE__ */ jsx7(
688
+ /* @__PURE__ */ jsx6(
1907
689
  TextArea,
1908
690
  {
1909
691
  ref: replyTextAreaRef,
@@ -1921,7 +703,7 @@ var CommentDrawer = ({
1921
703
  }
1922
704
  }
1923
705
  ),
1924
- /* @__PURE__ */ jsx7(
706
+ /* @__PURE__ */ jsx6(
1925
707
  Button2,
1926
708
  {
1927
709
  id: `add-reply-${selectedThread.id}`,
@@ -1937,323 +719,34 @@ var CommentDrawer = ({
1937
719
  ] })
1938
720
  ] }) })
1939
721
  ] });
1940
- return /* @__PURE__ */ jsx7(Drawer, { isExpanded: isDrawerOpen, isInline: true, position: "right", children: /* @__PURE__ */ jsx7(DrawerContent, { panelContent, children: /* @__PURE__ */ jsx7(DrawerContentBody, { children }) }) });
1941
- };
1942
-
1943
- // src/components/AIAssistant.tsx
1944
- import { ChatbotToggle } from "@patternfly/chatbot";
1945
-
1946
- // src/components/AIChatPanel.tsx
1947
- import * as React7 from "react";
1948
- import {
1949
- Chatbot,
1950
- ChatbotDisplayMode,
1951
- ChatbotHeader,
1952
- ChatbotHeaderActions,
1953
- ChatbotHeaderMain,
1954
- ChatbotHeaderTitle,
1955
- ChatbotContent,
1956
- ChatbotFooter,
1957
- ChatbotWelcomePrompt,
1958
- MessageBox,
1959
- Message
1960
- } from "@patternfly/chatbot";
1961
- import {
1962
- Button as Button3,
1963
- Label as Label2,
1964
- TextArea as TextArea2,
1965
- ActionList,
1966
- ActionListItem
1967
- } from "@patternfly/react-core";
1968
- import { TimesIcon as TimesIcon2, PaperPlaneIcon, TrashIcon } from "@patternfly/react-icons";
1969
- import { useLocation as useLocation3 } from "react-router-dom";
1970
- import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
1971
- var AIChatPanel = () => {
1972
- const { messages, isLoading, sendMessage, toggleChatbot, clearHistory } = useAIContext();
1973
- const { currentVersion } = useVersion();
1974
- const { threads } = useComments();
1975
- const location = useLocation3();
1976
- const [inputValue, setInputValue] = React7.useState("");
1977
- const commentCount = React7.useMemo(() => {
1978
- return threads.filter((t) => t.version === currentVersion).reduce((acc, t) => acc + t.comments.length, 0);
1979
- }, [threads, currentVersion]);
1980
- const handleSendMessage = async () => {
1981
- if (inputValue.trim() && !isLoading) {
1982
- await sendMessage(inputValue, {
1983
- threads,
1984
- version: currentVersion || "unknown",
1985
- route: location.pathname
1986
- });
1987
- setInputValue("");
1988
- }
1989
- };
1990
- const handleKeyDown = (event) => {
1991
- if (event.key === "Enter" && !event.shiftKey) {
1992
- event.preventDefault();
1993
- handleSendMessage();
1994
- }
1995
- };
1996
- const handleQuickAction = (action) => {
1997
- setInputValue(action);
1998
- };
1999
- return /* @__PURE__ */ jsx8(
2000
- "div",
2001
- {
2002
- style: {
2003
- position: "fixed",
2004
- bottom: "80px",
2005
- right: "20px",
2006
- width: "400px",
2007
- maxHeight: "600px",
2008
- zIndex: 2e3,
2009
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)"
2010
- },
2011
- children: /* @__PURE__ */ jsxs3(Chatbot, { displayMode: ChatbotDisplayMode.default, children: [
2012
- /* @__PURE__ */ jsxs3(ChatbotHeader, { children: [
2013
- /* @__PURE__ */ jsxs3(ChatbotHeaderMain, { children: [
2014
- /* @__PURE__ */ jsx8(ChatbotHeaderTitle, { children: "\u{1F916} AI Feedback Assistant" }),
2015
- currentVersion && commentCount > 0 && /* @__PURE__ */ jsxs3("span", { style: { marginLeft: "0.5rem" }, children: [
2016
- /* @__PURE__ */ jsxs3(Label2, { color: "blue", isCompact: true, children: [
2017
- "Version ",
2018
- currentVersion
2019
- ] }),
2020
- /* @__PURE__ */ jsxs3(Label2, { color: "grey", isCompact: true, style: { marginLeft: "0.25rem" }, children: [
2021
- commentCount,
2022
- " comments"
2023
- ] })
2024
- ] })
2025
- ] }),
2026
- /* @__PURE__ */ jsxs3(ChatbotHeaderActions, { children: [
2027
- messages.length > 0 && /* @__PURE__ */ jsx8(
2028
- Button3,
2029
- {
2030
- variant: "plain",
2031
- onClick: clearHistory,
2032
- "aria-label": "Clear chat history",
2033
- icon: /* @__PURE__ */ jsx8(TrashIcon, {})
2034
- }
2035
- ),
2036
- /* @__PURE__ */ jsx8(
2037
- Button3,
2038
- {
2039
- variant: "plain",
2040
- onClick: toggleChatbot,
2041
- "aria-label": "Close chatbot",
2042
- icon: /* @__PURE__ */ jsx8(TimesIcon2, {})
2043
- }
2044
- )
2045
- ] })
2046
- ] }),
2047
- /* @__PURE__ */ jsx8(ChatbotContent, { children: /* @__PURE__ */ jsx8(MessageBox, { children: messages.length === 0 ? /* @__PURE__ */ jsx8(
2048
- ChatbotWelcomePrompt,
2049
- {
2050
- title: "Welcome to AI Feedback Assistant",
2051
- description: "Ask me about comments across your prototype. I can help you:",
2052
- children: /* @__PURE__ */ jsxs3(ActionList, { children: [
2053
- /* @__PURE__ */ jsx8(ActionListItem, { children: /* @__PURE__ */ jsx8(
2054
- Button3,
2055
- {
2056
- variant: "link",
2057
- isInline: true,
2058
- onClick: () => handleQuickAction("What feedback was left in the last week?"),
2059
- children: "Summarize recent feedback"
2060
- }
2061
- ) }),
2062
- /* @__PURE__ */ jsx8(ActionListItem, { children: /* @__PURE__ */ jsx8(
2063
- Button3,
2064
- {
2065
- variant: "link",
2066
- isInline: true,
2067
- onClick: () => handleQuickAction("Show me all accessibility issues"),
2068
- children: "Find accessibility issues"
2069
- }
2070
- ) }),
2071
- /* @__PURE__ */ jsx8(ActionListItem, { children: /* @__PURE__ */ jsx8(
2072
- Button3,
2073
- {
2074
- variant: "link",
2075
- isInline: true,
2076
- onClick: () => handleQuickAction("What are the main navigation problems?"),
2077
- children: "Identify navigation problems"
2078
- }
2079
- ) }),
2080
- /* @__PURE__ */ jsx8(ActionListItem, { children: /* @__PURE__ */ jsx8(
2081
- Button3,
2082
- {
2083
- variant: "link",
2084
- isInline: true,
2085
- onClick: () => handleQuickAction("Which page has the most comments?"),
2086
- children: "Analyze comment distribution"
2087
- }
2088
- ) })
2089
- ] })
2090
- }
2091
- ) : /* @__PURE__ */ jsxs3(Fragment3, { children: [
2092
- messages.map((msg) => /* @__PURE__ */ jsx8(
2093
- Message,
2094
- {
2095
- name: msg.role === "user" ? "You" : "AI Assistant",
2096
- role: msg.role === "user" ? "user" : "bot",
2097
- avatar: msg.role === "user" ? "/images/user_advatar.jpg" : "/images/halefavicon.png",
2098
- content: msg.content,
2099
- timestamp: new Date(msg.timestamp).toLocaleString(void 0, {
2100
- month: "short",
2101
- day: "numeric",
2102
- hour: "2-digit",
2103
- minute: "2-digit"
2104
- })
2105
- },
2106
- msg.id
2107
- )),
2108
- isLoading && /* @__PURE__ */ jsx8(
2109
- Message,
2110
- {
2111
- name: "AI Assistant",
2112
- role: "bot",
2113
- avatar: "/images/halefavicon.png",
2114
- content: "\u23F3 Analyzing comments..."
2115
- }
2116
- )
2117
- ] }) }) }),
2118
- /* @__PURE__ */ jsx8(ChatbotFooter, { children: /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "0.5rem", alignItems: "flex-end", width: "100%" }, children: [
2119
- /* @__PURE__ */ jsx8(
2120
- TextArea2,
2121
- {
2122
- id: "ai-chat-input",
2123
- value: inputValue,
2124
- onChange: (_event, value) => setInputValue(value),
2125
- onKeyDown: handleKeyDown,
2126
- placeholder: "Ask about feedback...",
2127
- "aria-label": "Message input",
2128
- rows: 2,
2129
- style: { flex: 1 },
2130
- isDisabled: isLoading
2131
- }
2132
- ),
2133
- /* @__PURE__ */ jsx8(
2134
- Button3,
2135
- {
2136
- variant: "primary",
2137
- onClick: handleSendMessage,
2138
- isDisabled: !inputValue.trim() || isLoading,
2139
- icon: /* @__PURE__ */ jsx8(PaperPlaneIcon, {}),
2140
- "aria-label": "Send message"
2141
- }
2142
- )
2143
- ] }) })
2144
- ] })
2145
- }
2146
- );
2147
- };
2148
-
2149
- // src/components/AIAssistant.tsx
2150
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
2151
- var AIAssistant = () => {
2152
- const { isChatbotVisible, toggleChatbot } = useAIContext();
2153
- return /* @__PURE__ */ jsxs4(Fragment4, { children: [
2154
- /* @__PURE__ */ jsx9(
2155
- "div",
2156
- {
2157
- style: {
2158
- position: "fixed",
2159
- bottom: "20px",
2160
- right: "20px",
2161
- zIndex: 2e3
2162
- },
2163
- children: /* @__PURE__ */ jsx9(
2164
- ChatbotToggle,
2165
- {
2166
- id: "ai-chatbot-toggle",
2167
- isChatbotVisible,
2168
- onToggleChatbot: toggleChatbot,
2169
- toggleButtonLabel: "AI Assistant",
2170
- tooltipLabel: "Get AI help analyzing feedback",
2171
- style: {
2172
- backgroundColor: "#C9190B",
2173
- borderRadius: "50%",
2174
- width: "56px",
2175
- height: "56px",
2176
- border: "2px solid white",
2177
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
2178
- color: "white"
2179
- }
2180
- }
2181
- )
2182
- }
2183
- ),
2184
- isChatbotVisible && /* @__PURE__ */ jsx9(AIChatPanel, {})
2185
- ] });
722
+ return /* @__PURE__ */ jsx6(Drawer, { isExpanded: isDrawerOpen, isInline: true, position: "right", children: /* @__PURE__ */ jsx6(DrawerContent, { panelContent, children: /* @__PURE__ */ jsx6(DrawerContentBody, { children }) }) });
2186
723
  };
2187
724
 
2188
725
  // src/contexts/GitHubAuthContext.tsx
2189
- import * as React8 from "react";
2190
- import { jsx as jsx10 } from "react/jsx-runtime";
2191
- var GitHubAuthContext = React8.createContext(void 0);
726
+ import * as React6 from "react";
727
+ import { jsx as jsx7 } from "react/jsx-runtime";
728
+ var GitHubAuthContext = React6.createContext(void 0);
2192
729
  var GitHubAuthProvider = ({ children }) => {
2193
- const [user, setUser] = React8.useState(null);
2194
- React8.useEffect(() => {
2195
- const storedUser = getAuthenticatedUser();
2196
- if (storedUser) {
2197
- setUser(storedUser);
2198
- }
2199
- }, []);
2200
- React8.useEffect(() => {
2201
- const handleOAuthCallback = () => {
2202
- const hash = window.location.hash;
2203
- if (hash.includes("#/auth-callback")) {
2204
- const params = new URLSearchParams(hash.split("?")[1]);
2205
- const token = params.get("token");
2206
- const login2 = params.get("login");
2207
- const avatar = params.get("avatar");
2208
- if (token && login2 && avatar) {
2209
- storeGitHubAuth(token, login2, decodeURIComponent(avatar));
2210
- setUser({ login: login2, avatar: decodeURIComponent(avatar) });
2211
- window.location.hash = "/";
2212
- }
2213
- }
2214
- };
2215
- handleOAuthCallback();
2216
- }, []);
2217
- const login = () => {
2218
- const clientId = process.env.VITE_GITHUB_CLIENT_ID;
2219
- if (!clientId) {
2220
- alert("GitHub login is not configured (missing VITE_GITHUB_CLIENT_ID).");
2221
- return;
730
+ const value = {
731
+ user: null,
732
+ isAuthenticated: false,
733
+ login: () => {
734
+ console.log("GitHub login not available in local mode");
735
+ },
736
+ logout: () => {
737
+ console.log("GitHub logout not available in local mode");
2222
738
  }
2223
- const redirectUri = `${window.location.origin}/api/github-oauth-callback`;
2224
- const scope = "public_repo";
2225
- const githubAuthUrl = `https://github.com/login/oauth/authorize?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}`;
2226
- console.log("\u{1F50D} GitHub OAuth redirect:", { clientId: "present", redirectUri });
2227
- window.location.href = githubAuthUrl;
2228
739
  };
2229
- const logout = () => {
2230
- clearGitHubAuth();
2231
- setUser(null);
2232
- };
2233
- return /* @__PURE__ */ jsx10(
2234
- GitHubAuthContext.Provider,
2235
- {
2236
- value: {
2237
- user,
2238
- isAuthenticated: !!user,
2239
- login,
2240
- logout
2241
- },
2242
- children
2243
- }
2244
- );
740
+ return /* @__PURE__ */ jsx7(GitHubAuthContext.Provider, { value, children });
2245
741
  };
2246
742
  var useGitHubAuth = () => {
2247
- const context = React8.useContext(GitHubAuthContext);
743
+ const context = React6.useContext(GitHubAuthContext);
2248
744
  if (context === void 0) {
2249
745
  throw new Error("useGitHubAuth must be used within a GitHubAuthProvider");
2250
746
  }
2251
747
  return context;
2252
748
  };
2253
749
  export {
2254
- AIAssistant,
2255
- AIChatPanel,
2256
- AIProvider,
2257
750
  CommentDrawer,
2258
751
  CommentOverlay,
2259
752
  CommentPin,
@@ -2261,11 +754,6 @@ export {
2261
754
  GitHubAuthProvider,
2262
755
  GitLabAuthProvider,
2263
756
  VersionProvider,
2264
- githubAdapter,
2265
- gitlabAdapter,
2266
- isGitHubConfigured,
2267
- isGitLabConfigured,
2268
- useAIContext,
2269
757
  useComments,
2270
758
  useGitHubAuth,
2271
759
  useGitLabAuth,