@invergent/agent-chat-react 2.0.7 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6470,6 +6470,194 @@ function ChatComposerInner({
6470
6470
  }
6471
6471
  );
6472
6472
  }
6473
+
6474
+ // src/components/chat/turn-feedback.tsx
6475
+ init_utils();
6476
+ var MAX_REASON_LENGTH2 = 500;
6477
+ function TurnFeedback({ msg }) {
6478
+ const { adapter, sessionId } = useAgentChatAdapterContext();
6479
+ const [pending, setPending] = react.useState(
6480
+ null
6481
+ );
6482
+ const [error, setError] = react.useState(null);
6483
+ const [reasonDraft, setReasonDraft] = react.useState(null);
6484
+ const llmResponseEventId = msg.llmResponseEventId;
6485
+ const canRate = llmResponseEventId !== void 0 && msg.status === "complete" && sessionId !== null && adapter.submitUserFeedback !== void 0;
6486
+ if (!canRate) return null;
6487
+ const rating = msg.userFeedback?.rating ?? pending;
6488
+ const alreadyRated = msg.userFeedback !== void 0;
6489
+ const submit = async (next, reason) => {
6490
+ if (sessionId === null || llmResponseEventId === void 0 || adapter.submitUserFeedback === void 0) return;
6491
+ setPending(next);
6492
+ setError(null);
6493
+ try {
6494
+ await adapter.submitUserFeedback({
6495
+ sessionId,
6496
+ llmResponseEventId,
6497
+ rating: next,
6498
+ ...reason ? { reason } : {}
6499
+ });
6500
+ setReasonDraft(null);
6501
+ } catch (e) {
6502
+ setPending(null);
6503
+ setError(e instanceof Error ? e.message : "Failed to submit feedback");
6504
+ }
6505
+ };
6506
+ const handleRate = (next) => {
6507
+ if (alreadyRated || pending !== null) return;
6508
+ if (next === "down") {
6509
+ setReasonDraft("");
6510
+ return;
6511
+ }
6512
+ void submit("up");
6513
+ };
6514
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 space-y-1.5", children: [
6515
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
6516
+ /* @__PURE__ */ jsxRuntime.jsx(
6517
+ Button,
6518
+ {
6519
+ type: "button",
6520
+ variant: "ghost",
6521
+ size: "icon-xs",
6522
+ "aria-label": "Good response",
6523
+ title: alreadyRated && rating === "up" ? "Good response (recorded)" : "Good response",
6524
+ disabled: pending !== null || alreadyRated,
6525
+ onClick: () => handleRate("up"),
6526
+ className: cn(
6527
+ rating === "up" ? "text-foreground" : "text-muted-foreground/60"
6528
+ ),
6529
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ThumbsUpIcon, { className: "size-3.5" })
6530
+ }
6531
+ ),
6532
+ /* @__PURE__ */ jsxRuntime.jsx(
6533
+ Button,
6534
+ {
6535
+ type: "button",
6536
+ variant: "ghost",
6537
+ size: "icon-xs",
6538
+ "aria-label": "Poor response",
6539
+ title: alreadyRated && rating === "down" ? "Poor response (recorded)" : "Poor response",
6540
+ disabled: pending !== null || reasonDraft !== null || alreadyRated,
6541
+ onClick: () => handleRate("down"),
6542
+ className: cn(
6543
+ rating === "down" ? "text-foreground" : "text-muted-foreground/60"
6544
+ ),
6545
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ThumbsDownIcon, { className: "size-3.5" })
6546
+ }
6547
+ ),
6548
+ alreadyRated && msg.userFeedback?.reason && /* @__PURE__ */ jsxRuntime.jsxs(
6549
+ "span",
6550
+ {
6551
+ className: "text-muted-foreground/70 truncate max-w-xs",
6552
+ title: msg.userFeedback.reason,
6553
+ children: [
6554
+ '\xB7 "',
6555
+ msg.userFeedback.reason,
6556
+ '"'
6557
+ ]
6558
+ }
6559
+ ),
6560
+ error && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500 ml-1", children: error })
6561
+ ] }),
6562
+ reasonDraft !== null && !alreadyRated && /* @__PURE__ */ jsxRuntime.jsx(
6563
+ ReasonForm2,
6564
+ {
6565
+ initialValue: reasonDraft,
6566
+ busy: pending !== null,
6567
+ onSubmit: (reason) => void submit("down", reason),
6568
+ onCancel: () => {
6569
+ setReasonDraft(null);
6570
+ setError(null);
6571
+ }
6572
+ }
6573
+ )
6574
+ ] });
6575
+ }
6576
+ function ReasonForm2({
6577
+ initialValue,
6578
+ busy,
6579
+ onSubmit,
6580
+ onCancel
6581
+ }) {
6582
+ const [value, setValue] = react.useState(initialValue);
6583
+ const trimmed = value.trim();
6584
+ const charsLeft = MAX_REASON_LENGTH2 - value.length;
6585
+ const submit = () => {
6586
+ if (trimmed && !busy) onSubmit(trimmed);
6587
+ };
6588
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border border-border bg-muted/30 p-2 space-y-1.5", children: [
6589
+ /* @__PURE__ */ jsxRuntime.jsx(
6590
+ "label",
6591
+ {
6592
+ className: "text-xs text-muted-foreground",
6593
+ htmlFor: "turn-feedback-reason",
6594
+ children: "What was wrong with the response?"
6595
+ }
6596
+ ),
6597
+ /* @__PURE__ */ jsxRuntime.jsx(
6598
+ Textarea,
6599
+ {
6600
+ id: "turn-feedback-reason",
6601
+ autoFocus: true,
6602
+ rows: 3,
6603
+ value,
6604
+ maxLength: MAX_REASON_LENGTH2,
6605
+ placeholder: "e.g. wrong answer, missed a constraint, hallucinated a fact\u2026",
6606
+ onChange: (e) => setValue(e.target.value),
6607
+ onKeyDown: (e) => {
6608
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
6609
+ e.preventDefault();
6610
+ submit();
6611
+ } else if (e.key === "Escape") {
6612
+ e.preventDefault();
6613
+ onCancel();
6614
+ }
6615
+ },
6616
+ className: "text-xs min-h-12",
6617
+ disabled: busy
6618
+ }
6619
+ ),
6620
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
6621
+ /* @__PURE__ */ jsxRuntime.jsxs(
6622
+ "span",
6623
+ {
6624
+ className: cn(
6625
+ "text-[10px] tabular-nums",
6626
+ charsLeft < 50 ? "text-amber-500" : "text-muted-foreground/60"
6627
+ ),
6628
+ children: [
6629
+ charsLeft,
6630
+ " characters left"
6631
+ ]
6632
+ }
6633
+ ),
6634
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
6635
+ /* @__PURE__ */ jsxRuntime.jsx(
6636
+ Button,
6637
+ {
6638
+ type: "button",
6639
+ variant: "ghost",
6640
+ size: "xs",
6641
+ onClick: onCancel,
6642
+ disabled: busy,
6643
+ children: "Cancel"
6644
+ }
6645
+ ),
6646
+ /* @__PURE__ */ jsxRuntime.jsx(
6647
+ Button,
6648
+ {
6649
+ type: "button",
6650
+ variant: "default",
6651
+ size: "xs",
6652
+ onClick: submit,
6653
+ disabled: busy || !trimmed,
6654
+ children: busy ? "Sending\u2026" : "Send feedback"
6655
+ }
6656
+ )
6657
+ ] })
6658
+ ] })
6659
+ ] });
6660
+ }
6473
6661
  var MIN_CPS = 40;
