@paymanai/payman-ask-sdk 4.0.5 → 4.0.7

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.js CHANGED
@@ -157,11 +157,17 @@ var DEFAULT_SLASH_COMMANDS = [
157
157
  requiredAnyPermissions: ["vault:author", "vault:publish"]
158
158
  }
159
159
  ];
160
+ function hasCommandPermission(granted, requiredPermission) {
161
+ if (granted.has("*") || granted.has(requiredPermission)) return true;
162
+ if (!requiredPermission.includes(".")) return false;
163
+ const wildcard = requiredPermission.substring(0, requiredPermission.lastIndexOf(".")) + ".*";
164
+ return granted.has(wildcard);
165
+ }
160
166
  function filterSlashCommands(commands, permissions) {
161
167
  const granted = new Set(permissions ?? []);
162
168
  return commands.filter((command) => {
163
169
  const required = command.requiredAnyPermissions ?? [];
164
- return required.length === 0 || required.some((permission) => granted.has(permission));
170
+ return required.length === 0 || required.some((permission) => hasCommandPermission(granted, permission));
165
171
  });
166
172
  }
167
173
  function parseSlashCommand(content) {
@@ -174,9 +180,19 @@ function parseSlashCommand(content) {
174
180
  body: match[2] ?? ""
175
181
  };
176
182
  }
177
- function slashCommandBodyIsEmpty(content) {
183
+ function getSlashCommandValidationHint(content) {
178
184
  const parsed = parseSlashCommand(content);
179
- return parsed?.command === "/draft-knowledge" && parsed.body.trim().length === 0;
185
+ if (parsed?.command !== "/draft-knowledge") return null;
186
+ const lines = parsed.body.split(/\r?\n/);
187
+ const title = lines[0]?.trim() ?? "";
188
+ const markdownContent = lines.slice(1).join("\n").trim();
189
+ if (!title) {
190
+ return "Add a title after /draft-knowledge, then add markdown content on the next line.";
191
+ }
192
+ if (!markdownContent) {
193
+ return "Add markdown content on a new line below the title.";
194
+ }
195
+ return null;
180
196
  }
181
197
 
182
198
  // src/utils/errorMessages.ts
@@ -231,6 +247,53 @@ function initSentryIfNeeded(dsn) {
231
247
  }
232
248
  });
233
249
  }
250
+
251
+ // src/utils/feedbackClient.ts
252
+ var NEGATIVE_FEEDBACK_REASONS = [
253
+ { value: "NOT_FACTUALLY_CORRECT", label: "Not factually correct" },
254
+ { value: "INCOMPLETE_RESPONSE", label: "Incomplete response" },
255
+ { value: "DID_NOT_FOLLOW_REQUEST", label: "Did not follow my request" },
256
+ { value: "OVERACTIVE_REFUSAL", label: "Refused unnecessarily" },
257
+ { value: "ISSUE_WITH_THOUGHT_PROCESS", label: "Issue with reasoning" },
258
+ { value: "REPORT_CONTENT", label: "Report content" },
259
+ { value: "OTHER", label: "Other" }
260
+ ];
261
+ var DEFAULT_STREAM_ENDPOINT = "/api/playground/ask/stream";
262
+ async function submitFeedback({
263
+ baseUrl,
264
+ streamEndpoint,
265
+ headers,
266
+ authToken,
267
+ stage,
268
+ stageQueryParam,
269
+ executionId,
270
+ feedback,
271
+ details,
272
+ signal
273
+ }) {
274
+ const base = baseUrl.replace(/\/+$/, "");
275
+ const endpointPath = (streamEndpoint || DEFAULT_STREAM_ENDPOINT).split("?")[0].replace(/\/+$/, "");
276
+ const basePath = endpointPath.endsWith("/stream") ? endpointPath.slice(0, -"/stream".length) : endpointPath;
277
+ const query = new URLSearchParams();
278
+ if (stage) query.set(stageQueryParam ?? "stage", stage);
279
+ const qs = query.toString() ? `?${query.toString()}` : "";
280
+ const url = `${base}${basePath}/executions/${encodeURIComponent(executionId)}/feedback${qs}`;
281
+ const requestHeaders = {
282
+ "Content-Type": "application/json",
283
+ Accept: "application/json",
284
+ ...headers ?? {}
285
+ };
286
+ if (authToken) requestHeaders.Authorization = `Bearer ${authToken}`;
287
+ const resp = await fetch(url, {
288
+ method: "POST",
289
+ headers: requestHeaders,
290
+ body: JSON.stringify(details ? { feedback, details } : { feedback }),
291
+ signal
292
+ });
293
+ if (!resp.ok) {
294
+ throw new Error(`Feedback failed: ${resp.status} ${resp.statusText}`);
295
+ }
296
+ }
234
297
  function ImageLightbox({
235
298
  src,
236
299
  alt = "",
@@ -1693,6 +1756,221 @@ function ThinkingBlockV2({
1693
1756
  ) }) })
1694
1757
  ] });
1695
1758
  }
