@smartspace/chat-ui 1.14.2-main.fb46d29 → 1.14.4-main.0af780f

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
@@ -1,12 +1,12 @@
1
1
  import MuiButton from '@mui/material/Button';
2
2
  import IconButton from '@mui/material/IconButton';
3
- import { Loader2, Check, X, Paperclip, ArrowBigUp, Minimize2, AlertTriangle, FileImage, FileVideo, FileAudio, FileArchive, FileCode, FileSpreadsheet, Presentation, FileText, ChevronUp, ExternalLink, Copy, Download } from 'lucide-react';
3
+ import { Loader2, Check, X, Paperclip, Square, ArrowBigUp, Minimize2, AlertTriangle, FileImage, FileVideo, FileAudio, FileArchive, FileCode, FileSpreadsheet, Presentation, FileText, ChevronUp, Download, Copy, Globe, ShieldAlert } from 'lucide-react';
4
4
  import * as React9 from 'react';
5
5
  import { createContext, forwardRef, useImperativeHandle, useRef, useState, useEffect, useMemo, useCallback, createElement, useContext } from 'react';
6
6
  import { createPortal } from 'react-dom';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
8
  import { useQuery, queryOptions, useQueryClient, useMutation, skipToken } from '@tanstack/react-query';
8
9
  import { toast } from 'sonner';
9
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
10
  import { Editor, rootCtx, defaultValueCtx, editorViewOptionsCtx, editorViewCtx, serializerCtx, parserCtx, SchemaReady, nodeViewCtx, markViewCtx, schemaCtx, prosePluginsCtx, nodesCtx } from '@milkdown/core';
11
11
  import { history } from '@milkdown/kit/plugin/history';
12
12
  import { clipboard } from '@milkdown/plugin-clipboard';
@@ -221,6 +221,131 @@ var saveFile = (blob, fileName) => {
221
221
  a.click();
222
222
  };
223
223
 
