@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.mjs CHANGED
@@ -5,7 +5,7 @@ import { createContext, forwardRef, useRef, useState, useCallback, useImperative
5
5
  import { clsx } from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
7
  import * as Sentry from '@sentry/react';
8
- import { ArrowDown, Pencil, X, RotateCcw, Telescope, Zap, Plus, ImagePlus, Paperclip, Mic, ArrowUp, Check, AlertCircle, Copy, WifiOff, ThumbsUp, ThumbsDown, Binoculars, ShieldCheck, Download, Loader2, User, Clock, Sparkles, ImageOff, Eye, ChevronDown, ChevronRight } from 'lucide-react';
8
+ import { ArrowDown, Pencil, X, RotateCcw, Telescope, Zap, Plus, ImagePlus, Paperclip, Mic, ArrowUp, Check, AlertCircle, Copy, WifiOff, ThumbsUp, ThumbsDown, Binoculars, ShieldCheck, Download, Loader2, ChevronDown, User, Clock, Sparkles, ImageOff, Eye, ChevronRight } from 'lucide-react';
9
9
  import ReactMarkdown from 'react-markdown';
10
10
  import remarkGfm from 'remark-gfm';
11
11
  import { createPortal } from 'react-dom';
@@ -131,11 +131,17 @@ var DEFAULT_SLASH_COMMANDS = [
131
131
  requiredAnyPermissions: ["vault:author", "vault:publish"]
132
132
  }
133
133
  ];
134
+ function hasCommandPermission(granted, requiredPermission) {
135
+ if (granted.has("*") || granted.has(requiredPermission)) return true;
136
+ if (!requiredPermission.includes(".")) return false;
137
+ const wildcard = requiredPermission.substring(0, requiredPermission.lastIndexOf(".")) + ".*";
138
+ return granted.has(wildcard);
139
+ }
134
140
  function filterSlashCommands(commands, permissions) {
135
141
  const granted = new Set(permissions ?? []);
136
142
  return commands.filter((command) => {
137
143
  const required = command.requiredAnyPermissions ?? [];
138
- return required.length === 0 || required.some((permission) => granted.has(permission));
144
+ return required.length === 0 || required.some((permission) => hasCommandPermission(granted, permission));
139
145
  });
140
146
  }
141
147
  function parseSlashCommand(content) {
@@ -148,9 +154,19 @@ function parseSlashCommand(content) {
148
154
  body: match[2] ?? ""
149
155
  };
150
156
  }
151
- function slashCommandBodyIsEmpty(content) {
157
+ function getSlashCommandValidationHint(content) {
152
158
  const parsed = parseSlashCommand(content);
153
- return parsed?.command === "/draft-knowledge" && parsed.body.trim().length === 0;
159
+ if (parsed?.command !== "/draft-knowledge") return null;
160
+ const lines = parsed.body.split(/\r?\n/);
161
+ const title = lines[0]?.trim() ?? "";
162
+ const markdownContent = lines.slice(1).join("\n").trim();
163
+ if (!title) {
164
+ return "Add a title after /draft-knowledge, then add markdown content on the next line.";
165
+ }
166
+ if (!markdownContent) {
167
+ return "Add markdown content on a new line below the title.";
168
+ }
169
+ return null;
154
170
  }
155
171
 
156
172
  // src/utils/errorMessages.ts
@@ -205,6 +221,53 @@ function initSentryIfNeeded(dsn) {
205
221
  }
206
222
  });
207
223
  }
224
+
225
+ // src/utils/feedbackClient.ts
226
+ var NEGATIVE_FEEDBACK_REASONS = [
227
+ { value: "NOT_FACTUALLY_CORRECT", label: "Not factually correct" },
228
+ { value: "INCOMPLETE_RESPONSE", label: "Incomplete response" },
229
+ { value: "DID_NOT_FOLLOW_REQUEST", label: "Did not follow my request" },
230
+ { value: "OVERACTIVE_REFUSAL", label: "Refused unnecessarily" },
231
+ { value: "ISSUE_WITH_THOUGHT_PROCESS", label: "Issue with reasoning" },
232
+ { value: "REPORT_CONTENT", label: "Report content" },
233
+ { value: "OTHER", label: "Other" }
234
+ ];
235
+ var DEFAULT_STREAM_ENDPOINT = "/api/playground/ask/stream";
236
+ async function submitFeedback({
237
+ baseUrl,
238
+ streamEndpoint,
239
+ headers,
240
+ authToken,
241
+ stage,
242
+ stageQueryParam,
243
+ executionId,
244
+ feedback,
245
+ details,
246
+ signal
247
+ }) {
248
+ const base = baseUrl.replace(/\/+$/, "");
249
+ const endpointPath = (streamEndpoint || DEFAULT_STREAM_ENDPOINT).split("?")[0].replace(/\/+$/, "");
250
+ const basePath = endpointPath.endsWith("/stream") ? endpointPath.slice(0, -"/stream".length) : endpointPath;
251
+ const query = new URLSearchParams();
252
+ if (stage) query.set(stageQueryParam ?? "stage", stage);
253
+ const qs = query.toString() ? `?${query.toString()}` : "";
254
+ const url = `${base}${basePath}/executions/${encodeURIComponent(executionId)}/feedback${qs}`;
255
+ const requestHeaders = {
256
+ "Content-Type": "application/json",
257
+ Accept: "application/json",
258
+ ...headers ?? {}
259
+ };
260
+ if (authToken) requestHeaders.Authorization = `Bearer ${authToken}`;
261
+ const resp = await fetch(url, {
262
+ method: "POST",
263
+ headers: requestHeaders,
264
+ body: JSON.stringify(details ? { feedback, details } : { feedback }),
265
+ signal
266
+ });
267
+ if (!resp.ok) {
268
+ throw new Error(`Feedback failed: ${resp.status} ${resp.statusText}`);
269
+ }
270
+ }
208
271
  function ImageLightbox({
209
272
  src,
210
273
  alt = "",
@@ -1667,6 +1730,221 @@ function ThinkingBlockV2({
1667
1730
  ) }) })