1759
+ var MAX_DETAILS_CHARS = 2e3;
1760
+ function FeedbackReasonModal({
1761
+ open,
1762
+ onClose,
1763
+ onSubmit
1764
+ }) {
1765
+ const [reason, setReason] = react.useState(
1766
+ NEGATIVE_FEEDBACK_REASONS[0].value
1767
+ );
1768
+ const [details, setDetails] = react.useState("");
1769
+ const [submitting, setSubmitting] = react.useState(false);
1770
+ const [error, setError] = react.useState(null);
1771
+ const [reasonOpen, setReasonOpen] = react.useState(false);
1772
+ react.useEffect(() => {
1773
+ if (open) {
1774
+ setReason(NEGATIVE_FEEDBACK_REASONS[0].value);
1775
+ setDetails("");
1776
+ setError(null);
1777
+ setSubmitting(false);
1778
+ setReasonOpen(false);
1779
+ }
1780
+ }, [open]);
1781
+ const handleKeyDown = react.useCallback(
1782
+ (event) => {
1783
+ if (event.key !== "Escape") return;
1784
+ if (reasonOpen) {
1785
+ setReasonOpen(false);
1786
+ return;
1787
+ }
1788
+ onClose();
1789
+ },
1790
+ [onClose, reasonOpen]
1791
+ );
1792
+ react.useEffect(() => {
1793
+ if (!open || typeof document === "undefined") return;
1794
+ document.addEventListener("keydown", handleKeyDown);
1795
+ const previousOverflow = document.body.style.overflow;
1796
+ document.body.style.overflow = "hidden";
1797
+ return () => {
1798
+ document.removeEventListener("keydown", handleKeyDown);
1799
+ document.body.style.overflow = previousOverflow;
1800
+ };
1801
+ }, [handleKeyDown, open]);
1802
+ const handleSubmit = async () => {
1803
+ setSubmitting(true);
1804
+ setError(null);
1805
+ setReasonOpen(false);
1806
+ try {
1807
+ await onSubmit(reason, details.trim() ? details.trim() : void 0);
1808
+ onClose();
1809
+ } catch (e) {
1810
+ setError(e instanceof Error ? e.message : "Could not send feedback");
1811
+ setSubmitting(false);
1812
+ }
1813
+ };
1814
+ const selectedReason = NEGATIVE_FEEDBACK_REASONS.find((item) => item.value === reason) ?? NEGATIVE_FEEDBACK_REASONS[0];
1815
+ return /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: open ? /* @__PURE__ */ jsxRuntime.jsx(
1816
+ framerMotion.motion.div,
1817
+ {
1818
+ className: "payman-v2-feedback-modal-backdrop",
1819
+ initial: { opacity: 0 },
1820
+ animate: { opacity: 1 },
1821
+ exit: { opacity: 0 },
1822
+ transition: { duration: 0.18 },
1823
+ onClick: onClose,
1824
+ role: "presentation",
1825
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1826
+ framerMotion.motion.div,
1827
+ {
1828
+ initial: { opacity: 0, y: 12, scale: 0.98 },
1829
+ animate: { opacity: 1, y: 0, scale: 1 },
1830
+ exit: { opacity: 0, y: 8, scale: 0.98 },
1831
+ transition: { duration: 0.2, ease: [0.16, 1, 0.3, 1] },
1832
+ className: "payman-v2-feedback-modal-dialog",
1833
+ onClick: (event) => event.stopPropagation(),
1834
+ role: "dialog",
1835
+ "aria-modal": "true",
1836
+ "aria-labelledby": "payman-v2-feedback-title",
1837
+ children: [
1838
+ /* @__PURE__ */ jsxRuntime.jsx(
1839
+ "button",
1840
+ {
1841
+ type: "button",
1842
+ onClick: onClose,
1843
+ disabled: submitting,
1844
+ className: "payman-v2-feedback-modal-close",
1845
+ "aria-label": "Close feedback modal",
1846
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 18, strokeWidth: 2 })
1847
+ }
1848
+ ),
1849
+ /* @__PURE__ */ jsxRuntime.jsx(
1850
+ "h2",
1851
+ {
1852
+ id: "payman-v2-feedback-title",
1853
+ className: "payman-v2-feedback-modal-title",
1854
+ children: "Tell us what went wrong"
1855
+ }
1856
+ ),
1857
+ /* @__PURE__ */ jsxRuntime.jsx(
1858
+ "label",
1859
+ {
1860
+ className: "payman-v2-feedback-modal-label",
1861
+ htmlFor: "payman-v2-feedback-reason",
1862
+ children: "What was the issue?"
1863
+ }
1864
+ ),
1865
+ /* @__PURE__ */ jsxRuntime.jsxs(
1866
+ "div",
1867
+ {
1868
+ id: "payman-v2-feedback-reason",
1869
+ className: "payman-v2-feedback-modal-reason",
1870
+ onBlur: (event) => {
1871
+ const nextTarget = event.relatedTarget;
1872
+ if (!nextTarget || !event.currentTarget.contains(nextTarget)) {
1873
+ setReasonOpen(false);
1874
+ }
1875
+ },
1876
+ children: [
1877
+ /* @__PURE__ */ jsxRuntime.jsxs(
1878
+ "button",
1879
+ {
1880
+ type: "button",
1881
+ className: "payman-v2-feedback-modal-reason-trigger",
1882
+ "aria-haspopup": "listbox",
1883
+ "aria-expanded": reasonOpen,
1884
+ "aria-controls": "payman-v2-feedback-reason-options",
1885
+ disabled: submitting,
1886
+ onClick: () => setReasonOpen((current) => !current),
1887
+ children: [
1888
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: selectedReason.label }),
1889
+ /* @__PURE__ */ jsxRuntime.jsx(
1890
+ lucideReact.ChevronDown,
1891
+ {
1892
+ size: 17,
1893
+ strokeWidth: 2,
1894
+ className: reasonOpen ? "payman-v2-feedback-modal-reason-chevron payman-v2-feedback-modal-reason-chevron-open" : "payman-v2-feedback-modal-reason-chevron"
1895
+ }
1896
+ )
1897
+ ]
1898
+ }
1899
+ ),
1900
+ reasonOpen ? /* @__PURE__ */ jsxRuntime.jsx(
1901
+ "div",
1902
+ {
1903
+ id: "payman-v2-feedback-reason-options",
1904
+ className: "payman-v2-feedback-modal-reason-menu",
1905
+ role: "listbox",
1906
+ "aria-label": "Feedback issue",
1907
+ tabIndex: -1,
1908
+ children: NEGATIVE_FEEDBACK_REASONS.map((item) => {
1909
+ const isSelected = item.value === reason;
1910
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1911
+ "button",
1912
+ {
1913
+ type: "button",
1914
+ className: isSelected ? "payman-v2-feedback-modal-reason-option payman-v2-feedback-modal-reason-option-selected" : "payman-v2-feedback-modal-reason-option",
1915
+ role: "option",
1916
+ "aria-selected": isSelected,
1917
+ onClick: () => {
1918
+ setReason(item.value);
1919
+ setReasonOpen(false);
1920
+ },
1921
+ children: [
1922
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: item.label }),
1923
+ isSelected ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 16, strokeWidth: 2 }) : null
1924
+ ]
1925
+ },
1926
+ item.value
1927
+ );
1928
+ })
1929
+ }
1930
+ ) : null
1931
+ ]
1932
+ }
1933
+ ),
1934
+ /* @__PURE__ */ jsxRuntime.jsx(
1935
+ "textarea",
1936
+ {
1937
+ className: "payman-v2-feedback-modal-textarea",
1938
+ placeholder: "Add details (optional)",
1939
+ value: details,
1940
+ maxLength: MAX_DETAILS_CHARS,
1941
+ onChange: (e) => setDetails(e.target.value),
1942
+ disabled: submitting
1943
+ }
1944
+ ),
1945
+ error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-feedback-modal-error", children: error }) : null,
1946
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-feedback-modal-actions", children: [
1947
+ /* @__PURE__ */ jsxRuntime.jsx(
1948
+ "button",
1949
+ {
1950
+ type: "button",
1951
+ onClick: onClose,
1952
+ disabled: submitting,
1953
+ className: "payman-v2-feedback-modal-btn payman-v2-feedback-modal-btn-secondary",
1954
+ children: "Cancel"
1955
+ }
1956
+ ),
1957
+ /* @__PURE__ */ jsxRuntime.jsx(
1958
+ "button",
1959
+ {
1960
+ type: "button",
1961
+ onClick: handleSubmit,
1962
+ disabled: submitting,
1963
+ className: "payman-v2-feedback-modal-btn payman-v2-feedback-modal-btn-primary",
1964
+ children: submitting ? "Sending\u2026" : "Submit"
1965
+ }
1966
+ )
1967
+ ] })
1968
+ ]
1969
+ }
1970
+ )
1971
+ }
1972
+ ) : null });
1973
+ }
1696
1974
  var RESPONSE_SPEED = {
1697
1975
  normal: [2, 4],
1698
1976
  fast: 1,
@@ -1775,12 +2053,26 @@ function AssistantMessageV2({
1775
2053
  message,
1776
2054
  onImageClick,
1777
2055
  onExecutionTraceClick,
1778
- onFeedback,
2056
+ onSubmitFeedback,
1779
2057
  actions,
1780
2058
  typingSpeed = 4
1781
2059
  }) {
1782
2060
  const [copied, setCopied] = react.useState(false);
1783
2061
  const [activeFeedback, setActiveFeedback] = react.useState(null);
2062
+ const [reasonModalOpen, setReasonModalOpen] = react.useState(false);
2063
+ const canSubmitFeedback = !!onSubmitFeedback && !!message.executionId;
2064
+ const handlePositiveFeedback = () => {
2065
+ if (!canSubmitFeedback || activeFeedback === "up") return;
2066
+ const previous = activeFeedback;
2067
+ setActiveFeedback("up");
2068
+ Promise.resolve(
2069
+ onSubmitFeedback?.({
2070
+ messageId: message.id,
2071
+ executionId: message.executionId,
2072
+ feedback: "POSITIVE"
2073
+ })
2074
+ ).catch(() => setActiveFeedback(previous));
2075
+ };
1784
2076
  const [toast, setToast] = react.useState(null);
1785
2077
  const copyResetTimerRef = react.useRef(null);
1786
2078
  const toastTimerRef = react.useRef(null);
@@ -1810,34 +2102,6 @@ function AssistantMessageV2({
1810
2102
  void 0,
1811
2103
  typingSpeed
1812
2104
  );
1813
- const elapsedMs = (() => {
1814
- const fromServer = message.totalElapsedMs;
1815
- if (typeof fromServer === "number" && Number.isFinite(fromServer) && fromServer > 0) {
1816
- return fromServer;
1817
- }
1818
- const steps = message.steps;
1819
- if (!steps || steps.length === 0) return void 0;
1820
- let earliest = Number.POSITIVE_INFINITY;
1821
- let latest = Number.NEGATIVE_INFINITY;
1822
- for (const s of steps) {
1823
- if (!s.timestamp) continue;
1824
- if (s.timestamp < earliest) earliest = s.timestamp;
1825
- const end = s.timestamp + (s.elapsedMs ?? 0);
1826
- if (end > latest) latest = end;
1827
- }
1828
- if (!Number.isFinite(earliest) || !Number.isFinite(latest)) {
1829
- const total = steps.reduce((sum, s) => sum + (s.elapsedMs || 0), 0);
1830
- return total > 0 ? total : void 0;
1831
- }
1832
- const diff = latest - earliest;
1833
- return diff > 0 ? diff : void 0;
1834
- })();
1835
- function formatElapsed(ms) {
1836
- if (ms === void 0) return void 0;
1837
- if (ms < 1e3) return `${Math.round(ms)}ms`;
1838
- return `${(ms / 1e3).toFixed(1)}s`;
1839
- }
1840
- const totalElapsedLabel = formatElapsed(elapsedMs);
1841
2105
  const stickyLabel = (() => {
1842
2106
  const steps = message.steps;
1843
2107
  if (!steps || steps.length === 0) return void 0;
@@ -2020,14 +2284,10 @@ function AssistantMessageV2({
2020
2284
  children: copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { style: { width: 16, height: 16 } }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { style: { width: 16, height: 16 } })
2021
2285
  }
2022
2286
  ) }),
2023
- showThumbsUp && /* @__PURE__ */ jsxRuntime.jsx(ActionTooltipV2, { label: "Good response", children: /* @__PURE__ */ jsxRuntime.jsx(
2287
+ showThumbsUp && canSubmitFeedback && /* @__PURE__ */ jsxRuntime.jsx(ActionTooltipV2, { label: "Good response", children: /* @__PURE__ */ jsxRuntime.jsx(
2024
2288
  "button",
2025
2289
  {
2026
- onClick: () => {
2027
- const next = activeFeedback === "up" ? null : "up";
2028
- setActiveFeedback(next);
2029
- if (next) onFeedback?.({ messageId: message.id, feedback: "up" });
2030
- },
2290
+ onClick: handlePositiveFeedback,
2031
2291
  className: cn(
2032
2292
  "payman-v2-assistant-msg-action-btn",
2033
2293
  activeFeedback === "up" && "payman-v2-assistant-msg-action-btn-active"
@@ -2036,14 +2296,10 @@ function AssistantMessageV2({
2036
2296
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ThumbsUp, { style: { width: 15, height: 15 } })
2037
2297
  }
2038
2298
  ) }),
2039
- showThumbsDown && /* @__PURE__ */ jsxRuntime.jsx(ActionTooltipV2, { label: "Bad response", children: /* @__PURE__ */ jsxRuntime.jsx(
2299
+ showThumbsDown && canSubmitFeedback && /* @__PURE__ */ jsxRuntime.jsx(ActionTooltipV2, { label: "Bad response", children: /* @__PURE__ */ jsxRuntime.jsx(
2040
2300
  "button",
2041
2301
  {
2042
- onClick: () => {
2043
- const next = activeFeedback === "down" ? null : "down";
2044
- setActiveFeedback(next);
2045
- if (next) onFeedback?.({ messageId: message.id, feedback: "down" });
2046
- },
2302
+ onClick: () => setReasonModalOpen(true),
2047
2303
  className: cn(
2048
2304
  "payman-v2-assistant-msg-action-btn",
2049
2305
  activeFeedback === "down" && "payman-v2-assistant-msg-action-btn-active"
@@ -2060,10 +2316,25 @@ function AssistantMessageV2({
2060
2316
  "aria-label": "Trace response",
2061
2317
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Binoculars, { style: { width: 16, height: 16 } })
2062
2318
  }
2063
- ) }),
2064
- totalElapsedLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-assistant-msg-elapsed", children: totalElapsedLabel })
2319
+ ) })
2065
2320
  ] })
2066
- ] })
2321
+ ] }),
2322
+ /* @__PURE__ */ jsxRuntime.jsx(
2323
+ FeedbackReasonModal,
2324
+ {
2325
+ open: reasonModalOpen,
2326
+ onClose: () => setReasonModalOpen(false),
2327
+ onSubmit: async (reason, details) => {
2328
+ await onSubmitFeedback?.({
2329
+ messageId: message.id,
2330
+ executionId: message.executionId,
2331
+ feedback: reason,
2332
+ details
2333
+ });
2334
+ setActiveFeedback("down");
2335
+ }
2336
+ }
2337
+ )
2067
2338
  ] });