6474
6662
  var MAX_CPS = 240;
6475
6663
  var DRAIN_SECONDS = 0.25;
@@ -7130,7 +7318,8 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
7130
7318
  kind: "text",
7131
7319
  key: `${msg.id}-text`,
7132
7320
  content: msg.content,
7133
- isStreaming
7321
+ isStreaming,
7322
+ msg
7134
7323
  });
7135
7324
  }
7136
7325
  const hasRunningTool = hasToolCalls && msg.toolCalls.some(
@@ -7433,7 +7622,10 @@ function TextEntry({
7433
7622
  /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
7434
7623
  /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/40" })
7435
7624
  ] }),
7436
- /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: content }) })
7625
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineContent, { children: [
7626
+ /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: content }),
7627
+ entry.isFinalTurnText && entry.msg.status === "complete" && /* @__PURE__ */ jsxRuntime.jsx(TurnFeedback, { msg: entry.msg })
7628
+ ] })
7437
7629
  ] });
7438
7630
  }
7439
7631
  function AssistantGroup({
@@ -7453,6 +7645,13 @@ function AssistantGroup({
7453
7645
  }
7454
7646
  entries = groupBrowserActivityEntries(entries);
7455
7647
  entries = groupWebSearchEntries(entries);
7648
+ for (let i = entries.length - 1; i >= 0; i--) {
7649
+ const entry = entries[i];
7650
+ if (entry?.kind === "text") {
7651
+ entries[i] = { ...entry, isFinalTurnText: true };
7652
+ break;
7653
+ }
7654
+ }
7456
7655
  const isTailGroup = lastGlobalIndex === totalMessages - 1;
7457
7656
  const lastEntry = entries[entries.length - 1];
7458
7657
  const hasRunningTool = entries.some(
@@ -8774,6 +8973,7 @@ var AGENT_CHAT_LISTENED_EVENTS = [
8774
8973
  "expert.result",
8775
8974
  "expert.endorse",
8776
8975
  "expert.override",
8976
+ "user.feedback",
8777
8977
  "artifact.created",
8778
8978
  "artifact.updated",
8779
8979
  "browser.provisioned",
@@ -8932,6 +9132,11 @@ function applyAgentChatEvent(state, event) {
8932
9132
  nextState,
8933
9133
  applyExpertFeedback(nextState.messages, event.type, event.data)
8934
9134
  );
9135
+ case "user.feedback":
9136
+ return withMessages(
9137
+ nextState,
9138
+ applyUserFeedback(nextState.messages, event.data)
9139
+ );
8935
9140
  case "clarify.response":
8936
9141
  return withMessages(
8937
9142
  nextState,
@@ -9129,17 +9334,23 @@ function applyLlmResponse(state, event) {
9129
9334
  ...current,
9130
9335
  reasoning: (current.reasoning ?? "") + current.content,
9131
9336
  content: "",
9132
- status: "streaming"
9337
+ status: "streaming",
9338
+ llmResponseEventId: event.eventId
9133
9339
  };
9134
9340
  } else {
9135
- messages[idx] = { ...current, status: "complete" };
9341
+ messages[idx] = {
9342
+ ...current,
9343
+ status: "complete",
9344
+ llmResponseEventId: event.eventId
9345
+ };
9136
9346
  }
9137
9347
  } else if (matchesExistingToolTurn && idx >= 0) {
9138
9348
  const current = messages[idx];
9139
9349
  messages[idx] = {
9140
9350
  ...current,
9141
9351
  reasoning: responseContent ? appendText(current.reasoning, responseContent) : current.reasoning,
9142
- status: "streaming"
9352
+ status: "streaming",
9353
+ llmResponseEventId: event.eventId
9143
9354
  };
9144
9355
  } else if (prevHasTools || !prevAssistant || hasUserAfter) {
9145
9356
  messages.push({
@@ -9148,7 +9359,8 @@ function applyLlmResponse(state, event) {
9148
9359
  content: hasToolCalls ? "" : responseContent,
9149
9360
  reasoning: hasToolCalls && responseContent ? responseContent : void 0,
9150
9361
  createdAt: /* @__PURE__ */ new Date(),
9151
- status: hasToolCalls ? "streaming" : "complete"
9362
+ status: hasToolCalls ? "streaming" : "complete",
9363
+ llmResponseEventId: event.eventId
9152
9364
  });
9153
9365
  } else {
9154
9366
  const current = messages[idx];
@@ -9156,13 +9368,15 @@ function applyLlmResponse(state, event) {
9156
9368
  messages[idx] = {
9157
9369
  ...current,
9158
9370
  reasoning: (current.reasoning ?? "") + responseContent,
9159
- status: "streaming"
9371
+ status: "streaming",
9372
+ llmResponseEventId: event.eventId
9160
9373
  };
9161
9374
  } else {
9162
9375
  messages[idx] = {
9163
9376
  ...current,
9164
9377
  content: responseContent || current.content,
9165
- status: hasToolCalls ? "streaming" : "complete"
9378
+ status: hasToolCalls ? "streaming" : "complete",
9379
+ llmResponseEventId: event.eventId
9166
9380
  };
9167
9381
  }
9168
9382
  }
@@ -9375,6 +9589,26 @@ function applyExpertFeedback(messages, type, data) {
9375
9589
  }
9376
9590
  return messages;
9377
9591
  }
9592
+ function applyUserFeedback(messages, data) {
9593
+ const source = typeof data.source === "string" ? data.source : "user";
9594
+ if (source !== "user") return messages;
9595
+ const targetEventId = typeof data.target_event_id === "number" ? data.target_event_id : void 0;
9596
+ if (targetEventId == null) return messages;
9597
+ const rating = data.rating === "down" ? "down" : "up";
9598
+ const reason = typeof data.reason === "string" ? data.reason : void 0;
9599
+ for (let i = messages.length - 1; i >= 0; i--) {
9600
+ const msg = messages[i];
9601
+ if (msg?.role !== "assistant") continue;
9602
+ if (msg.llmResponseEventId !== targetEventId) continue;
9603
+ if (msg.userFeedback?.rating === rating && msg.userFeedback?.reason === reason) {
9604
+ return messages;
9605
+ }
9606
+ const next = [...messages];
9607
+ next[i] = { ...msg, userFeedback: { rating, reason } };
9608
+ return next;
9609
+ }
9610
+ return messages;
9611
+ }
9378
9612
  function applyClarifyResponse(messages, data) {
9379
9613
  const targetToolId = stringValue2(data.tool_call_id);
9380
9614
  const responses = Array.isArray(data.responses) ? data.responses : void 0;
@@ -11494,7 +11728,6 @@ function MetadataPane({ mission }) {
11494
11728
  }
11495
11729
  init_utils();
11496
11730
  var DEFAULT_POLL_INTERVAL_MS2 = 3e4;
11497
- var DEFAULT_STATUS = "active,paused";
11498
11731
  function fingerprint(items) {
11499
11732
  return items.map(
11500
11733
  (m) => `${m.id}:${m.status}:${m.iteration}:${m.lastEvaluationResult ?? ""}:${m.lastEvaluationAt ?? ""}:${m.pausedReason ?? ""}:${m.updatedAt}`
@@ -11620,7 +11853,7 @@ function MissionsPanel({
11620
11853
  adapter,
11621
11854
  agentId,
11622
11855
  title = "Missions",
11623
- status = DEFAULT_STATUS,
11856
+ status,
11624
11857
  hideHeader = false,
11625
11858
  pollIntervalMs = DEFAULT_POLL_INTERVAL_MS2,
11626
11859
  onMissionSelect,