1668
1731
  ] });
1669
1732
  }
1733
+ var MAX_DETAILS_CHARS = 2e3;
1734
+ function FeedbackReasonModal({
1735
+ open,
1736
+ onClose,
1737
+ onSubmit
1738
+ }) {
1739
+ const [reason, setReason] = useState(
1740
+ NEGATIVE_FEEDBACK_REASONS[0].value
1741
+ );
1742
+ const [details, setDetails] = useState("");
1743
+ const [submitting, setSubmitting] = useState(false);
1744
+ const [error, setError] = useState(null);
1745
+ const [reasonOpen, setReasonOpen] = useState(false);
1746
+ useEffect(() => {
1747
+ if (open) {
1748
+ setReason(NEGATIVE_FEEDBACK_REASONS[0].value);
1749
+ setDetails("");
1750
+ setError(null);
1751
+ setSubmitting(false);
1752
+ setReasonOpen(false);
1753
+ }
1754
+ }, [open]);
1755
+ const handleKeyDown = useCallback(
1756
+ (event) => {
1757
+ if (event.key !== "Escape") return;
1758
+ if (reasonOpen) {
1759
+ setReasonOpen(false);
1760
+ return;
1761
+ }
1762
+ onClose();
1763
+ },
1764
+ [onClose, reasonOpen]
1765
+ );
1766
+ useEffect(() => {
1767
+ if (!open || typeof document === "undefined") return;
1768
+ document.addEventListener("keydown", handleKeyDown);
1769
+ const previousOverflow = document.body.style.overflow;
1770
+ document.body.style.overflow = "hidden";
1771
+ return () => {
1772
+ document.removeEventListener("keydown", handleKeyDown);
1773
+ document.body.style.overflow = previousOverflow;
1774
+ };
1775
+ }, [handleKeyDown, open]);
1776
+ const handleSubmit = async () => {
1777
+ setSubmitting(true);
1778
+ setError(null);
1779
+ setReasonOpen(false);
1780
+ try {
1781
+ await onSubmit(reason, details.trim() ? details.trim() : void 0);
1782
+ onClose();
1783
+ } catch (e) {
1784
+ setError(e instanceof Error ? e.message : "Could not send feedback");
1785
+ setSubmitting(false);
1786
+ }
1787
+ };
1788
+ const selectedReason = NEGATIVE_FEEDBACK_REASONS.find((item) => item.value === reason) ?? NEGATIVE_FEEDBACK_REASONS[0];
1789
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: open ? /* @__PURE__ */ jsx(
1790
+ motion.div,
1791
+ {
1792
+ className: "payman-v2-feedback-modal-backdrop",
1793
+ initial: { opacity: 0 },
1794
+ animate: { opacity: 1 },
1795
+ exit: { opacity: 0 },
1796
+ transition: { duration: 0.18 },
1797
+ onClick: onClose,
1798
+ role: "presentation",
1799
+ children: /* @__PURE__ */ jsxs(
1800
+ motion.div,
1801
+ {
1802
+ initial: { opacity: 0, y: 12, scale: 0.98 },
1803
+ animate: { opacity: 1, y: 0, scale: 1 },
1804
+ exit: { opacity: 0, y: 8, scale: 0.98 },
1805
+ transition: { duration: 0.2, ease: [0.16, 1, 0.3, 1] },
1806
+ className: "payman-v2-feedback-modal-dialog",
1807
+ onClick: (event) => event.stopPropagation(),
1808
+ role: "dialog",
1809
+ "aria-modal": "true",
1810
+ "aria-labelledby": "payman-v2-feedback-title",
1811
+ children: [
1812
+ /* @__PURE__ */ jsx(
1813
+ "button",
1814
+ {
1815
+ type: "button",
1816
+ onClick: onClose,
1817
+ disabled: submitting,
1818
+ className: "payman-v2-feedback-modal-close",
1819
+ "aria-label": "Close feedback modal",
1820
+ children: /* @__PURE__ */ jsx(X, { size: 18, strokeWidth: 2 })
1821
+ }
1822
+ ),
1823
+ /* @__PURE__ */ jsx(
1824
+ "h2",
1825
+ {
1826
+ id: "payman-v2-feedback-title",
1827
+ className: "payman-v2-feedback-modal-title",
1828
+ children: "Tell us what went wrong"
1829
+ }
1830
+ ),
1831
+ /* @__PURE__ */ jsx(
1832
+ "label",
1833
+ {
1834
+ className: "payman-v2-feedback-modal-label",
1835
+ htmlFor: "payman-v2-feedback-reason",
1836
+ children: "What was the issue?"
1837
+ }
1838
+ ),
1839
+ /* @__PURE__ */ jsxs(
1840
+ "div",
1841
+ {
1842
+ id: "payman-v2-feedback-reason",
1843
+ className: "payman-v2-feedback-modal-reason",
1844
+ onBlur: (event) => {
1845
+ const nextTarget = event.relatedTarget;
1846
+ if (!nextTarget || !event.currentTarget.contains(nextTarget)) {
1847
+ setReasonOpen(false);
1848
+ }
1849
+ },
1850
+ children: [
1851
+ /* @__PURE__ */ jsxs(
1852
+ "button",
1853
+ {
1854
+ type: "button",
1855
+ className: "payman-v2-feedback-modal-reason-trigger",
1856
+ "aria-haspopup": "listbox",
1857
+ "aria-expanded": reasonOpen,
1858
+ "aria-controls": "payman-v2-feedback-reason-options",
1859
+ disabled: submitting,
1860
+ onClick: () => setReasonOpen((current) => !current),
1861
+ children: [
1862
+ /* @__PURE__ */ jsx("span", { children: selectedReason.label }),
1863
+ /* @__PURE__ */ jsx(
1864
+ ChevronDown,
1865
+ {
1866
+ size: 17,
1867
+ strokeWidth: 2,
1868
+ className: reasonOpen ? "payman-v2-feedback-modal-reason-chevron payman-v2-feedback-modal-reason-chevron-open" : "payman-v2-feedback-modal-reason-chevron"
1869
+ }
1870
+ )
1871
+ ]
1872
+ }
1873
+ ),
1874
+ reasonOpen ? /* @__PURE__ */ jsx(
1875
+ "div",
1876
+ {
1877
+ id: "payman-v2-feedback-reason-options",
1878
+ className: "payman-v2-feedback-modal-reason-menu",
1879
+ role: "listbox",
1880
+ "aria-label": "Feedback issue",
1881
+ tabIndex: -1,
1882
+ children: NEGATIVE_FEEDBACK_REASONS.map((item) => {
1883
+ const isSelected = item.value === reason;
1884
+ return /* @__PURE__ */ jsxs(
1885
+ "button",
1886
+ {
1887
+ type: "button",
1888
+ className: isSelected ? "payman-v2-feedback-modal-reason-option payman-v2-feedback-modal-reason-option-selected" : "payman-v2-feedback-modal-reason-option",
1889
+ role: "option",
1890
+ "aria-selected": isSelected,
1891
+ onClick: () => {
1892
+ setReason(item.value);
1893
+ setReasonOpen(false);
1894
+ },
1895
+ children: [
1896
+ /* @__PURE__ */ jsx("span", { children: item.label }),
1897
+ isSelected ? /* @__PURE__ */ jsx(Check, { size: 16, strokeWidth: 2 }) : null
1898
+ ]
1899
+ },
1900
+ item.value
1901
+ );
1902
+ })
1903
+ }
1904
+ ) : null
1905
+ ]
1906
+ }
1907
+ ),
1908
+ /* @__PURE__ */ jsx(
1909
+ "textarea",
1910
+ {
1911
+ className: "payman-v2-feedback-modal-textarea",
1912
+ placeholder: "Add details (optional)",
1913
+ value: details,
1914
+ maxLength: MAX_DETAILS_CHARS,
1915
+ onChange: (e) => setDetails(e.target.value),
1916
+ disabled: submitting
1917
+ }
1918
+ ),
1919
+ error ? /* @__PURE__ */ jsx("p", { className: "payman-v2-feedback-modal-error", children: error }) : null,
1920
+ /* @__PURE__ */ jsxs("div", { className: "payman-v2-feedback-modal-actions", children: [
1921
+ /* @__PURE__ */ jsx(
1922
+ "button",
1923
+ {
1924
+ type: "button",
1925
+ onClick: onClose,
1926
+ disabled: submitting,
1927
+ className: "payman-v2-feedback-modal-btn payman-v2-feedback-modal-btn-secondary",
1928
+ children: "Cancel"
1929
+ }
1930
+ ),
1931
+ /* @__PURE__ */ jsx(
1932
+ "button",
1933
+ {
1934
+ type: "button",
1935
+ onClick: handleSubmit,
1936
+ disabled: submitting,
1937
+ className: "payman-v2-feedback-modal-btn payman-v2-feedback-modal-btn-primary",
1938
+ children: submitting ? "Sending\u2026" : "Submit"
1939
+ }
1940
+ )
1941
+ ] })
1942
+ ]
1943
+ }
1944
+ )
1945
+ }
1946
+ ) : null });
1947
+ }
1670
1948
  var RESPONSE_SPEED = {
1671
1949
  normal: [2, 4],
1672
1950
  fast: 1,
@@ -1749,12 +2027,26 @@ function AssistantMessageV2({
1749
2027
  message,
1750
2028
  onImageClick,
1751
2029
  onExecutionTraceClick,
1752
- onFeedback,
2030
+ onSubmitFeedback,
1753
2031
  actions,
1754
2032
  typingSpeed = 4
1755
2033
  }) {
1756
2034
  const [copied, setCopied] = useState(false);
1757
2035
  const [activeFeedback, setActiveFeedback] = useState(null);
2036
+ const [reasonModalOpen, setReasonModalOpen] = useState(false);
2037
+ const canSubmitFeedback = !!onSubmitFeedback && !!message.executionId;
2038
+ const handlePositiveFeedback = () => {
2039
+ if (!canSubmitFeedback || activeFeedback === "up") return;
2040
+ const previous = activeFeedback;
2041
+ setActiveFeedback("up");
2042
+ Promise.resolve(
2043
+ onSubmitFeedback?.({
2044
+ messageId: message.id,
2045
+ executionId: message.executionId,
2046
+ feedback: "POSITIVE"
2047
+ })
2048
+ ).catch(() => setActiveFeedback(previous));
2049
+ };
1758
2050
  const [toast, setToast] = useState(null);
1759
2051
  const copyResetTimerRef = useRef(null);
1760
2052
  const toastTimerRef = useRef(null);
@@ -1784,34 +2076,6 @@ function AssistantMessageV2({
1784
2076
  void 0,
1785
2077
  typingSpeed
1786
2078
  );
1787
- const elapsedMs = (() => {
1788
- const fromServer = message.totalElapsedMs;
1789
- if (typeof fromServer === "number" && Number.isFinite(fromServer) && fromServer > 0) {
1790
- return fromServer;
1791
- }
1792
- const steps = message.steps;
1793
- if (!steps || steps.length === 0) return void 0;
1794
- let earliest = Number.POSITIVE_INFINITY;
1795
- let latest = Number.NEGATIVE_INFINITY;
1796
- for (const s of steps) {
1797
- if (!s.timestamp) continue;
1798
- if (s.timestamp < earliest) earliest = s.timestamp;
1799
- const end = s.timestamp + (s.elapsedMs ?? 0);
1800
- if (end > latest) latest = end;
1801
- }
1802
- if (!Number.isFinite(earliest) || !Number.isFinite(latest)) {
1803
- const total = steps.reduce((sum, s) => sum + (s.elapsedMs || 0), 0);
1804
- return total > 0 ? total : void 0;
1805
- }
1806
- const diff = latest - earliest;
1807
- return diff > 0 ? diff : void 0;
1808
- })();
1809
- function formatElapsed(ms) {
1810
- if (ms === void 0) return void 0;
1811
- if (ms < 1e3) return `${Math.round(ms)}ms`;
1812
- return `${(ms / 1e3).toFixed(1)}s`;
1813
- }
1814
- const totalElapsedLabel = formatElapsed(elapsedMs);
1815
2079
  const stickyLabel = (() => {
1816
2080
  const steps = message.steps;
1817
2081
  if (!steps || steps.length === 0) return void 0;
@@ -1994,14 +2258,10 @@ function AssistantMessageV2({
1994
2258
  children: copied ? /* @__PURE__ */ jsx(Check, { style: { width: 16, height: 16 } }) : /* @__PURE__ */ jsx(Copy, { style: { width: 16, height: 16 } })
1995
2259
  }
1996
2260
  ) }),
1997
- showThumbsUp && /* @__PURE__ */ jsx(ActionTooltipV2, { label: "Good response", children: /* @__PURE__ */ jsx(
2261
+ showThumbsUp && canSubmitFeedback && /* @__PURE__ */ jsx(ActionTooltipV2, { label: "Good response", children: /* @__PURE__ */ jsx(
1998
2262
  "button",
1999
2263
  {
2000
- onClick: () => {
2001
- const next = activeFeedback === "up" ? null : "up";
2002
- setActiveFeedback(next);
2003
- if (next) onFeedback?.({ messageId: message.id, feedback: "up" });
2004
- },
2264
+ onClick: handlePositiveFeedback,
2005
2265
  className: cn(
2006
2266
  "payman-v2-assistant-msg-action-btn",
2007
2267
  activeFeedback === "up" && "payman-v2-assistant-msg-action-btn-active"
@@ -2010,14 +2270,10 @@ function AssistantMessageV2({
2010
2270
  children: /* @__PURE__ */ jsx(ThumbsUp, { style: { width: 15, height: 15 } })
2011
2271
  }
2012
2272
  ) }),
2013
- showThumbsDown && /* @__PURE__ */ jsx(ActionTooltipV2, { label: "Bad response", children: /* @__PURE__ */ jsx(
2273
+ showThumbsDown && canSubmitFeedback && /* @__PURE__ */ jsx(ActionTooltipV2, { label: "Bad response", children: /* @__PURE__ */ jsx(
2014
2274
  "button",
2015
2275
  {
2016
- onClick: () => {
2017
- const next = activeFeedback === "down" ? null : "down";
2018
- setActiveFeedback(next);
2019
- if (next) onFeedback?.({ messageId: message.id, feedback: "down" });
2020
- },
2276
+ onClick: () => setReasonModalOpen(true),
2021
2277
  className: cn(
2022
2278
  "payman-v2-assistant-msg-action-btn",
2023
2279
  activeFeedback === "down" && "payman-v2-assistant-msg-action-btn-active"
@@ -2034,10 +2290,25 @@ function AssistantMessageV2({
2034
2290
  "aria-label": "Trace response",
2035
2291
  children: /* @__PURE__ */ jsx(Binoculars, { style: { width: 16, height: 16 } })
2036
2292
  }
2037
- ) }),
2038
- totalElapsedLabel && /* @__PURE__ */ jsx("span", { className: "payman-v2-assistant-msg-elapsed", children: totalElapsedLabel })
2293
+ ) })
2039
2294
  ] })
2040
- ] })
2295
+ ] }),
2296
+ /* @__PURE__ */ jsx(
2297
+ FeedbackReasonModal,
2298
+ {
2299
+ open: reasonModalOpen,
2300
+ onClose: () => setReasonModalOpen(false),
2301
+ onSubmit: async (reason, details) => {
2302
+ await onSubmitFeedback?.({
2303
+ messageId: message.id,
2304
+ executionId: message.executionId,
2305
+ feedback: reason,
2306
+ details
2307
+ });
2308
+ setActiveFeedback("down");
2309
+ }
2310
+ }
2311
+ )
2041
2312
  ] });