2068
2339
  }
2069
2340
  var DEFAULT_MAX_LENGTH = 6;
@@ -2344,7 +2615,7 @@ var MessageListV2 = react.forwardRef(
2344
2615
  onApproveAction,
2345
2616
  onRejectAction,
2346
2617
  onResendAction,
2347
- onMessageFeedback,
2618
+ onSubmitFeedback,
2348
2619
  typingSpeed = 4
2349
2620
  }, ref) {
2350
2621
  const scrollRef = react.useRef(null);
@@ -2484,7 +2755,7 @@ var MessageListV2 = react.forwardRef(
2484
2755
  message,
2485
2756
  onImageClick,
2486
2757
  onExecutionTraceClick,
2487
- onFeedback: onMessageFeedback,
2758
+ onSubmitFeedback,
2488
2759
  actions: messageActions?.assistantMessageActions,
2489
2760
  typingSpeed
2490
2761
  }
@@ -2648,8 +2919,9 @@ var ChatInputV2 = react.forwardRef(
2648
2919
  }, [isRecording, transcribedText]);
2649
2920
  const handleSend = react.useCallback(() => {
2650
2921
  if (!value.trim() || disabled) return;
2651
- if (slashCommandBodyIsEmpty(value)) {
2652
- setInlineHint("Add markdown content below the command line.");
2922
+ const commandHint = getSlashCommandValidationHint(value);
2923
+ if (commandHint) {
2924
+ setInlineHint(commandHint);
2653
2925
  return;
2654
2926
  }
2655
2927
  voiceDraftSyncActiveRef.current = false;
@@ -3251,6 +3523,316 @@ function ImageLightboxV2({ src, alt, onClose }) {
3251
3523
  document.body
3252
3524
  );
3253
3525
  }
3526
+ var PRE_PIPELINE_STEPS = /* @__PURE__ */ new Set([
3527
+ "record_execution",
3528
+ "resolve_provider",
3529
+ "resolve_mcp_servers",
3530
+ "build_pipeline",
3531
+ "load_skill_index"
3532
+ ]);
3533
+ function stepColor(step) {
3534
+ if (step.status === "failed") return "#ef4444";
3535
+ if (PRE_PIPELINE_STEPS.has(step.step)) return "#f59e0b";
3536
+ return "#3b82f6";
3537
+ }
3538
+ function formatMs(ms) {
3539
+ if (ms == null) return "\u2014";
3540
+ if (ms < 1e3) return `${ms} ms`;
3541
+ return `${(ms / 1e3).toFixed(2)} s`;
3542
+ }
3543
+ function TraceTimelineModal({
3544
+ open,
3545
+ onClose,
3546
+ executionId,
3547
+ apiBaseUrl,
3548
+ apiHeaders
3549
+ }) {
3550
+ const [trace, setTrace] = react.useState(null);
3551
+ const [loading, setLoading] = react.useState(false);
3552
+ const [error, setError] = react.useState(null);
3553
+ react.useEffect(() => {
3554
+ if (!open || !executionId) return;
3555
+ const ctrl = new AbortController();
3556
+ setLoading(true);
3557
+ setError(null);
3558
+ setTrace(null);
3559
+ const base = apiBaseUrl.replace(/\/+$/, "");
3560
+ fetch(`${base}/api/ask/executions/${encodeURIComponent(executionId)}/trace`, {
3561
+ method: "GET",
3562
+ headers: { Accept: "application/json", ...apiHeaders },
3563
+ signal: ctrl.signal
3564
+ }).then(async (resp) => {
3565
+ if (!resp.ok) {
3566
+ throw new Error(`Trace fetch failed: ${resp.status} ${resp.statusText}`);
3567
+ }
3568
+ return resp.json();
3569
+ }).then((data) => setTrace(data)).catch((e) => {
3570
+ if (e.name === "AbortError") return;
3571
+ setError(e instanceof Error ? e.message : "Trace fetch failed");
3572
+ }).finally(() => setLoading(false));
3573
+ return () => ctrl.abort();
3574
+ }, [open, executionId, apiBaseUrl, apiHeaders]);
3575
+ const totalMs = react.useMemo(() => {
3576
+ if (!trace) return 0;
3577
+ const meta = trace.runMetadata?.totalTimeMs;
3578
+ if (typeof meta === "number" && meta > 0) return meta;
3579
+ const starts = trace.pipelineSteps.map((s) => s.startTime);
3580
+ const ends = trace.pipelineSteps.map((s) => s.endTime ?? s.startTime).filter((t) => typeof t === "number");
3581
+ if (starts.length === 0 || ends.length === 0) return 0;
3582
+ return Math.max(...ends) - Math.min(...starts);
3583
+ }, [trace]);
3584
+ const accountedMs = react.useMemo(
3585
+ () => (trace?.pipelineSteps ?? []).reduce(
3586
+ (sum, s) => sum + (s.durationMs ?? 0),
3587
+ 0
3588
+ ),
3589
+ [trace]
3590
+ );
3591
+ const unaccountedMs = Math.max(totalMs - accountedMs, 0);
3592
+ if (typeof document === "undefined") return null;
3593
+ return reactDom.createPortal(
3594
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: open && /* @__PURE__ */ jsxRuntime.jsx(
3595
+ framerMotion.motion.div,
3596
+ {
3597
+ initial: { opacity: 0 },
3598
+ animate: { opacity: 1 },
3599
+ exit: { opacity: 0 },
3600
+ transition: { duration: 0.15 },
3601
+ onClick: onClose,
3602
+ style: {
3603
+ position: "fixed",
3604
+ inset: 0,
3605
+ background: "rgba(0,0,0,0.45)",
3606
+ zIndex: 9999,
3607
+ display: "flex",
3608
+ alignItems: "center",
3609
+ justifyContent: "center",
3610
+ padding: 24
3611
+ },
3612
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
3613
+ framerMotion.motion.div,
3614
+ {
3615
+ initial: { y: 12, opacity: 0 },
3616
+ animate: { y: 0, opacity: 1 },
3617
+ exit: { y: 12, opacity: 0 },
3618
+ transition: { duration: 0.18 },
3619
+ onClick: (e) => e.stopPropagation(),
3620
+ style: {
3621
+ background: "#0b1220",
3622
+ color: "#e5e7eb",
3623
+ borderRadius: 12,
3624
+ width: "100%",
3625
+ maxWidth: 760,
3626
+ maxHeight: "85vh",
3627
+ overflow: "auto",
3628
+ boxShadow: "0 20px 60px rgba(0,0,0,0.45)",
3629
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
3630
+ },
3631
+ children: [
3632
+ /* @__PURE__ */ jsxRuntime.jsxs(
3633
+ "header",
3634
+ {
3635
+ style: {
3636
+ display: "flex",
3637
+ alignItems: "center",
3638
+ justifyContent: "space-between",
3639
+ padding: "14px 18px",
3640
+ borderBottom: "1px solid rgba(255,255,255,0.08)"
3641
+ },
3642
+ children: [
3643
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3644
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 14, fontWeight: 600 }, children: "Execution trace" }),
3645
+ /* @__PURE__ */ jsxRuntime.jsx(
3646
+ "div",
3647
+ {
3648
+ style: {
3649
+ fontSize: 11,
3650
+ opacity: 0.6,
3651
+ marginTop: 2,
3652
+ fontFamily: "ui-monospace, SFMono-Regular, monospace"
3653
+ },
3654
+ children: executionId ?? ""
3655
+ }
3656
+ )
3657
+ ] }),
3658
+ /* @__PURE__ */ jsxRuntime.jsx(
3659
+ "button",
3660
+ {
3661
+ onClick: onClose,
3662
+ "aria-label": "Close",
3663
+ style: {
3664
+ background: "transparent",
3665
+ border: 0,
3666
+ color: "#e5e7eb",
3667
+ cursor: "pointer",
3668
+ padding: 6,
3669
+ borderRadius: 6
3670
+ },
3671
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { style: { width: 18, height: 18 } })
3672
+ }
3673
+ )
3674
+ ]
3675
+ }
3676
+ ),
3677
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 18 }, children: [
3678
+ loading && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { opacity: 0.7, fontSize: 13 }, children: "Loading trace\u2026" }),
3679
+ error && /* @__PURE__ */ jsxRuntime.jsx(
3680
+ "div",
3681
+ {
3682
+ style: {
3683
+ color: "#fca5a5",
3684
+ fontSize: 13,
3685
+ whiteSpace: "pre-wrap"
3686
+ },
3687
+ children: error
3688
+ }
3689
+ ),
3690
+ trace && !loading && !error && /* @__PURE__ */ jsxRuntime.jsx(
3691
+ TimelineBars,
3692
+ {
3693
+ trace,
3694
+ totalMs,
3695
+ unaccountedMs
3696
+ }
3697
+ )
3698
+ ] })
3699
+ ]
3700
+ }
3701
+ )
3702
+ }
3703
+ ) }),
3704
+ document.body
3705
+ );
3706
+ }
3707
+ function TimelineBars({
3708
+ trace,
3709
+ totalMs,
3710
+ unaccountedMs
3711
+ }) {
3712
+ const bars = trace.pipelineSteps.map((step) => {
3713
+ const duration = step.durationMs ?? 0;
3714
+ const widthPct = totalMs > 0 ? duration / totalMs * 100 : 0;
3715
+ const llmMs = step.llmCall?.durationMs ?? null;
3716
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 8 }, children: [
3717
+ /* @__PURE__ */ jsxRuntime.jsxs(
3718
+ "div",
3719
+ {
3720
+ style: {
3721
+ display: "flex",
3722
+ justifyContent: "space-between",
3723
+ fontSize: 12,
3724
+ marginBottom: 4,
3725
+ fontFamily: "ui-monospace, SFMono-Regular, monospace"
3726
+ },
3727
+ children: [
3728
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3729
+ step.step,
3730
+ step.status === "failed" && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#fca5a5", marginLeft: 6 }, children: "(failed)" })
3731
+ ] }),
3732
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { opacity: 0.75 }, children: [
3733
+ formatMs(duration),
3734
+ llmMs != null && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { marginLeft: 8, opacity: 0.6 }, children: [
3735
+ "(LLM ",
3736
+ formatMs(llmMs),
3737
+ ")"
3738
+ ] })
3739
+ ] })
3740
+ ]
3741
+ }
3742
+ ),
3743
+ /* @__PURE__ */ jsxRuntime.jsx(
3744
+ "div",
3745
+ {
3746
+ style: {
3747
+ height: 8,
3748
+ background: "rgba(255,255,255,0.06)",
3749
+ borderRadius: 4,
3750
+ overflow: "hidden"
3751
+ },
3752
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3753
+ "div",
3754
+ {
3755
+ style: {
3756
+ height: "100%",
3757
+ width: `${Math.max(widthPct, 0.3)}%`,
3758
+ background: stepColor(step),
3759
+ transition: "width 0.2s ease"
3760
+ }
3761
+ }
3762
+ )
3763
+ }
3764
+ )
3765
+ ] }, `${step.step}-${step.startTime}`);
3766
+ });
3767
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3768
+ bars,
3769
+ unaccountedMs > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 8 }, children: [
3770
+ /* @__PURE__ */ jsxRuntime.jsxs(
3771
+ "div",
3772
+ {
3773
+ style: {
3774
+ display: "flex",
3775
+ justifyContent: "space-between",
3776
+ fontSize: 12,
3777
+ marginBottom: 4,
3778
+ fontFamily: "ui-monospace, SFMono-Regular, monospace",
3779
+ opacity: 0.7
3780
+ },
3781
+ children: [
3782
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "(unaccounted)" }),
3783
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatMs(unaccountedMs) })
3784
+ ]
3785
+ }
3786
+ ),
3787
+ /* @__PURE__ */ jsxRuntime.jsx(
3788
+ "div",
3789
+ {
3790
+ style: {
3791
+ height: 8,
3792
+ background: "rgba(255,255,255,0.06)",
3793
+ borderRadius: 4,
3794
+ overflow: "hidden"
3795
+ },
3796
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3797
+ "div",
3798
+ {
3799
+ style: {
3800
+ height: "100%",
3801
+ width: `${unaccountedMs / Math.max(totalMs, 1) * 100}%`,
3802
+ background: "repeating-linear-gradient(45deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 8px)"
3803
+ }
3804
+ }
3805
+ )
3806
+ }
3807
+ )
3808
+ ] }),
3809
+ /* @__PURE__ */ jsxRuntime.jsxs(
3810
+ "div",
3811
+ {
3812
+ style: {
3813
+ marginTop: 14,
3814
+ paddingTop: 12,
3815
+ borderTop: "1px solid rgba(255,255,255,0.08)",
3816
+ fontSize: 12,
3817
+ display: "flex",
3818
+ justifyContent: "space-between",
3819
+ opacity: 0.85
3820
+ },
3821
+ children: [
3822
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3823
+ trace.pipelineSteps.length,
3824
+ " step",
3825
+ trace.pipelineSteps.length === 1 ? "" : "s"
3826
+ ] }),
3827
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontFamily: "ui-monospace, SFMono-Regular, monospace" }, children: [
3828
+ "total: ",
3829
+ formatMs(totalMs)
3830
+ ] })
3831
+ ]
3832
+ }
3833
+ )
3834
+ ] });
3835
+ }
3254
3836
  var DEFAULT_USER_ACTION_STATE = {
3255
3837
  request: null,
3256
3838
  result: null};