224
+ // src/shared/utils/threadId.ts
225
+ var NEW_THREAD_ID = "__new__";
226
+ function createThreadId() {
227
+ const cryptoObj = globalThis?.crypto;
228
+ return typeof cryptoObj?.randomUUID === "function" ? cryptoObj.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`;
229
+ }
230
+ var DRAFT_THREAD_PREFIX = "draft-";
231
+ var DRAFT_THREAD_STORAGE_KEY = "ss:draftThreadIds";
232
+ var draftThreadIds = /* @__PURE__ */ new Set();
233
+ var hydratedFromStorage = false;
234
+ function isBrowserStorageAvailable() {
235
+ try {
236
+ return typeof window !== "undefined" && !!window.sessionStorage;
237
+ } catch {
238
+ return false;
239
+ }
240
+ }
241
+ function hydrateFromStorageOnce() {
242
+ if (hydratedFromStorage) return;
243
+ hydratedFromStorage = true;
244
+ if (!isBrowserStorageAvailable()) return;
245
+ try {
246
+ const raw2 = window.sessionStorage.getItem(DRAFT_THREAD_STORAGE_KEY);
247
+ if (!raw2) return;
248
+ const parsed = JSON.parse(raw2);
249
+ if (!Array.isArray(parsed)) return;
250
+ for (const id of parsed) {
251
+ if (typeof id === "string" && id) draftThreadIds.add(id);
252
+ }
253
+ } catch {
254
+ }
255
+ }
256
+ function persistToStorage() {
257
+ if (!isBrowserStorageAvailable()) return;
258
+ try {
259
+ window.sessionStorage.setItem(
260
+ DRAFT_THREAD_STORAGE_KEY,
261
+ JSON.stringify(Array.from(draftThreadIds))
262
+ );
263
+ } catch {
264
+ }
265
+ }
266
+ function markDraftThreadId(threadId) {
267
+ if (!threadId) return;
268
+ hydrateFromStorageOnce();
269
+ draftThreadIds.add(threadId);
270
+ persistToStorage();
271
+ }
272
+ function unmarkDraftThreadId(threadId) {
273
+ if (!threadId) return;
274
+ hydrateFromStorageOnce();
275
+ draftThreadIds.delete(threadId);
276
+ persistToStorage();
277
+ }
278
+ function isDraftThreadId(threadId) {
279
+ if (!threadId) return false;
280
+ if (threadId.startsWith(DRAFT_THREAD_PREFIX)) return true;
281
+ hydrateFromStorageOnce();
282
+ return draftThreadIds.has(threadId);
283
+ }
284
+ function createDraftThreadId() {
285
+ const cryptoObj = globalThis?.crypto;
286
+ return typeof cryptoObj?.randomUUID === "function" ? cryptoObj.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`;
287
+ }
288
+
289
+ // src/domains/flowruns/queryKeys.ts
290
+ var flowRunsKeys = {
291
+ all: ["flowruns"],
292
+ variables: (flowRunId) => [...flowRunsKeys.all, "variables", { flowRunId }],
293
+ mutations: () => [...flowRunsKeys.all, "mutations"],
294
+ updateVariable: (flowRunId, variableName) => [
295
+ ...flowRunsKeys.mutations(),
296
+ "updateVariable",
297
+ { flowRunId, variableName }
298
+ ],
299
+ cancel: () => [...flowRunsKeys.mutations(), "cancel"]
300
+ };
301
+
302
+ // src/domains/flowruns/mutations.ts
303
+ function useCancelFlowRun() {
304
+ const service = useChatService();
305
+ return useMutation({
306
+ mutationKey: flowRunsKeys.cancel(),
307
+ mutationFn: async (flowRunId) => {
308
+ if (isDraftThreadId(flowRunId)) return;
309
+ await service.cancelFlowRun?.(flowRunId);
310
+ },
311
+ onError: (error) => {
312
+ console.error("Failed to cancel flow run:", error);
313
+ toast.error("Failed to stop the run");
314
+ }
315
+ });
316
+ }
317
+ function useUpdateFlowRunVariable() {
318
+ const qc = useQueryClient();
319
+ const service = useChatService();
320
+ return useMutation({
321
+ mutationKey: flowRunsKeys.updateVariable("", ""),
322
+ mutationFn: async ({
323
+ flowRunId,
324
+ variableName,
325
+ value
326
+ }) => {
327
+ if (isDraftThreadId(flowRunId)) return;
328
+ await service.updateFlowRunVariable({
329
+ flowRunId,
330
+ name: variableName,
331
+ value
332
+ });
333
+ },
334
+ onSuccess: (_data, variables) => {
335
+ if (variables?.flowRunId) {
336
+ qc.invalidateQueries({
337
+ queryKey: flowRunsKeys.variables(variables.flowRunId)
338
+ });
339
+ }
340
+ },
341
+ onError: (error) => {
342
+ console.error("Failed to update variable:", error);
343
+ toast.error("Failed to update variable");
344
+ throw error;
345
+ }
346
+ });
347
+ }
348
+
224
349
  // ../../node_modules/.pnpm/@milkdown+exception@7.20.0/node_modules/@milkdown/exception/lib/index.js