2042
2313
  }
2043
2314
  var DEFAULT_MAX_LENGTH = 6;
@@ -2318,7 +2589,7 @@ var MessageListV2 = forwardRef(
2318
2589
  onApproveAction,
2319
2590
  onRejectAction,
2320
2591
  onResendAction,
2321
- onMessageFeedback,
2592
+ onSubmitFeedback,
2322
2593
  typingSpeed = 4
2323
2594
  }, ref) {
2324
2595
  const scrollRef = useRef(null);
@@ -2458,7 +2729,7 @@ var MessageListV2 = forwardRef(
2458
2729
  message,
2459
2730
  onImageClick,
2460
2731
  onExecutionTraceClick,
2461
- onFeedback: onMessageFeedback,
2732
+ onSubmitFeedback,
2462
2733
  actions: messageActions?.assistantMessageActions,
2463
2734
  typingSpeed
2464
2735
  }
@@ -2622,8 +2893,9 @@ var ChatInputV2 = forwardRef(
2622
2893
  }, [isRecording, transcribedText]);
2623
2894
  const handleSend = useCallback(() => {
2624
2895
  if (!value.trim() || disabled) return;
2625
- if (slashCommandBodyIsEmpty(value)) {
2626
- setInlineHint("Add markdown content below the command line.");
2896
+ const commandHint = getSlashCommandValidationHint(value);
2897
+ if (commandHint) {
2898
+ setInlineHint(commandHint);
2627
2899
  return;
2628
2900
  }
2629
2901
  voiceDraftSyncActiveRef.current = false;
@@ -3225,6 +3497,316 @@ function ImageLightboxV2({ src, alt, onClose }) {
3225
3497
  document.body
3226
3498
  );
3227
3499
  }
3500
+ var PRE_PIPELINE_STEPS = /* @__PURE__ */ new Set([
3501
+ "record_execution",
3502
+ "resolve_provider",
3503
+ "resolve_mcp_servers",
3504
+ "build_pipeline",
3505
+ "load_skill_index"
3506
+ ]);
3507
+ function stepColor(step) {
3508
+ if (step.status === "failed") return "#ef4444";
3509
+ if (PRE_PIPELINE_STEPS.has(step.step)) return "#f59e0b";
3510
+ return "#3b82f6";
3511
+ }
3512
+ function formatMs(ms) {
3513
+ if (ms == null) return "\u2014";
3514
+ if (ms < 1e3) return `${ms} ms`;
3515
+ return `${(ms / 1e3).toFixed(2)} s`;
3516
+ }
3517
+ function TraceTimelineModal({
3518
+ open,
3519
+ onClose,
3520
+ executionId,
3521
+ apiBaseUrl,
3522
+ apiHeaders
3523
+ }) {
3524
+ const [trace, setTrace] = useState(null);
3525
+ const [loading, setLoading] = useState(false);
3526
+ const [error, setError] = useState(null);
3527
+ useEffect(() => {
3528
+ if (!open || !executionId) return;
3529
+ const ctrl = new AbortController();
3530
+ setLoading(true);
3531
+ setError(null);
3532
+ setTrace(null);
3533
+ const base = apiBaseUrl.replace(/\/+$/, "");
3534
+ fetch(`${base}/api/ask/executions/${encodeURIComponent(executionId)}/trace`, {
3535
+ method: "GET",
3536
+ headers: { Accept: "application/json", ...apiHeaders },
3537
+ signal: ctrl.signal
3538
+ }).then(async (resp) => {
3539
+ if (!resp.ok) {
3540
+ throw new Error(`Trace fetch failed: ${resp.status} ${resp.statusText}`);
3541
+ }
3542
+ return resp.json();
3543
+ }).then((data) => setTrace(data)).catch((e) => {
3544
+ if (e.name === "AbortError") return;
3545
+ setError(e instanceof Error ? e.message : "Trace fetch failed");
3546
+ }).finally(() => setLoading(false));
3547
+ return () => ctrl.abort();
3548
+ }, [open, executionId, apiBaseUrl, apiHeaders]);
3549
+ const totalMs = useMemo(() => {
3550
+ if (!trace) return 0;
3551
+ const meta = trace.runMetadata?.totalTimeMs;
3552
+ if (typeof meta === "number" && meta > 0) return meta;
3553
+ const starts = trace.pipelineSteps.map((s) => s.startTime);
3554
+ const ends = trace.pipelineSteps.map((s) => s.endTime ?? s.startTime).filter((t) => typeof t === "number");
3555
+ if (starts.length === 0 || ends.length === 0) return 0;
3556
+ return Math.max(...ends) - Math.min(...starts);
3557
+ }, [trace]);
3558
+ const accountedMs = useMemo(
3559
+ () => (trace?.pipelineSteps ?? []).reduce(
3560
+ (sum, s) => sum + (s.durationMs ?? 0),
3561
+ 0
3562
+ ),
3563
+ [trace]
3564
+ );
3565
+ const unaccountedMs = Math.max(totalMs - accountedMs, 0);
3566
+ if (typeof document === "undefined") return null;
3567
+ return createPortal(
3568
+ /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(
3569
+ motion.div,
3570
+ {
3571
+ initial: { opacity: 0 },
3572
+ animate: { opacity: 1 },
3573
+ exit: { opacity: 0 },
3574
+ transition: { duration: 0.15 },
3575
+ onClick: onClose,
3576
+ style: {
3577
+ position: "fixed",
3578
+ inset: 0,
3579
+ background: "rgba(0,0,0,0.45)",
3580
+ zIndex: 9999,
3581
+ display: "flex",
3582
+ alignItems: "center",
3583
+ justifyContent: "center",
3584
+ padding: 24
3585
+ },
3586
+ children: /* @__PURE__ */ jsxs(
3587
+ motion.div,
3588
+ {
3589
+ initial: { y: 12, opacity: 0 },
3590
+ animate: { y: 0, opacity: 1 },
3591
+ exit: { y: 12, opacity: 0 },
3592
+ transition: { duration: 0.18 },
3593
+ onClick: (e) => e.stopPropagation(),
3594
+ style: {
3595
+ background: "#0b1220",
3596
+ color: "#e5e7eb",
3597
+ borderRadius: 12,
3598
+ width: "100%",
3599
+ maxWidth: 760,
3600
+ maxHeight: "85vh",
3601
+ overflow: "auto",
3602
+ boxShadow: "0 20px 60px rgba(0,0,0,0.45)",
3603
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif"
3604
+ },
3605
+ children: [
3606
+ /* @__PURE__ */ jsxs(
3607
+ "header",
3608
+ {
3609
+ style: {
3610
+ display: "flex",
3611
+ alignItems: "center",
3612
+ justifyContent: "space-between",
3613
+ padding: "14px 18px",
3614
+ borderBottom: "1px solid rgba(255,255,255,0.08)"
3615
+ },
3616
+ children: [
3617
+ /* @__PURE__ */ jsxs("div", { children: [
3618
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 14, fontWeight: 600 }, children: "Execution trace" }),
3619
+ /* @__PURE__ */ jsx(
3620
+ "div",
3621
+ {
3622
+ style: {
3623
+ fontSize: 11,
3624
+ opacity: 0.6,
3625
+ marginTop: 2,
3626
+ fontFamily: "ui-monospace, SFMono-Regular, monospace"
3627
+ },
3628
+ children: executionId ?? ""
3629
+ }
3630
+ )
3631
+ ] }),
3632
+ /* @__PURE__ */ jsx(
3633
+ "button",
3634
+ {
3635
+ onClick: onClose,
3636
+ "aria-label": "Close",
3637
+ style: {
3638
+ background: "transparent",
3639
+ border: 0,
3640
+ color: "#e5e7eb",
3641
+ cursor: "pointer",
3642
+ padding: 6,
3643
+ borderRadius: 6
3644
+ },
3645
+ children: /* @__PURE__ */ jsx(X, { style: { width: 18, height: 18 } })
3646
+ }
3647
+ )
3648
+ ]
3649
+ }
3650
+ ),
3651
+ /* @__PURE__ */ jsxs("div", { style: { padding: 18 }, children: [
3652
+ loading && /* @__PURE__ */ jsx("div", { style: { opacity: 0.7, fontSize: 13 }, children: "Loading trace\u2026" }),
3653
+ error && /* @__PURE__ */ jsx(
3654
+ "div",
3655
+ {
3656
+ style: {
3657
+ color: "#fca5a5",
3658
+ fontSize: 13,
3659
+ whiteSpace: "pre-wrap"
3660
+ },
3661
+ children: error
3662
+ }
3663
+ ),
3664
+ trace && !loading && !error && /* @__PURE__ */ jsx(
3665
+ TimelineBars,
3666
+ {
3667
+ trace,
3668
+ totalMs,
3669
+ unaccountedMs
3670
+ }
3671
+ )
3672
+ ] })
3673
+ ]
3674
+ }
3675
+ )
3676
+ }
3677
+ ) }),
3678
+ document.body
3679
+ );
3680
+ }
3681
+ function TimelineBars({
3682
+ trace,
3683
+ totalMs,
3684
+ unaccountedMs
3685
+ }) {
3686
+ const bars = trace.pipelineSteps.map((step) => {
3687
+ const duration = step.durationMs ?? 0;
3688
+ const widthPct = totalMs > 0 ? duration / totalMs * 100 : 0;
3689
+ const llmMs = step.llmCall?.durationMs ?? null;
3690
+ return /* @__PURE__ */ jsxs("div", { style: { marginBottom: 8 }, children: [
3691
+ /* @__PURE__ */ jsxs(
3692
+ "div",
3693
+ {
3694
+ style: {
3695
+ display: "flex",
3696
+ justifyContent: "space-between",
3697
+ fontSize: 12,
3698
+ marginBottom: 4,
3699
+ fontFamily: "ui-monospace, SFMono-Regular, monospace"
3700
+ },
3701
+ children: [
3702
+ /* @__PURE__ */ jsxs("span", { children: [
3703
+ step.step,
3704
+ step.status === "failed" && /* @__PURE__ */ jsx("span", { style: { color: "#fca5a5", marginLeft: 6 }, children: "(failed)" })
3705
+ ] }),
3706
+ /* @__PURE__ */ jsxs("span", { style: { opacity: 0.75 }, children: [
3707
+ formatMs(duration),
3708
+ llmMs != null && /* @__PURE__ */ jsxs("span", { style: { marginLeft: 8, opacity: 0.6 }, children: [
3709
+ "(LLM ",
3710
+ formatMs(llmMs),
3711
+ ")"
3712
+ ] })
3713
+ ] })
3714
+ ]
3715
+ }
3716
+ ),
3717
+ /* @__PURE__ */ jsx(
3718
+ "div",
3719
+ {
3720
+ style: {
3721
+ height: 8,
3722
+ background: "rgba(255,255,255,0.06)",
3723
+ borderRadius: 4,
3724
+ overflow: "hidden"
3725
+ },
3726
+ children: /* @__PURE__ */ jsx(
3727
+ "div",
3728
+ {
3729
+ style: {
3730
+ height: "100%",
3731
+ width: `${Math.max(widthPct, 0.3)}%`,
3732
+ background: stepColor(step),
3733
+ transition: "width 0.2s ease"
3734
+ }
3735
+ }
3736
+ )
3737
+ }
3738
+ )
3739
+ ] }, `${step.step}-${step.startTime}`);
3740
+ });
3741
+ return /* @__PURE__ */ jsxs("div", { children: [
3742
+ bars,
3743
+ unaccountedMs > 0 && /* @__PURE__ */ jsxs("div", { style: { marginBottom: 8 }, children: [
3744
+ /* @__PURE__ */ jsxs(
3745
+ "div",
3746
+ {
3747
+ style: {
3748
+ display: "flex",
3749
+ justifyContent: "space-between",
3750
+ fontSize: 12,
3751
+ marginBottom: 4,
3752
+ fontFamily: "ui-monospace, SFMono-Regular, monospace",
3753
+ opacity: 0.7
3754
+ },
3755
+ children: [
3756
+ /* @__PURE__ */ jsx("span", { children: "(unaccounted)" }),
3757
+ /* @__PURE__ */ jsx("span", { children: formatMs(unaccountedMs) })
3758
+ ]
3759
+ }
3760
+ ),
3761
+ /* @__PURE__ */ jsx(
3762
+ "div",
3763
+ {
3764
+ style: {
3765
+ height: 8,
3766
+ background: "rgba(255,255,255,0.06)",
3767
+ borderRadius: 4,
3768
+ overflow: "hidden"
3769
+ },
3770
+ children: /* @__PURE__ */ jsx(
3771
+ "div",
3772
+ {
3773
+ style: {
3774
+ height: "100%",
3775
+ width: `${unaccountedMs / Math.max(totalMs, 1) * 100}%`,
3776
+ background: "repeating-linear-gradient(45deg, rgba(255,255,255,0.18) 0 4px, transparent 4px 8px)"
3777
+ }
3778
+ }
3779
+ )
3780
+ }
3781
+ )
3782
+ ] }),
3783
+ /* @__PURE__ */ jsxs(
3784
+ "div",
3785
+ {
3786
+ style: {
3787
+ marginTop: 14,
3788
+ paddingTop: 12,
3789
+ borderTop: "1px solid rgba(255,255,255,0.08)",
3790
+ fontSize: 12,
3791
+ display: "flex",
3792
+ justifyContent: "space-between",
3793
+ opacity: 0.85
3794
+ },
3795
+ children: [
3796
+ /* @__PURE__ */ jsxs("span", { children: [
3797
+ trace.pipelineSteps.length,
3798
+ " step",
3799
+ trace.pipelineSteps.length === 1 ? "" : "s"
3800
+ ] }),
3801
+ /* @__PURE__ */ jsxs("span", { style: { fontFamily: "ui-monospace, SFMono-Regular, monospace" }, children: [
3802
+ "total: ",
3803
+ formatMs(totalMs)
3804
+ ] })
3805
+ ]
3806
+ }
3807
+ )
3808
+ ] });
3809
+ }
3228
3810
  var DEFAULT_USER_ACTION_STATE = {
3229
3811
  request: null,
3230
3812
  result: null};
