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