@ergoblockchain/sage-widget 0.1.0 → 0.2.0

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/react.cjs CHANGED
@@ -14,13 +14,129 @@ var DEFAULT_REFRESH_MS = 6e4;
14
14
  async function fetchSageActivity(opts = {}) {
15
15
  const base = opts.apiBase ?? DEFAULT_API_BASE;
16
16
  const limit = Math.min(Math.max(opts.limit ?? DEFAULT_LIMIT, 1), 25);
17
- const url = `${base}/api/sage/activity?limit=${limit}`;
17
+ const url = `${trimSlash(base)}/api/sage/activity?limit=${limit}`;
18
18
  const res = await fetch(url, { signal: opts.signal });
19
19
  if (!res.ok) {
20
20
  throw new Error(`sage activity ${res.status}`);
21
21
  }
22
22
  return await res.json();
23
23
  }
24
+ async function fetchSageQuote(opts) {
25
+ const res = await fetch(`${apiBase(opts)}/api/sage/quote`, {
26
+ method: "POST",
27
+ headers: jsonHeaders(opts),
28
+ body: JSON.stringify({
29
+ question: opts.question,
30
+ history: opts.history ?? []
31
+ }),
32
+ signal: opts.signal
33
+ });
34
+ const body = await parseJson(res);
35
+ if (!res.ok) throw new Error(readError(body, `sage quote ${res.status}`));
36
+ return body;
37
+ }
38
+ async function verifySagePayment(opts) {
39
+ const res = await fetch(`${apiBase(opts)}/api/sage/verify-payment`, {
40
+ method: "POST",
41
+ headers: jsonHeaders(opts),
42
+ body: JSON.stringify({
43
+ quote: opts.quote,
44
+ question: opts.question,
45
+ noteBoxId: opts.noteBoxId
46
+ }),
47
+ signal: opts.signal
48
+ });
49
+ const body = await parseJson(res);
50
+ if (!res.ok) throw new Error(readError(body, `sage verify-payment ${res.status}`));
51
+ return body;
52
+ }
53
+ async function fetchSageReceipt(id, opts = {}) {
54
+ const res = await fetch(`${apiBase(opts)}/api/sage/receipt/${encodeURIComponent(id)}`, {
55
+ headers: requestHeaders(opts),
56
+ signal: opts.signal
57
+ });
58
+ const body = await parseJson(res);
59
+ if (!res.ok) throw new Error(readError(body, `sage receipt ${res.status}`));
60
+ return body;
61
+ }
62
+ async function streamSageChat(opts) {
63
+ const res = await fetch(`${apiBase(opts)}/api/sage/chat`, {
64
+ method: "POST",
65
+ headers: jsonHeaders(opts),
66
+ body: JSON.stringify({
67
+ messages: opts.messages,
68
+ ...opts.paymentToken ? { paymentToken: opts.paymentToken } : {}
69
+ }),
70
+ signal: opts.signal
71
+ });
72
+ if (res.status === 402) {
73
+ const body = await parseJson(res);
74
+ return {
75
+ ok: false,
76
+ status: res.status,
77
+ text: "",
78
+ paymentRequired: body
79
+ };
80
+ }
81
+ if (!res.ok) {
82
+ const body = await parseJson(res);
83
+ return {
84
+ ok: false,
85
+ status: res.status,
86
+ text: "",
87
+ error: readError(body, `sage chat ${res.status}`)
88
+ };
89
+ }
90
+ if (!res.body) {
91
+ return { ok: false, status: res.status, text: "", error: "Sage chat stream missing body" };
92
+ }
93
+ const reader = res.body.getReader();
94
+ const decoder = new TextDecoder();
95
+ let buffer = "";
96
+ let text = "";
97
+ let tier;
98
+ while (true) {
99
+ const { value, done } = await reader.read();
100
+ if (done) break;
101
+ buffer += decoder.decode(value, { stream: true });
102
+ const parts = buffer.split("\n\n");
103
+ buffer = parts.pop() ?? "";
104
+ for (const part of parts) {
105
+ const event = parseSseEvent(part);
106
+ if (!event) continue;
107
+ if (event.type === "delta") text += event.text;
108
+ if (event.type === "tier") tier = event.tier;
109
+ opts.onEvent?.(event);
110
+ if (event.type === "error") {
111
+ return {
112
+ ok: false,
113
+ status: res.status,
114
+ text,
115
+ tier,
116
+ error: event.message
117
+ };
118
+ }
119
+ }
120
+ }
121
+ if (buffer.trim()) {
122
+ const event = parseSseEvent(buffer);
123
+ if (event) {
124
+ if (event.type === "delta") text += event.text;
125
+ if (event.type === "tier") tier = event.tier;
126
+ opts.onEvent?.(event);
127
+ if (event.type === "error") {
128
+ return {
129
+ ok: false,
130
+ status: res.status,
131
+ text,
132
+ tier,
133
+ error: event.message
134
+ };
135
+ }
136
+ }
137
+ }
138
+ return { ok: true, status: res.status, text, tier };
139
+ }
24
140
  function nanoToErg(nano) {
25
141
  if (!nano || nano <= 0) return "0";
26
142
  const erg = nano / 1e9;
@@ -37,16 +153,92 @@ function relativeTime(ms, now = Date.now()) {
37
153
  const day = Math.floor(hr / 24);
38
154
  return `${day}d ago`;
39
155
  }
40
- function receiptUrl(txId, apiBase = DEFAULT_API_BASE) {
41
- return `${apiBase}/r/sage/${txId}`;
156
+ function receiptUrl(txId, apiBase2 = DEFAULT_API_BASE) {
157
+ return `${trimSlash(apiBase2)}/r/sage/${txId}`;
42
158
  }
43
159
  function explorerUrl(txId, network = "testnet") {
44
160
  return network === "testnet" ? `https://testnet.ergoplatform.com/transactions/${txId}` : `https://explorer.ergoplatform.com/transactions/${txId}`;
45
161
  }
162
+ function apiBase(opts) {
163
+ return trimSlash(opts.apiBase ?? DEFAULT_API_BASE);
164
+ }
165
+ function trimSlash(value) {
166
+ return value.replace(/\/+$/, "");
167
+ }
168
+ function requestHeaders(opts) {
169
+ return {
170
+ ...opts.tenant?.id ? { "x-sage-tenant-id": opts.tenant.id } : {},
171
+ ...opts.tenant?.headers ?? {},
172
+ ...opts.headers ?? {}
173
+ };
174
+ }
175
+ function jsonHeaders(opts) {
176
+ return {
177
+ "content-type": "application/json",
178
+ ...requestHeaders(opts)
179
+ };
180
+ }
181
+ async function parseJson(res) {
182
+ const text = await res.text();
183
+ if (!text) return null;
184
+ try {
185
+ return JSON.parse(text);
186
+ } catch {
187
+ return { error: text };
188
+ }
189
+ }
190
+ function readError(body, fallback) {
191
+ if (body && typeof body === "object" && "error" in body) {
192
+ const error = body.error;
193
+ if (typeof error === "string") return error;
194
+ }
195
+ return fallback;
196
+ }
197
+ function parseSseEvent(raw) {
198
+ let eventName = "message";
199
+ let data = "";
200
+ for (const line of raw.split("\n")) {
201
+ if (line.startsWith("event:")) eventName = line.slice("event:".length).trim();
202
+ if (line.startsWith("data:")) data += line.slice("data:".length).trim();
203
+ }
204
+ if (!data) return null;
205
+ let parsed;
206
+ try {
207
+ parsed = JSON.parse(data);
208
+ } catch {
209
+ return null;
210
+ }
211
+ if (eventName === "tier") {
212
+ const tier = parsed.tier === "premium" ? "premium" : "free";
213
+ return {
214
+ type: "tier",
215
+ tier,
216
+ ...typeof parsed.model === "string" ? { model: parsed.model } : {}
217
+ };
218
+ }
219
+ if (eventName === "delta" && typeof parsed.text === "string") {
220
+ return { type: "delta", text: parsed.text };
221
+ }
222
+ if (eventName === "done") {
223
+ return {
224
+ type: "done",
225
+ ...typeof parsed.stopReason === "string" ? { stopReason: parsed.stopReason } : {},
226
+ ...typeof parsed.inputTokens === "number" ? { inputTokens: parsed.inputTokens } : {},
227
+ ...typeof parsed.outputTokens === "number" ? { outputTokens: parsed.outputTokens } : {}
228
+ };
229
+ }
230
+ if (eventName === "error") {
231
+ return {
232
+ type: "error",
233
+ message: typeof parsed.message === "string" ? parsed.message : "Sage stream error"
234
+ };
235
+ }
236
+ return null;
237
+ }
46
238
  var ONE_MIN = 6e4;
47
239
  function SageActivityFeed(props) {
48
240
  const {
49
- apiBase,
241
+ apiBase: apiBase2,
50
242
  limit = DEFAULT_LIMIT,
51
243
  refreshMs = DEFAULT_REFRESH_MS,
52
244
  onUpdate,
@@ -73,7 +265,7 @@ function SageActivityFeed(props) {
73
265
  async function load() {
74
266
  try {
75
267
  const data = await fetchSageActivity({
76
- apiBase,
268
+ apiBase: apiBase2,
77
269
  limit,
78
270
  signal: abort.signal
79
271
  });
@@ -98,7 +290,7 @@ function SageActivityFeed(props) {
98
290
  if (pollId) clearInterval(pollId);
99
291
  clearInterval(tickId);
100
292
  };
101
- }, [apiBase, limit, refreshMs]);
293
+ }, [apiBase2, limit, refreshMs]);
102
294
  if (children) {
103
295
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children({ loading, response, error }) });
104
296
  }
@@ -110,7 +302,7 @@ function SageActivityFeed(props) {
110
302
  loading,
111
303
  events: response?.events ?? [],
112
304
  network: response?.network ?? "testnet",
113
- apiBase,
305
+ apiBase: apiBase2,
114
306
  now
115
307
  }
116
308
  ),