@@ -3415,12 +3997,51 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3415
3997
  ]
3416
3998
  );
3417
3999
  const {
3418
- onExecutionTraceClick,
4000
+ onExecutionTraceClick: rawOnExecutionTraceClick,
3419
4001
  onResetSession,
3420
4002
  onUploadImageClick,
3421
4003
  onAttachFileClick,
3422
4004
  onMessageFeedback
3423
4005
  } = callbacks;
4006
+ const [debugTraceExecutionId, setDebugTraceExecutionId] = useState(null);
4007
+ const handleSubmitFeedback = useCallback(
4008
+ async ({ messageId, executionId, feedback, details }) => {
4009
+ if (!executionId) {
4010
+ throw new Error("Cannot submit feedback before the response completes");
4011
+ }
4012
+ await submitFeedback({
4013
+ baseUrl: config.api.baseUrl,
4014
+ streamEndpoint: config.api.streamEndpoint,
4015
+ headers: config.api.headers,
4016
+ authToken: config.api.authToken,
4017
+ stage: config.stage,
4018
+ stageQueryParam: config.stageQueryParam,
4019
+ executionId,
4020
+ feedback,
4021
+ details
4022
+ });
4023
+ onMessageFeedback?.({
4024
+ messageId,
4025
+ feedback: feedback === "POSITIVE" ? "up" : "down"
4026
+ });
4027
+ },
4028
+ [
4029
+ config.api.baseUrl,
4030
+ config.api.streamEndpoint,
4031
+ config.api.headers,
4032
+ config.api.authToken,
4033
+ config.stage,
4034
+ config.stageQueryParam,
4035
+ onMessageFeedback
4036
+ ]
4037
+ );
4038
+ const onExecutionTraceClick = useMemo(() => {
4039
+ if (!config.debug) return rawOnExecutionTraceClick;
4040
+ return (data) => {
4041
+ rawOnExecutionTraceClick?.(data);
4042
+ if (data.executionId) setDebugTraceExecutionId(data.executionId);
4043
+ };
4044
+ }, [config.debug, rawOnExecutionTraceClick]);
3424
4045
  const performResetSession = useCallback(() => {
3425
4046
  resetToEmptyStateRef.current = true;
3426
4047
  setEditingMessageId(null);
@@ -3478,6 +4099,7 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3478
4099
  showEmptyStateIcon = true,
3479
4100
  emptyStateComponent,
3480
4101
  showResetSession = false,
4102
+ enableDeepModeToggle = true,
3481
4103
  showAttachmentButton = true,
3482
4104
  showUploadImageButton = true,
3483
4105
  showAttachFileButton = true,
@@ -3533,6 +4155,7 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3533
4155
  stopRecording();
3534
4156
  };
3535
4157
  const isV2InputDisabled = !isSessionParamsConfigured || disableInput;
4158
+ const effectiveAnalysisMode = enableDeepModeToggle ? analysisMode : "fast";
3536
4159
  const isEmpty = messages.length === 0;
3537
4160
  if (isChatDisabled) {
3538
4161
  if (disabledComponent) {
@@ -3591,7 +4214,7 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3591
4214
  if (isRecording) stopRecording();
3592
4215
  if (text.trim() && !disableInput && isSessionParamsConfigured) {
3593
4216
  setEditingMessageId(null);
3594
- void sendMessage(text.trim(), { analysisMode });
4217
+ void sendMessage(text.trim(), { analysisMode: effectiveAnalysisMode });
3595
4218
  }
3596
4219
  };
3597
4220
  const handleVoicePress = useCallback(async () => {
@@ -3618,7 +4241,9 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3618
4241
  (message) => message.id === messageId && message.role === "user"
3619
4242
  );
3620
4243
  if (!targetMessage?.content.trim()) return;
3621
- void sendMessage(targetMessage.content.trim(), { analysisMode });
4244
+ void sendMessage(targetMessage.content.trim(), {
4245
+ analysisMode: effectiveAnalysisMode
4246
+ });
3622
4247
  const bump = () => messageListV2Ref.current?.scrollToBottom("instant");
3623
4248
  requestAnimationFrame(() => {
3624
4249
  bump();
@@ -3626,7 +4251,7 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3626
4251
  window.setTimeout(bump, 120);
3627
4252
  });
3628
4253
  });
3629
- }, [isWaitingForResponse, messages, sendMessage, analysisMode]);
4254
+ }, [isWaitingForResponse, messages, sendMessage, effectiveAnalysisMode]);
3630
4255
  const handleClearEditing = useCallback(() => {
3631
4256
  setEditingMessageId(null);
3632
4257
  }, []);