225
350
  var ErrorCode = /* @__PURE__ */ (function(ErrorCode2) {
226
351
  ErrorCode2["docTypeError"] = "docTypeError";
@@ -1827,116 +1952,6 @@ var Button = React9.forwardRef(
1827
1952
  }
1828
1953
  );
1829
1954
  Button.displayName = "Button";
1830
-
1831
- // src/shared/utils/threadId.ts
1832
- var NEW_THREAD_ID = "__new__";
1833
- function createThreadId() {
1834
- const cryptoObj = globalThis?.crypto;
1835
- return typeof cryptoObj?.randomUUID === "function" ? cryptoObj.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`;
1836
- }
1837
- var DRAFT_THREAD_PREFIX = "draft-";
1838
- var DRAFT_THREAD_STORAGE_KEY = "ss:draftThreadIds";
1839
- var draftThreadIds = /* @__PURE__ */ new Set();
1840
- var hydratedFromStorage = false;
1841
- function isBrowserStorageAvailable() {
1842
- try {
1843
- return typeof window !== "undefined" && !!window.sessionStorage;
1844
- } catch {
1845
- return false;
1846
- }
1847
- }
1848
- function hydrateFromStorageOnce() {
1849
- if (hydratedFromStorage) return;
1850
- hydratedFromStorage = true;
1851
- if (!isBrowserStorageAvailable()) return;
1852
- try {
1853
- const raw2 = window.sessionStorage.getItem(DRAFT_THREAD_STORAGE_KEY);
1854
- if (!raw2) return;
1855
- const parsed = JSON.parse(raw2);
1856
- if (!Array.isArray(parsed)) return;
1857
- for (const id of parsed) {
1858
- if (typeof id === "string" && id) draftThreadIds.add(id);
1859
- }
1860
- } catch {
1861
- }
1862
- }
1863
- function persistToStorage() {
1864
- if (!isBrowserStorageAvailable()) return;
1865
- try {
1866
- window.sessionStorage.setItem(
1867
- DRAFT_THREAD_STORAGE_KEY,
1868
- JSON.stringify(Array.from(draftThreadIds))
1869
- );
1870
- } catch {
1871
- }
1872
- }
1873
- function markDraftThreadId(threadId) {
1874
- if (!threadId) return;
1875
- hydrateFromStorageOnce();
1876
- draftThreadIds.add(threadId);
1877
- persistToStorage();
1878
- }
1879
- function unmarkDraftThreadId(threadId) {
1880
- if (!threadId) return;
1881
- hydrateFromStorageOnce();
1882
- draftThreadIds.delete(threadId);
1883
- persistToStorage();
1884
- }
1885
- function isDraftThreadId(threadId) {
1886
- if (!threadId) return false;
1887
- if (threadId.startsWith(DRAFT_THREAD_PREFIX)) return true;
1888
- hydrateFromStorageOnce();
1889
- return draftThreadIds.has(threadId);
1890
- }
1891
- function createDraftThreadId() {
1892
- const cryptoObj = globalThis?.crypto;
1893
- return typeof cryptoObj?.randomUUID === "function" ? cryptoObj.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`;
1894
- }
1895
-
1896
- // src/domains/flowruns/queryKeys.ts
1897
- var flowRunsKeys = {
1898
- all: ["flowruns"],
1899
- variables: (flowRunId) => [...flowRunsKeys.all, "variables", { flowRunId }],
1900
- mutations: () => [...flowRunsKeys.all, "mutations"],
1901
- updateVariable: (flowRunId, variableName) => [
1902
- ...flowRunsKeys.mutations(),
1903
- "updateVariable",
1904
- { flowRunId, variableName }
1905
- ]
1906
- };
1907
-
1908
- // src/domains/flowruns/mutations.ts
1909
- function useUpdateFlowRunVariable() {
1910
- const qc = useQueryClient();
1911
- const service = useChatService();
1912
- return useMutation({
1913
- mutationKey: flowRunsKeys.updateVariable("", ""),
1914
- mutationFn: async ({
1915
- flowRunId,
1916
- variableName,
1917
- value
1918
- }) => {
1919
- if (isDraftThreadId(flowRunId)) return;
1920
- await service.updateFlowRunVariable({
1921
- flowRunId,
1922
- name: variableName,
1923
- value
1924
- });
1925
- },
1926
- onSuccess: (_data, variables) => {
1927
- if (variables?.flowRunId) {
1928
- qc.invalidateQueries({
1929
- queryKey: flowRunsKeys.variables(variables.flowRunId)
1930
- });
1931
- }
1932
- },
1933
- onError: (error) => {
1934
- console.error("Failed to update variable:", error);
1935
- toast.error("Failed to update variable");
1936
- throw error;
1937
- }
1938
- });
1939
- }
1940
1955
  function useFlowRunVariables(flowRunId) {
1941
1956
  const service = useChatService();
1942
1957
  return useQuery({
@@ -4119,6 +4134,25 @@ function MessageComposer({
4119
4134
  workspaceId,
4120
4135
  threadId: isDraftThread ? void 0 : threadId
4121
4136
  });
4137
+ const chatService = useChatService();
4138
+ const {
4139
+ mutate: cancelMutate,
4140
+ isPending: cancelPending,
4141
+ isSuccess: cancelAccepted,
4142
+ isError: cancelErrored,
4143
+ reset: cancelReset
4144
+ } = useCancelFlowRun();
4145
+ const canStop = isSending && !!chatService.cancelFlowRun;
4146
+ const stopping = cancelPending || cancelAccepted;
4147
+ const handleStopRun = () => {
4148
+ if (stopping) return;
4149
+ cancelMutate(threadId);
4150
+ };
4151
+ useEffect(() => {
4152
+ if (!isSending && (cancelAccepted || cancelErrored)) {
4153
+ cancelReset();
4154
+ }
4155
+ }, [isSending, cancelAccepted, cancelErrored, cancelReset]);
4122
4156
  useEffect(() => {
4123
4157
  if (typeof window === "undefined") return;
4124
4158
  window.__ssDownloadFile = (id) => getFileBlobUrl(id);
@@ -4391,7 +4425,21 @@ function MessageComposer({
4391
4425
  )
4392
4426
  }
4393
4427
  ),
4394
- /* @__PURE__ */ jsx(
4428
+ canStop ? /* @__PURE__ */ jsx(
4429
+ IconButton,
4430
+ {
4431
+ onClick: handleStopRun,
4432
+ className: "h-10 w-10 rounded-full self-end",
4433
+ disabled: stopping,
4434
+ "aria-label": stopping ? "Stopping" : "Stop",
4435
+ children: /* @__PURE__ */ jsx(
4436
+ Square,
4437
+ {
4438
+ className: `h-4 w-4 fill-current ${stopping ? "animate-pulse" : ""}`
4439
+ }
4440
+ )
4441
+ }
4442
+ ) : /* @__PURE__ */ jsx(
4395
4443
  IconButton,
4396
4444
  {
4397
4445
  onClick: handleSendMessageAndClear,
@@ -4485,7 +4533,21 @@ function MessageComposer({
4485
4533
  )
4486
4534
  }
4487
4535
  ),
4488
- /* @__PURE__ */ jsx(
4536
+ canStop ? /* @__PURE__ */ jsx(
4537
+ IconButton,
4538
+ {
4539
+ onClick: handleStopRun,
4540
+ className: "h-10 w-10 rounded-full",
4541
+ disabled: stopping,
4542
+ "aria-label": stopping ? "Stopping" : "Stop",
4543
+ children: /* @__PURE__ */ jsx(
4544
+ Square,
4545
+ {
4546
+ className: `h-4 w-4 fill-current ${stopping ? "animate-pulse" : ""}`
4547
+ }
4548
+ )
4549
+ }
4550
+ ) : /* @__PURE__ */ jsx(
4489
4551
  IconButton,
4490
4552
  {
4491
4553
  onClick: handleSendMessageAndClear,
@@ -4514,7 +4576,24 @@ function MessageComposer({
4514
4576
  children: /* @__PURE__ */ jsx(Paperclip, { className: "h-5 w-5", strokeWidth: 2 })
4515
4577
  }
4516
4578
  ) }),
4517
- /* @__PURE__ */ jsx(
4579
+ canStop ? /* @__PURE__ */ jsx(
4580
+ Button,
4581
+ {
4582
+ onClick: handleStopRun,
4583
+ className: `text-xs h-7 px-4 py-1 ${stopping ? "opacity-50 cursor-not-allowed" : ""}`,
4584
+ disabled: stopping,
4585
+ "aria-label": stopping ? "Stopping" : "Stop",
4586
+ children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
4587
+ /* @__PURE__ */ jsx(
4588
+ Square,
4589
+ {
4590
+ className: `h-3 w-3 fill-current ${stopping ? "animate-pulse" : ""}`
4591
+ }
4592
+ ),
4593
+ stopping ? "Stopping\u2026" : "Stop"
4594
+ ] })
4595
+ }
4596
+ ) : /* @__PURE__ */ jsx(
4518
4597
  Button,
4519
4598
  {
4520
4599
  onClick: handleSendMessageAndClear,
@@ -19773,17 +19852,12 @@ function getSafeUrl(url) {
19773
19852
  return null;
19774
19853
  }
19775
19854
  }
19776
- function getDisplayName(source) {
19777
- if (source.file?.name) return source.file.name;
19778
- if (source.url) {
19779
- try {
19780
- const parsed = new URL(source.url);
19781
- return parsed.hostname + (parsed.pathname !== "/" ? parsed.pathname : "");
19782
- } catch {
19783
- return source.url;
19784
- }
19855
+ function getHost(url) {
19856
+ try {
19857
+ return new URL(url).hostname.replace(/^www\./, "");
19858
+ } catch {
19859
+ return url;
19785
19860
  }
19786
- return `Source ${source.index}`;
19787
19861
  }
19788
19862
  function isFileSource(s2) {
19789
19863
  return s2.sourceType === "File" /* File */ && !!s2.file;
@@ -19791,15 +19865,82 @@ function isFileSource(s2) {
19791
19865
  function isLinkSource(s2) {
19792
19866
  return (s2.sourceType === "URL" /* URL */ || s2.sourceType === "WebExternal" /* WebExternal */) && !!s2.url;
19793
19867
  }
19868
+ function UnverifiedBadge({
19869
+ attribution,
19870
+ compact = false
19871
+ }) {
19872
+ if (attribution !== "unsupported" /* Unsupported */) return null;
19873
+ return /* @__PURE__ */ jsxs(
19874
+ "span",
19875
+ {
19876
+ title: "This citation couldn't be verified against the source text \u2014 double-check it.",
19877
+ className: cn(
19878
+ "inline-flex items-center gap-0.5 rounded text-[10px] font-medium text-amber-600 dark:text-amber-400 flex-shrink-0",
19879
+ !compact && "px-1 py-px bg-amber-500/10"
19880
+ ),
19881
+ children: [
19882
+ /* @__PURE__ */ jsx(ShieldAlert, { className: "h-3 w-3" }),
19883
+ !compact && "Unverified"
19884
+ ]
19885
+ }
19886
+ );
19887
+ }
19888
+ function IndexChip({ index }) {
19889
+ return /* @__PURE__ */ jsx("span", { className: "flex h-4 min-w-[16px] flex-shrink-0 items-center justify-center rounded-full bg-muted px-1 text-[10px] font-medium tabular-nums text-muted-foreground", children: index });
19890
+ }
19891
+ function SourceFavicon({ host }) {
19892
+ const [failed, setFailed] = useState(false);
19893
+ if (failed) {
19894
+ return /* @__PURE__ */ jsx(Globe, { className: "h-4 w-4 flex-shrink-0 text-muted-foreground" });
19895
+ }
19896
+ return /* @__PURE__ */ jsx(
19897
+ "img",
19898
+ {
19899
+ src: `https://www.google.com/s2/favicons?domain=${encodeURIComponent(
19900
+ host
19901
+ )}&sz=64`,
19902
+ alt: "",
19903
+ width: 16,
19904
+ height: 16,
19905
+ loading: "lazy",
19906
+ onError: () => setFailed(true),
19907
+ className: "h-4 w-4 flex-shrink-0 rounded-sm"
19908
+ }
19909
+ );
19910
+ }
19911
+ function CitedQuote({ text: text6 }) {
19912
+ if (!text6) return null;
19913
+ return /* @__PURE__ */ jsxs("p", { className: "m-0 text-xs leading-snug text-muted-foreground", children: [
19914
+ "\u201C",
19915
+ text6,
19916
+ "\u201D"
19917
+ ] });
19918
+ }
19919
+ function SectionLabel({ children: children2 }) {
19920
+ return /* @__PURE__ */ jsx("p", { className: "m-0 px-0.5 pb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground", children: children2 });
19921
+ }
19922
+ var pillClasses = "inline-flex max-w-full items-center gap-1.5 rounded-md border border-border/60 bg-background px-2 py-1 text-xs font-medium text-foreground transition-colors hover:border-border hover:bg-muted/40";
19923
+ var expandedCardClasses = "w-full rounded-lg border border-border/60 bg-background p-2.5 flex flex-col gap-1.5";
19794
19924
  function ChatMessageSources({
19795
19925
  sources
19796
19926
  }) {
19797
19927
  const { workspaceId, threadId } = useChatContext();
19798
19928
  const { downloadFileMutation } = useFileMutations({ workspaceId, threadId });
19799
19929
  const [isExpanded, setIsExpanded] = useState(true);
19930
+ const [openIndexes, setOpenIndexes] = useState(
19931
+ /* @__PURE__ */ new Set()
19932
+ );
19933
+ const toggleOpen = (index) => setOpenIndexes((prev) => {
19934
+ const next2 = new Set(prev);
19935
+ if (next2.has(index)) next2.delete(index);
19936
+ else next2.add(index);
19937
+ return next2;
19938
+ });
19800
19939
  const displaySources = (sources ?? []).filter(
19801
19940
  (s2) => isFileSource(s2) || isLinkSource(s2)
19802
19941
  );
19942
+ const urlSources = displaySources.filter((s2) => !isFileSource(s2));
19943
+ const fileSources = displaySources.filter(isFileSource);
19803
19944
  if (displaySources.length === 0) return null;
19804
19945
  return /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-lg border border-border bg-muted/30 overflow-hidden", children: [
19805
19946
  /* @__PURE__ */ jsxs(
@@ -19825,72 +19966,148 @@ function ChatMessageSources({
19825
19966
  ]
19826
19967
  }
19827
19968
  ),
19828
- isExpanded && /* @__PURE__ */ jsx("div", { className: "bg-background px-3 py-0.5", children: /* @__PURE__ */ jsx(
19829
- "ul",
19830
- {
19831
- className: "list-none space-y-1 m-0 p-0",
19832
- style: { paddingLeft: 0, marginLeft: 0 },
19833
- children: displaySources.map((source) => {
19834
- const displayName = getDisplayName(source);
19835
- if (isFileSource(source)) {
19836
- const Icon = getFileIcon3(source.file.name);
19969
+ isExpanded && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2.5 p-2.5", children: [
19970
+ urlSources.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
19971
+ /* @__PURE__ */ jsx(SectionLabel, { children: "URLs" }),
19972
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: urlSources.map((source) => {
19973
+ const host = getHost(source.url ?? "");
19974
+ const safeHref = source.url ? getSafeUrl(source.url) : null;
19975
+ if (!openIndexes.has(source.index)) {
19837
19976
  return /* @__PURE__ */ jsxs(
19838
- "li",
19977
+ "button",
19839
19978
  {
19840
- className: "list-none text-sm text-foreground m-0 p-0 flex items-center gap-1.5",
19841
- style: { paddingLeft: 0, marginLeft: 0 },
19979
+ type: "button",
19980
+ "aria-expanded": false,
19981
+ title: source.url ?? host,
19982
+ onClick: () => toggleOpen(source.index),
19983
+ className: pillClasses,
19842
19984
  children: [
19843
- /* @__PURE__ */ jsxs("span", { children: [
19844
- `(${source.index})`,
19845
- " : "
19846
- ] }),
19847
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 text-muted-foreground flex-shrink-0" }),
19985
+ /* @__PURE__ */ jsx(SourceFavicon, { host }),
19986
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: host }),
19987
+ /* @__PURE__ */ jsx(
19988
+ UnverifiedBadge,
19989
+ {
19990
+ attribution: source.attribution,
19991
+ compact: true
19992
+ }
19993
+ ),
19994
+ /* @__PURE__ */ jsx(IndexChip, { index: source.index })
19995
+ ]
19996
+ },
19997
+ `url-${source.index}`
19998
+ );
19999
+ }
20000
+ return /* @__PURE__ */ jsxs(
20001
+ "div",
20002
+ {
20003
+ className: expandedCardClasses,
20004
+ children: [
20005
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
20006
+ /* @__PURE__ */ jsx(SourceFavicon, { host }),
20007
+ safeHref ? /* @__PURE__ */ jsx(
20008
+ "a",
20009
+ {
20010
+ href: safeHref,
20011
+ target: "_blank",
20012
+ rel: "noreferrer",
20013
+ className: "min-w-0 truncate text-sm font-medium text-foreground no-underline hover:underline underline-offset-2",
20014
+ children: host
20015
+ }
20016
+ ) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-sm font-medium text-foreground", children: host }),
20017
+ /* @__PURE__ */ jsx(UnverifiedBadge, { attribution: source.attribution }),
20018
+ /* @__PURE__ */ jsx(IndexChip, { index: source.index }),
19848
20019
  /* @__PURE__ */ jsx(
19849
20020
  "button",
19850
20021
  {
19851
20022
  type: "button",
19852
- title: displayName,
19853
- onClick: () => downloadFileMutation.mutate(source.file),
19854
- className: "text-foreground hover:bg-muted/50 rounded px-1 py-0.5 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed",
19855
- disabled: downloadFileMutation.isPending,
19856
- children: displayName
20023
+ "aria-expanded": true,
20024
+ "aria-label": "Collapse source",
20025
+ onClick: () => toggleOpen(source.index),
20026
+ className: "ml-auto rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground",
20027
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" })
19857
20028
  }
19858
20029
  )
20030
+ ] }),
20031
+ source.url && /* @__PURE__ */ jsx("p", { className: "m-0 truncate text-xs text-muted-foreground/80", children: source.url }),
20032
+ /* @__PURE__ */ jsx(CitedQuote, { text: source.citedText })
20033
+ ]
20034
+ },
20035
+ `url-${source.index}`
20036
+ );
20037
+ }) })
20038
+ ] }),
20039
+ fileSources.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
20040
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Files" }),
20041
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: fileSources.map((source) => {
20042
+ const Icon = getFileIcon3(source.file.name);
20043
+ if (!openIndexes.has(source.index)) {
20044
+ return /* @__PURE__ */ jsxs(
20045
+ "button",
20046
+ {
20047
+ type: "button",
20048
+ "aria-expanded": false,
20049
+ title: source.file.name,
20050
+ onClick: () => toggleOpen(source.index),
20051
+ className: pillClasses,
20052
+ children: [
20053
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 flex-shrink-0 text-muted-foreground" }),
20054
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: source.file.name }),
20055
+ /* @__PURE__ */ jsx(
20056
+ UnverifiedBadge,
20057
+ {
20058
+ attribution: source.attribution,
20059
+ compact: true
20060
+ }
20061
+ ),
20062
+ /* @__PURE__ */ jsx(IndexChip, { index: source.index })
19859
20063
  ]
19860
20064
  },
19861
20065
  `${source.file.id}-${source.index}`
19862
20066
  );
19863
20067
  }
19864
- const safeHref = source.url ? getSafeUrl(source.url) : null;
19865
20068
  return /* @__PURE__ */ jsxs(
19866
- "li",
20069
+ "div",
19867
20070
  {
19868
- className: "list-none text-sm text-foreground m-0 p-0 flex items-center gap-1.5",
19869
- style: { paddingLeft: 0, marginLeft: 0 },
20071
+ className: expandedCardClasses,
19870
20072
  children: [
19871
- /* @__PURE__ */ jsxs("span", { children: [
19872
- `(${source.index})`,
19873
- " : "
20073
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
20074
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 flex-shrink-0 text-muted-foreground" }),
20075
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-sm font-medium text-foreground", children: source.file.name }),
20076
+ /* @__PURE__ */ jsx(UnverifiedBadge, { attribution: source.attribution }),
20077
+ /* @__PURE__ */ jsx(IndexChip, { index: source.index }),
20078
+ /* @__PURE__ */ jsx(
20079
+ "button",
20080
+ {
20081
+ type: "button",
20082
+ "aria-expanded": true,
20083
+ "aria-label": "Collapse source",
20084
+ onClick: () => toggleOpen(source.index),
20085
+ className: "ml-auto rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground",
20086
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" })
20087
+ }
20088
+ )
19874
20089
  ] }),
19875
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5 text-muted-foreground flex-shrink-0" }),
19876
- safeHref ? /* @__PURE__ */ jsx(
19877
- "a",
20090
+ /* @__PURE__ */ jsx(CitedQuote, { text: source.citedText }),
20091
+ /* @__PURE__ */ jsxs(
20092
+ "button",
19878
20093
  {
19879
- title: source.url ?? displayName,
19880
- href: safeHref,
19881
- target: "_blank",
19882
- rel: "noreferrer",
19883
- className: "text-foreground hover:bg-muted/50 rounded px-1 py-0.5 underline underline-offset-2",
19884
- children: displayName
20094
+ type: "button",
20095
+ onClick: () => downloadFileMutation.mutate(source.file),
20096
+ disabled: downloadFileMutation.isPending,
20097
+ className: "inline-flex w-fit items-center gap-1 rounded-md border border-border/60 px-2 py-1 text-xs font-medium text-foreground transition-colors hover:bg-muted/40 disabled:cursor-not-allowed disabled:opacity-50",
20098
+ children: [
20099
+ /* @__PURE__ */ jsx(Download, { className: "h-3 w-3" }),
20100
+ "Download"
20101
+ ]
19885
20102
  }
19886
- ) : /* @__PURE__ */ jsx("span", { className: "text-foreground rounded px-1 py-0.5", children: displayName })
20103
+ )
19887
20104
  ]
19888
20105
  },
19889
- `url-${source.index}`
20106
+ `${source.file.id}-${source.index}`
19890
20107
  );
19891
- })
19892
- }
19893
- ) })
20108
+ }) })
20109
+ ] })
20110
+ ] })
19894
20111
  ] });