@@ -3441,12 +4023,51 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
3441
4023
  ]
3442
4024
  );
3443
4025
  const {
3444
- onExecutionTraceClick,
4026
+ onExecutionTraceClick: rawOnExecutionTraceClick,
3445
4027
  onResetSession,
3446
4028
  onUploadImageClick,
3447
4029
  onAttachFileClick,
3448
4030
  onMessageFeedback
3449
4031
  } = callbacks;
4032
+ const [debugTraceExecutionId, setDebugTraceExecutionId] = react.useState(null);
4033
+ const handleSubmitFeedback = react.useCallback(
4034
+ async ({ messageId, executionId, feedback, details }) => {
4035
+ if (!executionId) {
4036
+ throw new Error("Cannot submit feedback before the response completes");
4037
+ }
4038
+ await submitFeedback({
4039
+ baseUrl: config.api.baseUrl,
4040
+ streamEndpoint: config.api.streamEndpoint,
4041
+ headers: config.api.headers,
4042
+ authToken: config.api.authToken,
4043
+ stage: config.stage,
4044
+ stageQueryParam: config.stageQueryParam,
4045
+ executionId,
4046
+ feedback,
4047
+ details
4048
+ });
4049
+ onMessageFeedback?.({
4050
+ messageId,
4051
+ feedback: feedback === "POSITIVE" ? "up" : "down"
4052
+ });
4053
+ },
4054
+ [
4055
+ config.api.baseUrl,
4056
+ config.api.streamEndpoint,
4057
+ config.api.headers,
4058
+ config.api.authToken,
4059
+ config.stage,
4060
+ config.stageQueryParam,
4061
+ onMessageFeedback
4062
+ ]
4063
+ );
4064
+ const onExecutionTraceClick = react.useMemo(() => {
4065
+ if (!config.debug) return rawOnExecutionTraceClick;
4066
+ return (data) => {
4067
+ rawOnExecutionTraceClick?.(data);
4068
+ if (data.executionId) setDebugTraceExecutionId(data.executionId);
4069
+ };
4070
+ }, [config.debug, rawOnExecutionTraceClick]);
3450
4071
  const performResetSession = react.useCallback(() => {
3451
4072
  resetToEmptyStateRef.current = true;
3452
4073
  setEditingMessageId(null);
@@ -3504,6 +4125,7 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
3504
4125
  showEmptyStateIcon = true,
3505
4126
  emptyStateComponent,
3506
4127
  showResetSession = false,
4128
+ enableDeepModeToggle = true,
3507
4129
  showAttachmentButton = true,
3508
4130
  showUploadImageButton = true,
3509
4131
  showAttachFileButton = true,
@@ -3559,6 +4181,7 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
3559
4181
  stopRecording();
3560
4182
  };
3561
4183
  const isV2InputDisabled = !isSessionParamsConfigured || disableInput;
4184
+ const effectiveAnalysisMode = enableDeepModeToggle ? analysisMode : "fast";
3562
4185
  const isEmpty = messages.length === 0;
3563
4186
  if (isChatDisabled) {
3564
4187
  if (disabledComponent) {
@@ -3617,7 +4240,7 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
3617
4240
  if (isRecording) stopRecording();
3618
4241
  if (text.trim() && !disableInput && isSessionParamsConfigured) {
3619
4242
  setEditingMessageId(null);
3620
- void sendMessage(text.trim(), { analysisMode });
4243
+ void sendMessage(text.trim(), { analysisMode: effectiveAnalysisMode });
3621
4244
  }
3622
4245
  };
3623
4246
  const handleVoicePress = react.useCallback(async () => {
@@ -3644,7 +4267,9 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
3644
4267
  (message) => message.id === messageId && message.role === "user"
3645
4268
  );
3646
4269
  if (!targetMessage?.content.trim()) return;
3647
- void sendMessage(targetMessage.content.trim(), { analysisMode });
4270
+ void sendMessage(targetMessage.content.trim(), {
4271
+ analysisMode: effectiveAnalysisMode
4272
+ });
3648
4273
  const bump = () => messageListV2Ref.current?.scrollToBottom("instant");
3649
4274
  requestAnimationFrame(() => {
3650
4275
  bump();
@@ -3652,7 +4277,7 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
3652
4277
  window.setTimeout(bump, 120);
3653
4278
  });
3654
4279
  });
3655
- }, [isWaitingForResponse, messages, sendMessage, analysisMode]);
4280
+ }, [isWaitingForResponse, messages, sendMessage, effectiveAnalysisMode]);
3656
4281
  const handleClearEditing = react.useCallback(() => {
3657
4282
  setEditingMessageId(null);
3658
4283
  }, []);