@@ -3705,8 +4330,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3705
4330
  onAttachFileClick,
3706
4331
  editingMessageId,
3707
4332
  onClearEditing: handleClearEditing,
3708
- analysisMode,
3709
- onAnalysisModeChange: setAnalysisMode,
4333
+ analysisMode: enableDeepModeToggle ? analysisMode : void 0,
4334
+ onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
3710
4335
  slashCommands
3711
4336
  }
3712
4337
  )
@@ -3746,7 +4371,7 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3746
4371
  onResendAction: isUserActionSupported ? async () => {
3747
4372
  await resendOtp();
3748
4373
  } : void 0,
3749
- onMessageFeedback
4374
+ onSubmitFeedback: handleSubmitFeedback
3750
4375
  }
3751
4376
  ),
3752
4377
  /* @__PURE__ */ jsx(
@@ -3781,8 +4406,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3781
4406
  onAttachFileClick,
3782
4407
  editingMessageId,
3783
4408
  onClearEditing: handleClearEditing,
3784
- analysisMode,
3785
- onAnalysisModeChange: setAnalysisMode,
4409
+ analysisMode: enableDeepModeToggle ? analysisMode : void 0,
4410
+ onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
3786
4411
  slashCommands
3787
4412
  }
3788
4413
  )
@@ -3805,6 +4430,16 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3805
4430
  onClose: closeResetSessionConfirm,
3806
4431
  onConfirm: performResetSession
3807
4432
  }
4433
+ ),
4434
+ config.debug && /* @__PURE__ */ jsx(
4435
+ TraceTimelineModal,
4436
+ {
4437
+ open: debugTraceExecutionId !== null,
4438
+ onClose: () => setDebugTraceExecutionId(null),
4439
+ executionId: debugTraceExecutionId,
4440
+ apiBaseUrl: config.api.baseUrl,
4441
+ apiHeaders: config.api.headers
4442
+ }
3808
4443
  )
3809
4444
  ]
3810
4445
  }