@nikitadmitrieff/feedback-chat 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,10 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
3
  type Stage = 'created' | 'queued' | 'running' | 'validating' | 'preview_ready' | 'deployed' | 'failed' | 'rejected';
4
+ type ActivityEntry = {
5
+ message: string;
6
+ time: string;
7
+ };
4
8
  type StatusResponse = {
5
9
  stage: Stage;
6
10
  issueNumber: number;
@@ -9,6 +13,7 @@ type StatusResponse = {
9
13
  previewUrl?: string;
10
14
  prNumber?: number;
11
15
  prUrl?: string;
16
+ activity?: ActivityEntry[];
12
17
  };
13
18
  type Conversation = {
14
19
  id: string;
@@ -40,4 +45,4 @@ type PipelineTrackerProps = {
40
45
  };
41
46
  declare function PipelineTracker({ issueUrl, statusEndpoint, }: PipelineTrackerProps): react_jsx_runtime.JSX.Element;
42
47
 
43
- export { type Conversation, FeedbackPanel, type FeedbackPanelProps, PipelineTracker, type Stage, type StatusResponse, useConversations };
48
+ export { type ActivityEntry, type Conversation, FeedbackPanel, type FeedbackPanelProps, PipelineTracker, type Stage, type StatusResponse, useConversations };
@@ -95,6 +95,19 @@ function cn(...inputs) {
95
95
 
96
96
  // src/client/ui/tooltip.tsx
97
97
  import { jsx, jsxs } from "react/jsx-runtime";
98
+ function TooltipProvider({
99
+ delayDuration = 0,
100
+ ...props
101
+ }) {
102
+ return /* @__PURE__ */ jsx(
103
+ TooltipPrimitive.Provider,
104
+ {
105
+ "data-slot": "tooltip-provider",
106
+ delayDuration,
107
+ ...props
108
+ }
109
+ );
110
+ }
98
111
  function Tooltip({
99
112
  ...props
100
113
  }) {
@@ -1687,6 +1700,19 @@ var STAGE_INDEX = {
1687
1700
  failed: -1,
1688
1701
  rejected: -1
1689
1702
  };
1703
+ var STAGE_MESSAGE = {
1704
+ created: "Creating issue\u2026",
1705
+ queued: "Waiting for agent\u2026",
1706
+ running: "Running\u2026",
1707
+ validating: "Checking build\u2026",
1708
+ preview_ready: "Deploying preview\u2026"
1709
+ };
1710
+ function formatElapsed(ms) {
1711
+ const totalSeconds = Math.floor(ms / 1e3);
1712
+ const minutes = Math.floor(totalSeconds / 60);
1713
+ const seconds = totalSeconds % 60;
1714
+ return `${minutes}:${seconds.toString().padStart(2, "0")}`;
1715
+ }
1690
1716
  var TERMINAL_STAGES = ["deployed", "failed", "rejected"];
1691
1717
  var POLL_INTERVAL_MS = 5e3;
1692
1718
  var PREVIEW_POLL_INTERVAL_MS = 15e3;
@@ -1750,6 +1776,8 @@ function PipelineTracker({
1750
1776
  const [changeComment, setChangeComment] = useState6("");
1751
1777
  const [confirmReject, setConfirmReject] = useState6(false);
1752
1778
  const previousStageRef = useRef3(null);
1779
+ const stageStartRef = useRef3(Date.now());
1780
+ const [elapsed, setElapsed] = useState6(0);
1753
1781
  const fetchStatus = useCallback3(async () => {
1754
1782
  try {
1755
1783
  const res = await fetch(`${statusEndpoint}?issue=${issueNumber}`);
@@ -1768,6 +1796,17 @@ function PipelineTracker({
1768
1796
  previousStageRef.current = status.stage;
1769
1797
  }
1770
1798
  }, [status.stage]);
1799
+ useEffect3(() => {
1800
+ stageStartRef.current = Date.now();
1801
+ setElapsed(0);
1802
+ }, [status.stage]);
1803
+ useEffect3(() => {
1804
+ if (TERMINAL_STAGES.includes(status.stage)) return;
1805
+ const interval = setInterval(() => {
1806
+ setElapsed(Date.now() - stageStartRef.current);
1807
+ }, 1e3);
1808
+ return () => clearInterval(interval);
1809
+ }, [status.stage]);
1771
1810
  useEffect3(() => {
1772
1811
  setPipelineActive(issueNumber, "created");
1773
1812
  fetchStatus();
@@ -1815,18 +1854,39 @@ function PipelineTracker({
1815
1854
  const currentIndex = STAGE_INDEX[status.stage];
1816
1855
  const isFailed = status.stage === "failed";
1817
1856
  const failedAtIndex = isFailed ? getFailedStepIndex(previousStageRef.current) : -1;
1857
+ const progressIndex = isFailed ? failedAtIndex : currentIndex;
1858
+ const latestActivity = status.activity?.at(-1)?.message;
1859
+ const activityMessage = latestActivity || STAGE_MESSAGE[status.stage] || "";
1860
+ const isActive = !TERMINAL_STAGES.includes(status.stage);
1818
1861
  return /* @__PURE__ */ jsxs10("div", { className: "rounded-2xl border border-border bg-card p-3 space-y-2", children: [
1819
- /* @__PURE__ */ jsx14("div", { className: "space-y-0", children: STEPS.map((step, i) => {
1820
- const state = deriveStepState(i, currentIndex, failedAtIndex, status.stage);
1821
- return /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 h-6", children: [
1822
- /* @__PURE__ */ jsx14(StepDot, { state }),
1823
- /* @__PURE__ */ jsx14("span", { className: `text-xs ${STEP_LABEL_CLASS[state]}`, children: state === "failed" && status.failReason ? status.failReason : step.label }),
1824
- i === 0 && /* @__PURE__ */ jsxs10("span", { className: "ml-auto text-[11px] text-muted-foreground/60", children: [
1825
- "#",
1826
- issueNumber
1827
- ] })
1828
- ] }, step.stage);
1829
- }) }),
1862
+ /* @__PURE__ */ jsxs10("div", { className: "relative", children: [
1863
+ /* @__PURE__ */ jsx14("div", { className: "absolute left-[7.5px] top-3 bottom-3 w-px bg-muted-foreground/15" }),
1864
+ progressIndex > 0 && /* @__PURE__ */ jsx14(
1865
+ "div",
1866
+ {
1867
+ className: `absolute left-[7.5px] top-3 w-px transition-all duration-700 ease-out ${isFailed ? "bg-destructive/50" : "bg-emerald-500/50"}`,
1868
+ style: { height: `${progressIndex * 24}px` }
1869
+ }
1870
+ ),
1871
+ STEPS.map((step, i) => {
1872
+ const state = deriveStepState(i, currentIndex, failedAtIndex, status.stage);
1873
+ return /* @__PURE__ */ jsxs10("div", { children: [
1874
+ /* @__PURE__ */ jsxs10("div", { className: "relative flex items-center gap-2 h-6", children: [
1875
+ /* @__PURE__ */ jsx14(StepDot, { state }),
1876
+ /* @__PURE__ */ jsx14("span", { className: `text-xs truncate ${STEP_LABEL_CLASS[state]}`, children: step.label }),
1877
+ i === 0 && /* @__PURE__ */ jsxs10("span", { className: "ml-auto text-[11px] text-muted-foreground/60 shrink-0", children: [
1878
+ "#",
1879
+ issueNumber
1880
+ ] })
1881
+ ] }),
1882
+ state === "failed" && status.failReason && /* @__PURE__ */ jsx14("div", { className: "pl-6 pb-0.5", children: /* @__PURE__ */ jsx14("span", { className: "text-[10px] text-destructive/80 line-clamp-2", children: status.failReason }) }),
1883
+ state === "active" && isActive && activityMessage && /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 pl-6 h-5", children: [
1884
+ /* @__PURE__ */ jsx14("span", { className: "text-[10px] text-muted-foreground truncate flex-1", children: activityMessage }),
1885
+ /* @__PURE__ */ jsx14("span", { className: "text-[10px] text-muted-foreground/60 tabular-nums shrink-0", children: formatElapsed(elapsed) })
1886
+ ] })
1887
+ ] }, step.stage);
1888
+ })
1889
+ ] }),
1830
1890
  status.stage === "preview_ready" && status.previewUrl && /* @__PURE__ */ jsxs10("div", { className: "space-y-2.5 pt-2 border-t border-border", children: [
1831
1891
  /* @__PURE__ */ jsxs10(
1832
1892
  "a",
@@ -2061,7 +2121,7 @@ function FeedbackPanel({ isOpen, onToggle, apiUrl = "/api/feedback/chat" }) {
2061
2121
  const clearPendingMessage = useCallback4(() => {
2062
2122
  setPendingMessage("");
2063
2123
  }, []);
2064
- return /* @__PURE__ */ jsxs12(Fragment3, { children: [
2124
+ return /* @__PURE__ */ jsxs12(TooltipProvider, { children: [
2065
2125
  /* @__PURE__ */ jsx16(
2066
2126
  "div",
2067
2127
  {
@@ -37,6 +37,10 @@ type StatusHandlerConfig = {
37
37
  agentUrl?: string;
38
38
  };
39
39
  type Stage = 'created' | 'queued' | 'running' | 'validating' | 'preview_ready' | 'deployed' | 'failed' | 'rejected';
40
+ type ActivityEntry = {
41
+ message: string;
42
+ time: string;
43
+ };
40
44
  type StatusResponse = {
41
45
  stage: Stage;
42
46
  issueNumber: number;
@@ -45,6 +49,7 @@ type StatusResponse = {
45
49
  previewUrl?: string;
46
50
  prNumber?: number;
47
51
  prUrl?: string;
52
+ activity?: ActivityEntry[];
48
53
  };
49
54
  /**
50
55
  * Creates Next.js App Router GET and POST handlers for the feedback status endpoint.
@@ -112,4 +117,4 @@ declare function createGitHubIssue({ title, body, labels, }: {
112
117
  labels?: string[];
113
118
  }): Promise<string | null>;
114
119
 
115
- export { type FeedbackHandlerConfig, type GitHubIssueCreator, type Stage, type StatusHandlerConfig, type StatusResponse, buildDefaultPrompt, createFeedbackHandler, createGitHubIssue, createStatusHandler, createTools };
120
+ export { type ActivityEntry, type FeedbackHandlerConfig, type GitHubIssueCreator, type Stage, type StatusHandlerConfig, type StatusResponse, buildDefaultPrompt, createFeedbackHandler, createGitHubIssue, createStatusHandler, createTools };
@@ -284,17 +284,22 @@ async function getPreviewUrl(config, sha) {
284
284
  }
285
285
  return null;
286
286
  }
287
- async function getFailReason(config, issueNumber) {
287
+ async function getActivity(config, issueNumber) {
288
288
  const res = await fetch(
289
289
  `${issueEndpoint(config, issueNumber)}/comments?per_page=5&direction=desc`,
290
290
  { headers: githubHeaders(config.token), cache: "no-store" }
291
291
  );
292
- if (!res.ok) return void 0;
292
+ if (!res.ok) return [];
293
293
  const comments = await res.json();
294
- const failComment = comments.find(
295
- (c) => c.body?.startsWith("Agent failed:")
296
- );
297
- return failComment?.body?.replace("Agent failed:", "").trim();
294
+ if (!Array.isArray(comments)) return [];
295
+ return comments.slice(0, 3).map((c) => {
296
+ const raw = c.body ?? "";
297
+ const clean = raw.replace(/<[^>]*>/g, "").replace(/```[\s\S]*?```/g, "").replace(/`[^`]*`/g, "").replace(/[#*_~>\-|]/g, "").replace(/\s+/g, " ").trim().slice(0, 120);
298
+ return {
299
+ message: clean,
300
+ time: c.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
301
+ };
302
+ }).reverse();
298
303
  }
299
304
  async function isAgentRunning(agentUrl, issueNumber) {
300
305
  if (!agentUrl) return false;
@@ -313,8 +318,13 @@ async function deriveStage(config, issueNumber, agentUrl) {
313
318
  const labels = (issue.labels ?? []).map((l) => l.name);
314
319
  const issueUrl = issue.html_url;
315
320
  if (labels.includes("agent-failed")) {
316
- const failReason = await getFailReason(config, issueNumber);
317
- return { stage: "failed", issueUrl, failReason };
321
+ const activity = await getActivity(config, issueNumber);
322
+ const failComment = activity.find((a) => a.message.startsWith("Agent failed:"));
323
+ let failReason = failComment?.message.replace("Agent failed:", "").trim();
324
+ if (failReason) {
325
+ failReason = failReason.replace(/<[^>]*>/g, "").replace(/```[\s\S]*?```/g, "").replace(/`[^`]*`/g, "").replace(/\s+/g, " ").trim().slice(0, 80);
326
+ }
327
+ return { stage: "failed", issueUrl, failReason, activity };
318
328
  }
319
329
  if (labels.includes("rejected")) {
320
330
  return { stage: "rejected", issueUrl };
@@ -454,7 +464,8 @@ async function handleRequestChanges(config, issueNumber, comment) {
454
464
  await fetch(`${url}/comments`, {
455
465
  method: "POST",
456
466
  headers,
457
- body: JSON.stringify({ body: `**Changes requested:**
467
+ // Must match the string the agent's retry detection looks for
468
+ body: JSON.stringify({ body: `**Modifications demand\xE9es :**
458
469
 
459
470
  ${comment}` })
460
471
  });
@@ -501,6 +512,10 @@ function createStatusHandler(config) {
501
512
  if (!result) {
502
513
  return Response.json({ error: "Issue not found" }, { status: 404 });
503
514
  }
515
+ const terminal = ["deployed", "rejected"];
516
+ if (!terminal.includes(result.stage) && !result.activity) {
517
+ result.activity = await getActivity(ghConfig, issueNumber);
518
+ }
504
519
  const response = { issueNumber, ...result };
505
520
  return Response.json(response);
506
521
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nikitadmitrieff/feedback-chat",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "./dist/client/index.js",
6
6
  "types": "./dist/client/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "bin": {
24
24
  "feedback-chat": "./dist/cli/init.js"
25
25
  },
26
- "files": ["dist"],
26
+ "files": ["dist", "README.md"],
27
27
  "scripts": {
28
28
  "build": "tsup && cp src/client/styles.css dist/styles.css",
29
29
  "dev": "tsup --watch",