@@ -3731,8 +4356,8 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
3731
4356
  onAttachFileClick,
3732
4357
  editingMessageId,
3733
4358
  onClearEditing: handleClearEditing,
3734
- analysisMode,
3735
- onAnalysisModeChange: setAnalysisMode,
4359
+ analysisMode: enableDeepModeToggle ? analysisMode : void 0,
4360
+ onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
3736
4361
  slashCommands
3737
4362
  }
3738
4363
  )
@@ -3772,7 +4397,7 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
3772
4397
  onResendAction: isUserActionSupported ? async () => {
3773
4398
  await resendOtp();
3774
4399
  } : void 0,
3775
- onMessageFeedback
4400
+ onSubmitFeedback: handleSubmitFeedback
3776
4401
  }
3777
4402
  ),
3778
4403
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -3807,8 +4432,8 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
3807
4432
  onAttachFileClick,
3808
4433
  editingMessageId,
3809
4434
  onClearEditing: handleClearEditing,
3810
- analysisMode,
3811
- onAnalysisModeChange: setAnalysisMode,
4435
+ analysisMode: enableDeepModeToggle ? analysisMode : void 0,
4436
+ onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
3812
4437
  slashCommands
3813
4438
  }
3814
4439
  )
@@ -3831,6 +4456,16 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
3831
4456
  onClose: closeResetSessionConfirm,
3832
4457
  onConfirm: performResetSession
3833
4458
  }
4459
+ ),
4460
+ config.debug && /* @__PURE__ */ jsxRuntime.jsx(
4461
+ TraceTimelineModal,
4462
+ {
4463
+ open: debugTraceExecutionId !== null,
4464
+ onClose: () => setDebugTraceExecutionId(null),
4465
+ executionId: debugTraceExecutionId,
4466
+ apiBaseUrl: config.api.baseUrl,
4467
+ apiHeaders: config.api.headers
4468
+ }
3834
4469
  )
3835
4470
  ]
3836
4471
  }