@@ -140,22 +332,22 @@ function List({
140
332
  loading,
141
333
  events,
142
334
  network,
143
- apiBase,
335
+ apiBase: apiBase2,
144
336
  now
145
337
  }) {
146
338
  if (loading) return /* @__PURE__ */ jsxRuntime.jsx("div", { style: emptyStyle, children: "Loading\u2026" });
147
339
  if (events.length === 0)
148
340
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: emptyStyle, children: "No activity yet \u2014 be the first to ask Sage a paid query." });
149
- return /* @__PURE__ */ jsxRuntime.jsx("div", { children: events.map((evt) => /* @__PURE__ */ jsxRuntime.jsx(Row, { evt, network, apiBase, now }, evt.txId)) });
341
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: events.map((evt) => /* @__PURE__ */ jsxRuntime.jsx(Row, { evt, network, apiBase: apiBase2, now }, evt.txId)) });
150
342
  }
151
343
  function Row({
152
344
  evt,
153
345
  network,
154
- apiBase,
346
+ apiBase: apiBase2,
155
347
  now
156
348
  }) {
157
349
  const isSettle = evt.type === "settlement";
158
- const href = isSettle ? receiptUrl(evt.txId, apiBase) : explorerUrl(evt.txId, network);
350
+ const href = isSettle ? receiptUrl(evt.txId, apiBase2) : explorerUrl(evt.txId, network);
159
351
  const amount = isSettle ? evt.paymentNanoErg ?? evt.inflowNanoErg : evt.inflowNanoErg;
160
352
  const chipStyle = isSettle ? chipSettleStyle : chipIssueStyle;
161
353
  const chipText = isSettle ? "Settled" : evt.type === "issuance" ? "Issued" : "Transfer";
@@ -293,10 +485,484 @@ var errorStyle = {
293
485
  ...emptyStyle,
294
486
  color: "#f87171"
295
487
  };
488
+ function SagePaymentWidget(props) {
489
+ const {
490
+ apiBase: apiBase2 = DEFAULT_API_BASE,
491
+ tenant,
492
+ initialMessages = [],
493
+ placeholder = "Ask Sage about Ergo or agent payments...",
494
+ paymentInstructions,
495
+ className,
496
+ style,
497
+ title = "Ask Sage"
498
+ } = props;
499
+ const [messages, setMessages] = react.useState(initialMessages);
500
+ const [input, setInput] = react.useState("");
501
+ const [phase, setPhase] = react.useState("idle");
502
+ const [quoteResponse, setQuoteResponse] = react.useState(null);
503
+ const [activeQuestion, setActiveQuestion] = react.useState("");
504
+ const [noteBoxId, setNoteBoxId] = react.useState("");
505
+ const [receipt, setReceipt] = react.useState(null);
506
+ const [receiptBundle, setReceiptBundle] = react.useState(null);
507
+ const [error, setError] = react.useState(null);
508
+ const [tier, setTier] = react.useState(null);
509
+ const mountedRef = react.useRef(true);
510
+ react.useEffect(() => {
511
+ mountedRef.current = true;
512
+ return () => {
513
+ mountedRef.current = false;
514
+ };
515
+ }, []);
516
+ const busy = phase === "quoting" || phase === "verifying" || phase === "streaming";
517
+ const apiOpts = react.useMemo(() => ({ apiBase: apiBase2, tenant }), [apiBase2, tenant]);
518
+ async function submit(e) {
519
+ e.preventDefault();
520
+ const question = input.trim();
521
+ if (!question || busy) return;
522
+ const userMessage = { role: "user", content: question };
523
+ const nextMessages = [...messages, userMessage];
524
+ setMessages(nextMessages);
525
+ props.onMessage?.(userMessage, nextMessages);
526
+ setInput("");
527
+ setError(null);
528
+ setReceipt(null);
529
+ setReceiptBundle(null);
530
+ setQuoteResponse(null);
531
+ setActiveQuestion(question);
532
+ setNoteBoxId("");
533
+ setTier(null);
534
+ transition("quoting");
535
+ try {
536
+ const quote2 = await fetchSageQuote({
537
+ ...apiOpts,
538
+ question,
539
+ history: messages
540
+ });
541
+ props.onQuote?.(quote2);
542
+ if (quote2.premium) {
543
+ if (!quote2.quote) throw new Error("Sage marked this question premium but did not return a quote.");
544
+ setQuoteResponse(quote2);
545
+ transition("payment_required", { quote: quote2.quote });
546
+ return;
547
+ }
548
+ await streamAnswer(nextMessages, void 0, question);
549
+ } catch (err) {
550
+ fail(err);
551
+ }
552
+ }
553
+ async function verifyAndContinue() {
554
+ const quote2 = quoteResponse?.quote;
555
+ const note = noteBoxId.trim();
556
+ if (!quote2 || !activeQuestion || !note || busy) return;
557
+ setError(null);
558
+ transition("verifying");
559
+ let verified;
560
+ try {
561
+ verified = await verifySagePayment({
562
+ ...apiOpts,
563
+ quote: quote2,
564
+ question: activeQuestion,
565
+ noteBoxId: note
566
+ });
567
+ } catch (err) {
568
+ transition("payment_required");
569
+ fail(err, false);
570
+ return;
571
+ }
572
+ if (!mountedRef.current) return;
573
+ setReceipt(verified);
574
+ props.onReceipt?.(verified);
575
+ setQuoteResponse(null);
576
+ setNoteBoxId("");
577
+ transition("streaming", { quote: null, receipt: verified });
578
+ try {
579
+ const bundle = await fetchSageReceipt(verified.receiptId, apiOpts);
580
+ if (!mountedRef.current) return;
581
+ setReceiptBundle(bundle);
582
+ props.onReceiptBundle?.(bundle);
583
+ emitStatus("streaming", { quote: null, receipt: verified, receiptBundle: bundle });
584
+ } catch (bundleErr) {
585
+ props.onError?.(bundleErr);
586
+ }
587
+ try {
588
+ await streamAnswer(messages, verified.paymentToken, activeQuestion);
589
+ } catch (err) {
590
+ fail(err);
591
+ }
592
+ }
593
+ async function streamAnswer(baseMessages, paymentToken, fallbackQuestion) {
594
+ transition("streaming");
595
+ let text = "";
596
+ const placeholderMessage = { role: "assistant", content: "" };
597
+ setMessages([...baseMessages, placeholderMessage]);
598
+ const result = await streamSageChat({
599
+ ...apiOpts,
600
+ messages: baseMessages,
601
+ paymentToken,
602
+ onEvent(event) {
603
+ if (!mountedRef.current) return;
604
+ if (event.type === "tier") {
605
+ setTier(event.tier);
606
+ props.onTier?.(event.tier);
607
+ emitStatus("streaming", { tier: event.tier, messages: baseMessages });
608
+ }
609
+ if (event.type === "delta") {
610
+ text += event.text;
611
+ setMessages([...baseMessages, { role: "assistant", content: text }]);
612
+ }
613
+ }
614
+ });
615
+ if (!result.ok) {
616
+ if (result.paymentRequired) {
617
+ const question = fallbackQuestion ?? lastUserQuestion(baseMessages) ?? activeQuestion;
618
+ const quote2 = await fetchSageQuote({
619
+ ...apiOpts,
620
+ question,
621
+ history: baseMessages.slice(0, -1)
622
+ });
623
+ if (!mountedRef.current) return;
624
+ props.onQuote?.(quote2);
625
+ setQuoteResponse(quote2);
626
+ setActiveQuestion(question);
627
+ transition("payment_required", { quote: quote2.quote ?? null });
628
+ return;
629
+ }
630
+ throw new Error(result.error ?? "Sage chat failed.");
631
+ }
632
+ if (!mountedRef.current) return;
633
+ if (result.text && result.text !== text) {
634
+ setMessages([...baseMessages, { role: "assistant", content: result.text }]);
635
+ }
636
+ transition("idle");
637
+ }
638
+ function fail(err, setErrorPhase = true) {
639
+ const message = err instanceof Error ? err.message : "Sage request failed.";
640
+ setError(message);
641
+ if (setErrorPhase) transition("error", { error: message });
642
+ else emitStatus(phase, { error: message });
643
+ props.onError?.(err);
644
+ }
645
+ function transition(next, overrides = {}) {
646
+ setPhase(next);
647
+ props.onPhase?.(next);
648
+ emitStatus(next, overrides);
649
+ }
650
+ function emitStatus(nextPhase, overrides = {}) {
651
+ const has = (key) => Object.prototype.hasOwnProperty.call(overrides, key);
652
+ props.onStatus?.({
653
+ phase: nextPhase,
654
+ tier: has("tier") ? overrides.tier ?? null : tier,
655
+ quote: has("quote") ? overrides.quote ?? null : quoteResponse?.quote ?? null,
656
+ receipt: has("receipt") ? overrides.receipt ?? null : receipt,
657
+ receiptBundle: has("receiptBundle") ? overrides.receiptBundle ?? null : receiptBundle,
658
+ error: has("error") ? overrides.error ?? null : error,
659
+ messages: overrides.messages ?? messages,
660
+ activeQuestion: has("activeQuestion") ? overrides.activeQuestion ?? null : activeQuestion || null
661
+ });
662
+ }
663
+ const quote = quoteResponse?.quote;
664
+ const showPaymentPanel = quote && !receipt;
665
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className, style: { ...rootStyle2, ...style }, children: [
666
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { style: headerStyle2, children: [
667
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
668
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: eyebrowStyle, children: tenant?.label ?? "Ergo agent economy" }),
669
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: titleStyle, children: title })
670
+ ] }),
671
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: badgeStyle, children: tier ? tier.toUpperCase() : "SAGE" })
672
+ ] }),
673
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: messagesStyle, "aria-live": "polite", children: messages.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: emptyStyle2, children: "Free questions answer immediately. Premium questions return an Accord Note quote." }) : messages.map((message, index) => /* @__PURE__ */ jsxRuntime.jsx(
674
+ "div",
675
+ {
676
+ style: message.role === "user" ? userBubbleStyle : assistantBubbleStyle,
677
+ children: message.content || (message.role === "assistant" && phase === "streaming" ? "Thinking..." : "")
678
+ },
679
+ `${message.role}-${index}`
680
+ )) }),
681
+ showPaymentPanel ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: paymentStyle, children: [
682
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: paymentHeaderStyle, children: [
683
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Payment required" }),
684
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
685
+ quote.price,
686
+ " ERG testnet"
687
+ ] })
688
+ ] }),
689
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: helperStyle, children: [
690
+ paymentInstructions?.helperText ?? "Issue an Ergo testnet Note for this quote, then paste the created Note box id.",
691
+ paymentInstructions?.walletUrl ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
692
+ " ",
693
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: paymentInstructions.walletUrl, target: "_blank", rel: "noopener noreferrer", style: inlineLinkStyle, children: "Wallet guide" })
694
+ ] }) : null
695
+ ] }),
696
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Quote", value: quote.quoteId }),
697
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Receiver", value: quote.receiverAddress, copy: true }),
698
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Reserve box", value: quote.reserveBoxId, copy: true }),
699
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: "Task hash", value: quote.taskHash, copy: true }),
700
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: labelStyle2, children: [
701
+ paymentInstructions?.noteBoxLabel ?? "Note box id",
702
+ /* @__PURE__ */ jsxRuntime.jsx(
703
+ "input",
704
+ {
705
+ value: noteBoxId,
706
+ onChange: (e) => setNoteBoxId(e.currentTarget.value),
707
+ placeholder: "Paste 64-char Ergo box id",
708
+ style: inputStyle,
709
+ disabled: busy
710
+ }
711
+ )
712
+ ] }),
713
+ /* @__PURE__ */ jsxRuntime.jsx(
714
+ "button",
715
+ {
716
+ type: "button",
717
+ onClick: verifyAndContinue,
718
+ disabled: !noteBoxId.trim() || busy,
719
+ style: primaryButtonStyle,
720
+ children: phase === "verifying" ? "Verifying..." : "Verify payment"
721
+ }
722
+ )
723
+ ] }) : null,
724
+ receipt ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: receiptPanelStyle, children: [
725
+ /* @__PURE__ */ jsxRuntime.jsxs("a", { href: receipt.receiptUrl, target: "_blank", rel: "noopener noreferrer", style: receiptStyle, children: [
726
+ "Receipt: ",
727
+ shortId(receipt.receiptId),
728
+ receiptBundle ? ` \xB7 ${receiptBundle.completeness}` : ""
729
+ ] }),
730
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: receipt.receiptApiUrl, target: "_blank", rel: "noopener noreferrer", style: receiptApiStyle, children: "machine-readable JSON" })
731
+ ] }) : null,
732
+ error ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: errorStyle2, children: error }) : null,
733
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, style: formStyle, children: [
734
+ /* @__PURE__ */ jsxRuntime.jsx(
735
+ "input",
736
+ {
737
+ value: input,
738
+ onChange: (e) => setInput(e.currentTarget.value),
739
+ placeholder,
740
+ disabled: busy,
741
+ style: inputStyle
742
+ }
743
+ ),
744
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: !input.trim() || busy, style: sendButtonStyle, children: phase === "quoting" ? "Quoting..." : phase === "streaming" ? "Streaming..." : "Send" })
745
+ ] })
746
+ ] });
747
+ }
748
+ function Field({ label, value, copy = false }) {
749
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: fieldStyle, children: [
750
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: fieldLabelStyle, children: label }),
751
+ /* @__PURE__ */ jsxRuntime.jsx("code", { style: fieldValueStyle, children: value }),
752
+ copy ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: copyButtonStyle, onClick: () => copyText(value), children: "Copy" }) : null
753
+ ] });
754
+ }
755
+ function copyText(value) {
756
+ if (typeof navigator !== "undefined" && navigator.clipboard) {
757
+ void navigator.clipboard.writeText(value);
758
+ }
759
+ }
760
+ function shortId(value) {
761
+ return value.length > 18 ? `${value.slice(0, 10)}...${value.slice(-8)}` : value;
762
+ }
763
+ function lastUserQuestion(messages) {
764
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
765
+ const message = messages[i];
766
+ if (message?.role === "user" && message.content.trim()) return message.content.trim();
767
+ }
768
+ return null;
769
+ }
770
+ var rootStyle2 = {
771
+ background: "#070707",
772
+ color: "#f8fafc",
773
+ border: "1px solid rgba(255,255,255,.1)",
774
+ borderRadius: 8,
775
+ padding: 16,
776
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
777
+ boxSizing: "border-box",
778
+ width: "100%",
779
+ maxWidth: 520
780
+ };
781
+ var headerStyle2 = {
782
+ display: "flex",
783
+ justifyContent: "space-between",
784
+ alignItems: "flex-start",
785
+ gap: 12,
786
+ marginBottom: 12
787
+ };
788
+ var eyebrowStyle = {
789
+ color: "#fb923c",
790
+ fontSize: 11,
791
+ letterSpacing: ".16em",
792
+ textTransform: "uppercase",
793
+ marginBottom: 4
794
+ };
795
+ var titleStyle = {
796
+ fontSize: 18,
797
+ lineHeight: 1.2,
798
+ margin: 0
799
+ };
800
+ var badgeStyle = {
801
+ color: "#0f172a",
802
+ background: "#fdba74",
803
+ borderRadius: 4,
804
+ padding: "4px 7px",
805
+ fontSize: 10,
806
+ fontWeight: 800,
807
+ letterSpacing: ".12em"
808
+ };
809
+ var messagesStyle = {
810
+ minHeight: 180,
811
+ maxHeight: 360,
812
+ overflow: "auto",
813
+ display: "flex",
814
+ flexDirection: "column",
815
+ gap: 8,
816
+ padding: "12px 0"
817
+ };
818
+ var emptyStyle2 = {
819
+ color: "#94a3b8",
820
+ border: "1px dashed rgba(148,163,184,.28)",
821
+ borderRadius: 6,
822
+ padding: 12,
823
+ fontSize: 13
824
+ };
825
+ var bubbleBase = {
826
+ borderRadius: 6,
827
+ padding: "9px 10px",
828
+ fontSize: 14,
829
+ lineHeight: 1.45,
830
+ whiteSpace: "pre-wrap"
831
+ };
832
+ var userBubbleStyle = {
833
+ ...bubbleBase,
834
+ alignSelf: "flex-end",
835
+ maxWidth: "88%",
836
+ background: "#fb923c",
837
+ color: "#111827"
838
+ };
839
+ var assistantBubbleStyle = {
840
+ ...bubbleBase,
841
+ alignSelf: "flex-start",
842
+ maxWidth: "92%",
843
+ background: "rgba(255,255,255,.07)",
844
+ color: "#e5e7eb"
845
+ };
846
+ var paymentStyle = {
847
+ border: "1px solid rgba(251,146,60,.35)",
848
+ background: "rgba(251,146,60,.08)",
849
+ borderRadius: 8,
850
+ padding: 12,
851
+ display: "grid",
852
+ gap: 8
853
+ };
854
+ var paymentHeaderStyle = {
855
+ display: "flex",
856
+ justifyContent: "space-between",
857
+ color: "#fed7aa",
858
+ fontSize: 13
859
+ };
860
+ var helperStyle = {
861
+ color: "#fdba74",
862
+ fontSize: 12,
863
+ lineHeight: 1.45,
864
+ margin: 0
865
+ };
866
+ var inlineLinkStyle = {
867
+ color: "#67e8f9",
868
+ textDecoration: "none"
869
+ };
870
+ var labelStyle2 = {
871
+ display: "grid",
872
+ gap: 6,
873
+ color: "#cbd5e1",
874
+ fontSize: 12
875
+ };
876
+ var fieldStyle = {
877
+ display: "grid",
878
+ gridTemplateColumns: "minmax(70px, 88px) minmax(0, 1fr) auto",
879
+ alignItems: "center",
880
+ gap: 8,
881
+ fontSize: 12
882
+ };
883
+ var fieldLabelStyle = {
884
+ color: "#94a3b8"
885
+ };
886
+ var fieldValueStyle = {
887
+ color: "#f8fafc",
888
+ overflow: "hidden",
889
+ textOverflow: "ellipsis",
890
+ whiteSpace: "nowrap",
891
+ fontSize: 11
892
+ };
893
+ var copyButtonStyle = {
894
+ border: "1px solid rgba(255,255,255,.16)",
895
+ background: "rgba(255,255,255,.06)",
896
+ color: "#f8fafc",
897
+ borderRadius: 4,
898
+ padding: "4px 7px",
899
+ cursor: "pointer"
900
+ };
901
+ var formStyle = {
902
+ display: "grid",
903
+ gridTemplateColumns: "minmax(0, 1fr) auto",
904
+ gap: 8,
905
+ marginTop: 12
906
+ };
907
+ var inputStyle = {
908
+ width: "100%",
909
+ boxSizing: "border-box",
910
+ border: "1px solid rgba(255,255,255,.16)",
911
+ background: "#050505",
912
+ color: "#f8fafc",
913
+ borderRadius: 6,
914
+ padding: "10px 11px",
915
+ fontSize: 14
916
+ };
917
+ var sendButtonStyle = {
918
+ border: 0,
919
+ background: "#fb923c",
920
+ color: "#111827",
921
+ borderRadius: 6,
922
+ padding: "0 14px",
923
+ fontWeight: 800,
924
+ cursor: "pointer"
925
+ };
926
+ var primaryButtonStyle = {
927
+ ...sendButtonStyle,
928
+ padding: "10px 12px"
929
+ };
930
+ var receiptStyle = {
931
+ display: "inline-flex",
932
+ color: "#67e8f9",
933
+ fontSize: 13,
934
+ textDecoration: "none"
935
+ };
936
+ var receiptPanelStyle = {
937
+ display: "flex",
938
+ alignItems: "center",
939
+ flexWrap: "wrap",
940
+ gap: 10,
941
+ marginTop: 10,
942
+ border: "1px solid rgba(103,232,249,.22)",
943
+ background: "rgba(103,232,249,.07)",
944
+ borderRadius: 6,
945
+ padding: "9px 10px"
946
+ };
947
+ var receiptApiStyle = {
948
+ color: "#cbd5e1",
949
+ fontSize: 12,
950
+ textDecoration: "none"
951
+ };
952
+ var errorStyle2 = {
953
+ color: "#fecaca",
954
+ background: "rgba(239,68,68,.12)",
955
+ border: "1px solid rgba(239,68,68,.25)",
956
+ borderRadius: 6,
957
+ padding: 10,
958
+ marginTop: 10,
959
+ fontSize: 13
960
+ };
296
961
 
297
962
  exports.DEFAULT_API_BASE = DEFAULT_API_BASE;
298
963
  exports.DEFAULT_LIMIT = DEFAULT_LIMIT;
299
964
  exports.DEFAULT_REFRESH_MS = DEFAULT_REFRESH_MS;
300
965
  exports.SageActivityFeed = SageActivityFeed;
966
+ exports.SagePaymentWidget = SagePaymentWidget;
301
967
  //# sourceMappingURL=react.cjs.map
302
968
  //# sourceMappingURL=react.cjs.map