19895
20112
  }
19896
20113
  var MessageBubble = (props) => {
@@ -20037,7 +20254,14 @@ function pushContent(items, value) {
20037
20254
  return;
20038
20255
  }
20039
20256
  if (Array.isArray(value)) {
20040
- items.push(...value);
20257
+ const looksLikeContent = value.every(
20258
+ (it) => it != null && typeof it === "object" && ("text" in it || "image" in it)
20259
+ );
20260
+ if (looksLikeContent) {
20261
+ items.push(...value);
20262
+ } else {
20263
+ items.push({ text: "```json\n" + JSON.stringify(value, null, 2) + "\n```" });
20264
+ }
20041
20265
  return;
20042
20266
  }
20043
20267
  if (typeof value === "object") {
@@ -20112,25 +20336,28 @@ var MessageItem = ({
20112
20336
  let groupOpen = false;
20113
20337
  let keyCounter = 0;
20114
20338
  let lastStatusNode = null;
20339
+ const groupHasAnything = () => groupContent.length > 0 || groupFiles.length > 0 || groupSources.length > 0;
20115
20340
  const flush = (nextType) => {
20116
- bubbles.push(
20117
- /* @__PURE__ */ jsx(
20118
- MessageBubble,
20119
- {
20120
- createdBy: lastCreatedBy,
20121
- createdByUserId: lastCreatedByUserId,
20122
- createdAt: lastCreatedAt,
20123
- type: groupType,
20124
- content: groupContent,
20125
- files: groupFiles,
20126
- sources: groupSources,
20127
- chatbotName,
20128
- userOutput: null,
20129
- userInput: null
20130
- },
20131
- `bubble-${message.id ?? "msg"}-${keyCounter++}`
20132
- )
20133
- );
20341
+ if (groupHasAnything()) {
20342
+ bubbles.push(
20343
+ /* @__PURE__ */ jsx(
20344
+ MessageBubble,
20345
+ {
20346
+ createdBy: lastCreatedBy,
20347
+ createdByUserId: lastCreatedByUserId,
20348
+ createdAt: lastCreatedAt,
20349
+ type: groupType,
20350
+ content: groupContent,
20351
+ files: groupFiles,
20352
+ sources: groupSources,
20353
+ chatbotName,
20354
+ userOutput: null,
20355
+ userInput: null
20356
+ },
20357
+ `bubble-${message.id ?? "msg"}-${keyCounter++}`
20358
+ )
20359
+ );
20360
+ }
20134
20361
  groupContent = [];
20135
20362
  groupFiles = [];
20136
20363
  groupSources = [];
@@ -20163,6 +20390,9 @@ var MessageItem = ({
20163
20390
  case "prompt":
20164
20391
  case "response":
20165
20392
  case "content": {
20393
+ if (v.value === "") {
20394
+ continue;
20395
+ }
20166
20396
  lastStatusNode = null;
20167
20397
  if (groupContent.length > 0) flush(v.type);
20168
20398
  if (v.value == null) {
@@ -20217,7 +20447,7 @@ var MessageItem = ({
20217
20447
  }
20218
20448
  case "sources": {
20219
20449
  groupSources = coerceSources(v.value);
20220
- groupOpen = true;
20450
+ if (groupSources.length > 0) groupOpen = true;
20221
20451
  break;
20222
20452
  }
20223
20453
  default: {
@@ -20231,7 +20461,7 @@ var MessageItem = ({
20231
20461
  lastCreatedBy = v.createdBy;
20232
20462
  lastCreatedByUserId = v.createdByUserId;
20233
20463
  }
20234
- if (groupOpen) {
20464
+ if (groupOpen && groupHasAnything()) {
20235
20465
  bubbles.push(
20236
20466
  /* @__PURE__ */ jsx(
20237
20467
  MessageBubble,