@smartspace/chat-ui 1.13.1-dev.8f17e91 → 1.13.1-dev.90dbac9

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,13 +1,13 @@
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';
4
- import * as React8 from 'react';
3
+ import { Loader2, Check, X, Paperclip, ArrowBigUp, Minimize2, AlertTriangle, FileImage, FileVideo, FileAudio, FileArchive, FileCode, FileSpreadsheet, Presentation, FileText, ChevronUp, Download, Copy, Globe, ShieldAlert } from 'lucide-react';
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 { useQuery, queryOptions, useQueryClient, useMutation } from '@tanstack/react-query';
7
+ import { useQuery, queryOptions, useQueryClient, useMutation, skipToken } from '@tanstack/react-query';
8
8
  import { toast } from 'sonner';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
- import { Editor, rootCtx, defaultValueCtx, editorViewOptionsCtx, editorViewCtx, serializerCtx, SchemaReady, nodeViewCtx, markViewCtx, schemaCtx, prosePluginsCtx, nodesCtx } from '@milkdown/core';
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';
13
13
  import { listenerCtx, listener } from '@milkdown/plugin-listener';
@@ -127,6 +127,7 @@ var filesKeys = {
127
127
  var useFileMutations = (scope) => {
128
128
  const { workspaceId, threadId } = scope;
129
129
  const service = useChatService();
130
+ const queryClient = useQueryClient();
130
131
  const [uploadedFiles, setUploadedFiles] = useState([]);
131
132
  const [fileProgress, setFileProgress] = useState({});
132
133
  const clearUploadState = useCallback(() => {
@@ -185,11 +186,19 @@ var useFileMutations = (scope) => {
185
186
  status: uploadedFiles.some((f) => f.name === file.name) ? "done" : "uploading"
186
187
  }));
187
188
  const getFileBlobUrl = useCallback(
188
- async (id) => {
189
- const blob = await service.downloadFile(id, { workspaceId, threadId });
190
- return URL.createObjectURL(blob);
191
- },
192
- [service, workspaceId, threadId]
189
+ (id) => queryClient.fetchQuery({
190
+ queryKey: filesKeys.downloadBlob(id),
191
+ queryFn: async () => {
192
+ const blob = await service.downloadFile(id, {
193
+ workspaceId,
194
+ threadId
195
+ });
196
+ return URL.createObjectURL(blob);
197
+ },
198
+ staleTime: Infinity,
199
+ gcTime: Infinity
200
+ }),
201
+ [queryClient, service, workspaceId, threadId]
193
202
  );
194
203
  return {
195
204
  uploadFilesMutation,
@@ -415,6 +424,29 @@ var fileTag = $node("fileTag", () => ({
415
424
  var MAX_IFRAME_HEIGHT = 5e3;
416
425
  var HEIGHT_MESSAGE = "ss-html-preview-height";
417
426
  var ERROR_MESSAGE = "ss-html-preview-error";
427
+ var SNAPSHOT_REQUEST = "ss-html-preview-snapshot";
428
+ var SNAPSHOT_RESULT = "ss-html-preview-snapshot-result";
429
+ var SS_MARKDOWN_CLIPBOARD_TYPE = "web text/markdown";
430
+ var MARKDOWN_MARKER_PREFIX = "<!--ss-md:";
431
+ var MARKDOWN_MARKER_SUFFIX = "-->";
432
+ var MARKDOWN_MARKER_RE = /<!--ss-md:([A-Za-z0-9+/=]+)-->/;
433
+ function encodeMarkdownMarker(markdown) {
434
+ try {
435
+ const b64 = btoa(unescape(encodeURIComponent(markdown)));
436
+ return `${MARKDOWN_MARKER_PREFIX}${b64}${MARKDOWN_MARKER_SUFFIX}`;
437
+ } catch {
438
+ return "";
439
+ }
440
+ }
441
+ function extractMarkdownMarker(html4) {
442
+ const match = html4.match(MARKDOWN_MARKER_RE);
443
+ if (!match) return null;
444
+ try {
445
+ return decodeURIComponent(escape(atob(match[1])));
446
+ } catch {
447
+ return null;
448
+ }
449
+ }
418
450
  var HEIGHT_REPORTER_SCRIPT = `
419
451
  <script>(function(){
420
452
  try {
@@ -439,6 +471,48 @@ var HEIGHT_REPORTER_SCRIPT = `
439
471
  window.addEventListener('unhandledrejection', function(ev){
440
472
  reportError(ev && ev.reason && ev.reason.message);
441
473
  });
474
+ // Snapshot responder. The parent can't read this sandboxed (no
475
+ // allow-same-origin) document, so when it asks for a snapshot we rasterize
476
+ // our own <canvas> elements via toDataURL and post just the PNG strings
477
+ // back out \u2014 strings cross the sandbox boundary fine. Used by the message
478
+ // "smart copy" so client-side charts (Chart.js etc.) paste into Word as
479
+ // images instead of dead <canvas>/<script> source.
480
+ window.addEventListener('message', function(ev){
481
+ var d = ev && ev.data;
482
+ if (!d || d.type !== ${JSON.stringify(SNAPSHOT_REQUEST)}) return;
483
+ var images = [];
484
+ try {
485
+ var canvases = document.querySelectorAll('canvas');
486
+ for (var i = 0; i < canvases.length; i++) {
487
+ var c = canvases[i];
488
+ // Skip zero-area canvases (off-screen / not yet drawn).
489
+ if (!c.width || !c.height) continue;
490
+ try {
491
+ var rect = c.getBoundingClientRect();
492
+ images.push({
493
+ dataUrl: c.toDataURL('image/png'),
494
+ width: c.width,
495
+ height: c.height,
496
+ // On-screen CSS size, independent of devicePixelRatio scaling, so
497
+ // the pasted <img> renders at the chart's intended size rather than
498
+ // 2x on a retina display. Falls back to the pixel size.
499
+ cssWidth: Math.round(rect.width) || c.width,
500
+ cssHeight: Math.round(rect.height) || c.height
501
+ });
502
+ } catch (e) {
503
+ // Tainted canvas (cross-origin image drawn without CORS) \u2014 skip it;
504
+ // the host falls back to inlining the static HTML source.
505
+ }
506
+ }
507
+ } catch (e) {}
508
+ try {
509
+ parent.postMessage({
510
+ type: ${JSON.stringify(SNAPSHOT_RESULT)},
511
+ id: d.id,
512
+ images: images
513
+ }, '*');
514
+ } catch (e) {}
515
+ });
442
516
  var lastSent = -1;
443
517
  function send(){
444
518
  try {
@@ -524,6 +598,38 @@ async function copyText(text6) {
524
598
  return false;
525
599
  }
526
600
  }
601
+ var snapshotSeq = 0;
602
+ function snapshotIframe(iframe, timeoutMs = 1500) {
603
+ return new Promise((resolve) => {
604
+ const win = iframe.contentWindow;
605
+ if (!win || typeof window === "undefined") {
606
+ resolve([]);
607
+ return;
608
+ }
609
+ const id = `snap-${++snapshotSeq}`;
610
+ let settled = false;
611
+ const finish = (images) => {
612
+ if (settled) return;
613
+ settled = true;
614
+ window.removeEventListener("message", onMessage);
615
+ clearTimeout(timer);
616
+ resolve(images);
617
+ };
618
+ const onMessage = (event) => {
619
+ if (event.source !== win) return;
620
+ const data = event.data;
621
+ if (!data || data.type !== SNAPSHOT_RESULT || data.id !== id) return;
622
+ finish(Array.isArray(data.images) ? data.images : []);
623
+ };
624
+ const timer = setTimeout(() => finish([]), timeoutMs);
625
+ window.addEventListener("message", onMessage);
626
+ try {
627
+ win.postMessage({ type: SNAPSHOT_REQUEST, id }, "*");
628
+ } catch {
629
+ finish([]);
630
+ }
631
+ });
632
+ }
527
633
 
528
634
  // src/shared/markdown/extensions/htmlPreview.ts
529
635
  var PREVIEW_LANGUAGES = /* @__PURE__ */ new Set(["html"]);
@@ -747,7 +853,7 @@ var mention = $node("mention", () => ({
747
853
  match: (node2) => node2.type.name === "mention",
748
854
  runner: (state, node2) => {
749
855
  const { id, label } = node2.attrs;
750
- state.addNode("mention", void 0, `${id}|${label}`);
856
+ state.addNode("text", void 0, label || `@${id}`);
751
857
  }
752
858
  }
753
859
  }));
@@ -871,7 +977,7 @@ var ssImageView = $view(ssImageNode, (ctx) => (node2) => {
871
977
  removeBtn.type = "button";
872
978
  removeBtn.className = "ss-attach__remove";
873
979
  removeBtn.setAttribute("aria-label", "Remove image");
874
- removeBtn.textContent = "\xD7";
980
+ removeBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>';
875
981
  removeBtn.addEventListener("mousedown", (e) => {
876
982
  e.preventDefault();
877
983
  e.stopPropagation();
@@ -890,16 +996,19 @@ var ssImageView = $view(ssImageNode, (ctx) => (node2) => {
890
996
  }
891
997
  });
892
998
  img.style.background = "rgba(0,0,0,0.04)";
999
+ img.style.visibility = "hidden";
893
1000
  let hasSetRealSrc = false;
894
1001
  img.addEventListener("load", () => {
895
1002
  if (!hasSetRealSrc) return;
896
1003
  spinner.remove();
897
1004
  img.style.background = "";
1005
+ img.style.visibility = "";
898
1006
  });
899
1007
  img.addEventListener("error", () => {
900
1008
  if (!hasSetRealSrc) return;
901
1009
  spinner.remove();
902
1010
  img.style.background = "rgba(255,0,0,0.06)";
1011
+ img.style.visibility = "";
903
1012
  });
904
1013
  try {
905
1014
  const anyWin = window;
@@ -970,6 +1079,7 @@ function EditorInner({
970
1079
  const [_isDragging, setIsDragging] = useState(false);
971
1080
  const viewRef = useRef(null);
972
1081
  const serializerRef = useRef(null);
1082
+ const parserRef = useRef(null);
973
1083
  function guessImageExt(mime) {
974
1084
  const t = (mime || "").toLowerCase();
975
1085
  if (t === "image/jpeg") return "jpg";
@@ -1088,6 +1198,10 @@ function EditorInner({
1088
1198
  serializerRef.current = anyCtx.get(serializerCtx);
1089
1199
  } catch {
1090
1200
  }
1201
+ try {
1202
+ parserRef.current = anyCtx.get(parserCtx);
1203
+ } catch {
1204
+ }
1091
1205
  const handle2 = () => {
1092
1206
  try {
1093
1207
  if (enableMentions) updateMentionFromView();
@@ -1177,6 +1291,19 @@ function EditorInner({
1177
1291
  const tr = view.state.tr.replaceWith(from, to, node2);
1178
1292
  view.dispatch(tr.scrollIntoView());
1179
1293
  }
1294
+ function insertMarkdownAtSelection(view, markdown) {
1295
+ const parser = parserRef.current;
1296
+ if (!parser) return false;
1297
+ try {
1298
+ const doc = parser(markdown);
1299
+ if (!doc) return false;
1300
+ const slice = new Slice(doc.content, 0, 0);
1301
+ view.dispatch(view.state.tr.replaceSelection(slice).scrollIntoView());
1302
+ return true;
1303
+ } catch {
1304
+ return false;
1305
+ }
1306
+ }
1180
1307
  async function insertUploadedFilesIntoEditor(files) {
1181
1308
  const view = viewRef.current;
1182
1309
  if (!isEditable || !view) return false;
@@ -1449,6 +1576,17 @@ function EditorInner({
1449
1576
  },
1450
1577
  onPasteCapture: (e) => {
1451
1578
  try {
1579
+ const view = viewRef.current;
1580
+ if (isEditable && view) {
1581
+ const ssMarkdown = extractMarkdownMarker(
1582
+ e.clipboardData?.getData("text/html") ?? ""
1583
+ ) ?? e.clipboardData?.getData(SS_MARKDOWN_CLIPBOARD_TYPE);
1584
+ if (ssMarkdown && insertMarkdownAtSelection(view, ssMarkdown)) {
1585
+ e.preventDefault();
1586
+ e.stopPropagation();
1587
+ return;
1588
+ }
1589
+ }
1452
1590
  const items = e.clipboardData?.items;
1453
1591
  if (!items) return;
1454
1592
  const imageFiles = [];
@@ -1675,7 +1813,7 @@ var buttonVariants = cva(
1675
1813
  }
1676
1814
  }
1677
1815
  );
1678
- var Button = React8.forwardRef(
1816
+ var Button = React9.forwardRef(
1679
1817
  ({ className, variant, size, asChild = false, ...props }, ref) => {
1680
1818
  const Comp = asChild ? Slot : "button";
1681
1819
  return /* @__PURE__ */ jsx(
@@ -2890,6 +3028,128 @@ var modelIdRendererTester = rankWith(
2890
3028
  }
2891
3029
  );
2892
3030
  var ModelIdRendererControl = withJsonFormsControlProps(ModelIdRenderer);
3031
+ var NumberRenderer = ({
3032
+ data,
3033
+ handleChange,
3034
+ path: path2,
3035
+ label,
3036
+ description,
3037
+ errors,
3038
+ schema,
3039
+ uischema,
3040
+ visible,
3041
+ enabled,
3042
+ required
3043
+ }) => {
3044
+ const isInteger = schema?.type === "integer";
3045
+ const handleInputChange = useCallback(
3046
+ (event) => {
3047
+ const raw2 = event.target.value;
3048
+ if (raw2 === "") {
3049
+ handleChange(path2, void 0);
3050
+ return;
3051
+ }
3052
+ const parsed = isInteger ? parseInt(raw2, 10) : parseFloat(raw2);
3053
+ if (Number.isNaN(parsed)) {
3054
+ handleChange(path2, void 0);
3055
+ return;
3056
+ }
3057
+ handleChange(path2, parsed);
3058
+ },
3059
+ [handleChange, path2, isInteger]
3060
+ );
3061
+ if (!visible) return null;
3062
+ const readOnly = uischema?.access === "Read";
3063
+ const isDisabled = !enabled || readOnly;
3064
+ const hasError = !!errors && errors.length > 0;
3065
+ const fieldSchema = schema;
3066
+ const min = fieldSchema?.minimum;
3067
+ const max = fieldSchema?.maximum;
3068
+ const step = isInteger ? 1 : fieldSchema?.multipleOf ?? "any";
3069
+ return /* @__PURE__ */ jsxs(
3070
+ "div",
3071
+ {
3072
+ className: "ss-jsonforms-field ss-jsonforms-number",
3073
+ style: {
3074
+ display: "inline-flex",
3075
+ flexDirection: "row",
3076
+ alignItems: "center",
3077
+ gap: 8,
3078
+ minHeight: "40px"
3079
+ },
3080
+ children: [
3081
+ label && /* @__PURE__ */ jsxs(
3082
+ "label",
3083
+ {
3084
+ htmlFor: `number-${path2}`,
3085
+ style: {
3086
+ color: hasError ? "#ef4444" : "#475569",
3087
+ fontSize: "0.875rem",
3088
+ fontWeight: 500,
3089
+ whiteSpace: "nowrap",
3090
+ lineHeight: "24px"
3091
+ },
3092
+ children: [
3093
+ label,
3094
+ required && /* @__PURE__ */ jsx("span", { style: { color: "#ef4444", marginLeft: "0.25rem" }, children: "*" })
3095
+ ]
3096
+ }
3097
+ ),
3098
+ /* @__PURE__ */ jsx(
3099
+ "input",
3100
+ {
3101
+ id: `number-${path2}`,
3102
+ type: "number",
3103
+ value: data ?? "",
3104
+ onChange: handleInputChange,
3105
+ disabled: isDisabled,
3106
+ min,
3107
+ max,
3108
+ step,
3109
+ style: {
3110
+ width: "80px",
3111
+ height: "24px",
3112
+ padding: "0 0.5rem",
3113
+ border: hasError ? "2px solid #ef4444" : "1px solid #d1d5db",
3114
+ borderRadius: "6px",
3115
+ fontSize: "0.875rem",
3116
+ lineHeight: "24px",
3117
+ fontFamily: "inherit",
3118
+ backgroundColor: isDisabled ? "#f9fafb" : "#ffffff",
3119
+ color: isDisabled ? "#9ca3af" : "#111827",
3120
+ outline: "none",
3121
+ boxSizing: "border-box"
3122
+ }
3123
+ }
3124
+ ),
3125
+ hasError && /* @__PURE__ */ jsx(
3126
+ "div",
3127
+ {
3128
+ style: {
3129
+ color: "#ef4444",
3130
+ fontSize: "0.75rem"
3131
+ },
3132
+ children: errors
3133
+ }
3134
+ )
3135
+ ]
3136
+ }
3137
+ );
3138
+ };
3139
+ var numberRendererTester = rankWith(
3140
+ 40,
3141
+ (uischema, schema) => {
3142
+ if (uischema.type !== "Control") return false;
3143
+ const propertyPath = uischema.scope.replace(
3144
+ "#/properties/",
3145
+ ""
3146
+ );
3147
+ const fieldSchema = schema?.properties?.[propertyPath];
3148
+ if (!fieldSchema) return false;
3149
+ return fieldSchema.type === "integer" || fieldSchema.type === "number";
3150
+ }
3151
+ );
3152
+ var NumberRendererControl = withJsonFormsControlProps(NumberRenderer);
2893
3153
  var TextareaRenderer = ({
2894
3154
  data,
2895
3155
  handleChange,
@@ -3059,6 +3319,7 @@ var renderers = [
3059
3319
  { tester: modelIdRendererTester, renderer: ModelIdRendererControl },
3060
3320
  { tester: booleanRendererTester, renderer: BooleanRendererControl },
3061
3321
  { tester: dropdownRendererTester, renderer: DropdownRendererControl },
3322
+ { tester: numberRendererTester, renderer: NumberRendererControl },
3062
3323
  { tester: textareaRendererTester, renderer: TextareaRendererControl },
3063
3324
  ...vanillaRenderers,
3064
3325
  { tester: jsonEditorTester, renderer: JsonEditorRendererControl }
@@ -3112,26 +3373,26 @@ function useChatVariablesFormVm({
3112
3373
  const { mutate: updateVariableMutation } = useUpdateFlowRunVariable();
3113
3374
  const querySettled = !isLoading && (threadVars !== void 0 || isError);
3114
3375
  const shouldUseDefaults = isError || threadVars && Object.keys(threadVars).length === 0;
3115
- const built = React8.useMemo(() => {
3376
+ const built = React9.useMemo(() => {
3116
3377
  return buildSimpleSchemaAndUi(
3117
3378
  workspace.variables,
3118
3379
  threadVars,
3119
3380
  shouldUseDefaults ?? false
3120
3381
  );
3121
3382
  }, [workspace.variables, threadVars, shouldUseDefaults]);
3122
- const [data, setData] = React8.useState(null);
3123
- React8.useEffect(() => {
3383
+ const [data, setData] = React9.useState(null);
3384
+ React9.useEffect(() => {
3124
3385
  if (querySettled) {
3125
3386
  setData(built.initialData);
3126
3387
  setVariables(built.initialData);
3127
3388
  }
3128
3389
  }, [querySettled, built.initialData, setVariables]);
3129
- const ajv = React8.useMemo(() => createAjv({ useDefaults: false }), []);
3130
- const prevRef = React8.useRef(null);
3131
- React8.useEffect(() => {
3390
+ const ajv = React9.useMemo(() => createAjv({ useDefaults: false }), []);
3391
+ const prevRef = React9.useRef(null);
3392
+ React9.useEffect(() => {
3132
3393
  prevRef.current = data;
3133
3394
  }, [data]);
3134
- const onChange = React8.useCallback(
3395
+ const onChange = React9.useCallback(
3135
3396
  ({ data: next2 }) => {
3136
3397
  if (prevRef.current && !isDraftThreadId(threadId)) {
3137
3398
  const keys2 = Object.keys(workspace.variables || {});
@@ -3152,7 +3413,7 @@ function useChatVariablesFormVm({
3152
3413
  },
3153
3414
  [workspace.variables, setVariables, updateVariableMutation, threadId]
3154
3415
  );
3155
- const config = React8.useMemo(
3416
+ const config = React9.useMemo(
3156
3417
  () => ({
3157
3418
  restrict: true,
3158
3419
  trim: false,
@@ -3221,7 +3482,20 @@ var threadsKeys = {
3221
3482
  };
3222
3483
 
3223
3484
  // src/domains/threads/cache.ts
3485
+ function isStaleSummary(incoming, existing) {
3486
+ if (!existing) return false;
3487
+ if (typeof existing.summaryEmittedAt !== "number") return false;
3488
+ if (typeof incoming.summaryEmittedAt !== "number") return false;
3489
+ if (incoming.summaryEmittedAt >= existing.summaryEmittedAt) return false;
3490
+ return existing.isFlowRunning === false && incoming.isFlowRunning === true;
3491
+ }
3224
3492
  function applyThreadToCache(qc, thread) {
3493
+ const existingDetail = qc.getQueryData(
3494
+ threadsKeys.detail(thread.workSpaceId, thread.id)
3495
+ );
3496
+ if (isStaleSummary(thread, existingDetail)) {
3497
+ return false;
3498
+ }
3225
3499
  qc.setQueryData(
3226
3500
  threadsKeys.detail(thread.workSpaceId, thread.id),
3227
3501
  (old) => ({ ...old ?? thread, ...thread })
@@ -3242,6 +3516,7 @@ function applyThreadToCache(qc, thread) {
3242
3516
  if (!page?.data) return page;
3243
3517
  const idx2 = page.data.findIndex((t) => t.id === thread.id);
3244
3518
  if (idx2 === -1) return page;
3519
+ if (isStaleSummary(thread, page.data[idx2])) return page;
3245
3520
  changed = true;
3246
3521
  foundInList = true;
3247
3522
  const nextData2 = page.data.slice();
@@ -3254,6 +3529,7 @@ function applyThreadToCache(qc, thread) {
3254
3529
  if (!list2.data) return old;
3255
3530
  const idx = list2.data.findIndex((t) => t.id === thread.id);
3256
3531
  if (idx === -1) return old;
3532
+ if (isStaleSummary(thread, list2.data[idx])) return old;
3257
3533
  foundInList = true;
3258
3534
  const nextData = list2.data.slice();
3259
3535
  nextData[idx] = { ...nextData[idx], ...thread };
@@ -3323,36 +3599,40 @@ var {
3323
3599
  messageThreadsGetMessageThreadWorkspacesWorkspaceIdMessagethreadsIdResponse: threadResponseSchema
3324
3600
  } = ChatZod;
3325
3601
  function mapThreadDtoToModel(dto) {
3602
+ const lastUpdatedAt = utcDate(dto.lastUpdatedAt);
3326
3603
  return {
3327
3604
  id: dto.id,
3328
3605
  createdAt: utcDate(dto.createdAt),
3329
3606
  createdBy: dto.createdBy ?? "",
3330
3607
  createdByUserId: dto.createdByUserId,
3331
3608
  isFlowRunning: dto.isFlowRunning,
3332
- lastUpdatedAt: utcDate(dto.lastUpdatedAt),
3609
+ lastUpdatedAt,
3333
3610
  lastUpdatedByUserId: dto.lastUpdatedByUserId,
3334
3611
  name: dto.name ?? "",
3335
3612
  totalMessages: dto.totalMessages,
3336
3613
  pinned: dto.favorited,
3337
- workSpaceId: dto.workSpaceId
3614
+ workSpaceId: dto.workSpaceId,
3615
+ summaryEmittedAt: lastUpdatedAt.getTime()
3338
3616
  };
3339
3617
  }
3340
3618
  function mapThreadsResponseDtoToModel(dto) {
3341
3619
  return { data: dto.data.map(mapThreadDtoToModel), total: dto.total };
3342
3620
  }
3343
3621
  function mapSignalRThreadSummaryToModel(summary) {
3622
+ const lastUpdatedAt = utcDate(summary.lastUpdatedAt);
3344
3623
  return {
3345
3624
  id: summary.id,
3346
3625
  createdAt: utcDate(summary.createdAt),
3347
3626
  createdBy: summary.createdBy ?? "",
3348
3627
  createdByUserId: summary.createdByUserId,
3349
3628
  isFlowRunning: summary.isFlowRunning,
3350
- lastUpdatedAt: utcDate(summary.lastUpdatedAt),
3629
+ lastUpdatedAt,
3351
3630
  lastUpdatedByUserId: summary.lastUpdatedByUserId,
3352
3631
  name: summary.name ?? "",
3353
3632
  totalMessages: summary.totalMessages,
3354
3633
  pinned: summary.favorited,
3355
- workSpaceId: summary.workSpaceId
3634
+ workSpaceId: summary.workSpaceId,
3635
+ summaryEmittedAt: lastUpdatedAt.getTime()
3356
3636
  };
3357
3637
  }
3358
3638
  var threadDetailOptions = ({
@@ -3406,11 +3686,12 @@ var useThread = ({
3406
3686
  });
3407
3687
  };
3408
3688
  var useThreadIsRunning = (workspaceId, threadId) => {
3409
- const { data: thread } = useThread({
3410
- workspaceId: workspaceId ?? "",
3411
- threadId: threadId ?? "",
3412
- enabled: !!workspaceId && !!threadId
3689
+ const queryClient = useQueryClient();
3690
+ const { data: detailThread } = useQuery({
3691
+ queryKey: threadsKeys.detail(workspaceId ?? "", threadId ?? ""),
3692
+ queryFn: skipToken
3413
3693
  });
3694
+ const listThread = workspaceId && threadId ? getThreadPlaceholderFromListCache(queryClient, workspaceId, threadId) : void 0;
3414
3695
  const { data: optimistic } = useQuery({
3415
3696
  queryKey: threadsKeys.optimisticRunning(threadId ?? ""),
3416
3697
  queryFn: () => false,
@@ -3418,9 +3699,15 @@ var useThreadIsRunning = (workspaceId, threadId) => {
3418
3699
  staleTime: Infinity,
3419
3700
  enabled: !!threadId
3420
3701
  });
3421
- return !!optimistic || !!thread?.isFlowRunning;
3702
+ return !!optimistic || !!(detailThread ?? listThread)?.isFlowRunning;
3422
3703
  };
3423
3704
 
3705
+ // src/shared/utils/randomUUID.ts
3706
+ function randomUUID() {
3707
+ const cryptoObj = globalThis?.crypto;
3708
+ return typeof cryptoObj?.randomUUID === "function" ? cryptoObj.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`;
3709
+ }
3710
+
3424
3711
  // src/domains/messages/enums.ts
3425
3712
  var MessageValueType = /* @__PURE__ */ ((MessageValueType2) => {
3426
3713
  MessageValueType2["OUTPUT"] = "Output";
@@ -3444,6 +3731,15 @@ var messagesMutationsKeys = {
3444
3731
  };
3445
3732
 
3446
3733
  // src/domains/messages/mutations.ts
3734
+ function reconcileWithMessage(old, incoming, onDuplicate = "keep-existing") {
3735
+ const stable = old.filter((m) => !m.optimistic);
3736
+ const idx = stable.findIndex((m) => m.id === incoming.id);
3737
+ if (idx === -1) return [...stable, incoming];
3738
+ if (onDuplicate === "keep-existing") return stable;
3739
+ const copy = stable.slice();
3740
+ copy[idx] = incoming;
3741
+ return copy;
3742
+ }
3447
3743
  function useSendMessage() {
3448
3744
  const qc = useQueryClient();
3449
3745
  const { userId, displayName: userName } = useChatIdentity();
@@ -3459,10 +3755,10 @@ function useSendMessage() {
3459
3755
  if (!threadId) throw new Error("Thread ID is required");
3460
3756
  if (!workspaceId) throw new Error("Workspace ID is required");
3461
3757
  const optimistic = {
3462
- id: `temp-${crypto.randomUUID()}`,
3758
+ id: `temp-${randomUUID()}`,
3463
3759
  values: [
3464
3760
  {
3465
- id: `temp-${crypto.randomUUID()}-prompt`,
3761
+ id: `temp-${randomUUID()}-prompt`,
3466
3762
  type: "Input" /* INPUT */,
3467
3763
  name: "prompt",
3468
3764
  value: contentList,
@@ -3473,7 +3769,7 @@ function useSendMessage() {
3473
3769
  },
3474
3770
  ...files?.length ? [
3475
3771
  {
3476
- id: `temp-${crypto.randomUUID()}-files`,
3772
+ id: `temp-${randomUUID()}-files`,
3477
3773
  type: "Input" /* INPUT */,
3478
3774
  name: "files",
3479
3775
  value: files,
@@ -3485,7 +3781,7 @@ function useSendMessage() {
3485
3781
  ] : [],
3486
3782
  ...variables && Object.keys(variables).length ? [
3487
3783
  {
3488
- id: `temp-${crypto.randomUUID()}-vars`,
3784
+ id: `temp-${randomUUID()}-vars`,
3489
3785
  type: "Input" /* INPUT */,
3490
3786
  name: "variables",
3491
3787
  value: variables,
@@ -3527,7 +3823,10 @@ function useSendMessage() {
3527
3823
  toast.error("There was an error posting your message");
3528
3824
  throw err;
3529
3825
  }
3530
- qc.setQueryData(messagesKeys.list(threadId), [realMessage]);
3826
+ qc.setQueryData(
3827
+ messagesKeys.list(threadId),
3828
+ (old = []) => reconcileWithMessage(old, realMessage)
3829
+ );
3531
3830
  qc.setQueryData(
3532
3831
  threadsKeys.detail(workspaceId, threadId),
3533
3832
  (old) => old ? { ...old, isFlowRunning: true } : old
@@ -3551,8 +3850,9 @@ function useAddInputToMessage() {
3551
3850
  const { userId, displayName: userName } = useChatIdentity();
3552
3851
  const service = useChatService();
3553
3852
  const addInputToMessageMutation = useMutation({
3554
- mutationFn: async ({ threadId, messageId, name, value, channels }) => {
3555
- if (!threadId) throw new Error("Thread ID is required");
3853
+ onMutate: async ({ threadId, messageId, name, value, channels }) => {
3854
+ await qc.cancelQueries({ queryKey: messagesKeys.list(threadId) });
3855
+ const previousMessages = qc.getQueryData(messagesKeys.list(threadId)) ?? [];
3556
3856
  qc.setQueryData(
3557
3857
  messagesKeys.list(threadId),
3558
3858
  (old = []) => old.map(
@@ -3561,7 +3861,7 @@ function useAddInputToMessage() {
3561
3861
  values: [
3562
3862
  ...m.values ?? [],
3563
3863
  {
3564
- id: `temp-${Date.now()}-add`,
3864
+ id: `temp-${randomUUID()}-add`,
3565
3865
  type: "Input" /* INPUT */,
3566
3866
  name,
3567
3867
  value,
@@ -3574,7 +3874,10 @@ function useAddInputToMessage() {
3574
3874
  } : m
3575
3875
  )
3576
3876
  );
3577
- await qc.cancelQueries({ queryKey: messagesKeys.list(threadId) });
3877
+ return { previousMessages };
3878
+ },
3879
+ mutationFn: async ({ threadId, messageId, name, value, channels }) => {
3880
+ if (!threadId) throw new Error("Thread ID is required");
3578
3881
  return await service.addInputToMessage({
3579
3882
  messageId,
3580
3883
  name,
@@ -3583,20 +3886,18 @@ function useAddInputToMessage() {
3583
3886
  });
3584
3887
  },
3585
3888
  onSuccess: (message, { threadId }) => {
3586
- qc.setQueryData(messagesKeys.list(threadId), (old = []) => {
3587
- const stable = old.filter((x) => !x.optimistic);
3588
- const idx = stable.findIndex((x) => x.id === message.id);
3589
- if (idx === -1) return [...stable, message];
3590
- const copy = stable.slice();
3591
- copy[idx] = message;
3592
- return copy;
3593
- });
3594
- },
3595
- onError: (_e, { threadId }) => {
3596
3889
  qc.setQueryData(
3597
3890
  messagesKeys.list(threadId),
3598
- (old = []) => old.filter((m) => !m.optimistic)
3891
+ (old = []) => reconcileWithMessage(old, message, "replace")
3599
3892
  );
3893
+ },
3894
+ onError: (_e, { threadId }, context) => {
3895
+ if (context) {
3896
+ qc.setQueryData(
3897
+ messagesKeys.list(threadId),
3898
+ context.previousMessages
3899
+ );
3900
+ }
3600
3901
  toast.error("There was an error posting your form input");
3601
3902
  },
3602
3903
  retry: false
@@ -18545,17 +18846,29 @@ function CodeBlock({ language, source }) {
18545
18846
  /* @__PURE__ */ jsx("pre", { ...language ? { "data-language": language } : {}, children: /* @__PURE__ */ jsx("code", { className: codeClass, children: source }) })
18546
18847
  ] });
18547
18848
  }
18849
+ var STREAM_SETTLE_MS = 250;
18548
18850
  function HtmlPreview({ source }) {
18549
18851
  const [showingPreview, setShowingPreview] = useState(true);
18550
18852
  const [copyLabel, setCopyLabel] = useState(
18551
18853
  "Copy"
18552
18854
  );
18553
18855
  const [iframeHeight, setIframeHeight] = useState(null);
18856
+ const [settledSource, setSettledSource] = useState(source);
18857
+ const [measured, setMeasured] = useState(false);
18858
+ const loading = showingPreview && (source !== settledSource || !measured);
18554
18859
  const iframeRef = useRef(null);
18555
18860
  const rafIdRef = useRef(null);
18556
18861
  const pendingHeightRef = useRef(null);
18557
18862
  const copyResetTimerRef = useRef(null);
18558
- const srcdoc = injectHeightReporter(source);
18863
+ const srcdoc = injectHeightReporter(settledSource);
18864
+ useEffect(() => {
18865
+ if (source === settledSource) return;
18866
+ const id = setTimeout(() => setSettledSource(source), STREAM_SETTLE_MS);
18867
+ return () => clearTimeout(id);
18868
+ }, [source, settledSource]);
18869
+ useEffect(() => {
18870
+ setMeasured(false);
18871
+ }, [settledSource]);
18559
18872
  useEffect(() => {
18560
18873
  ensureGlobalListener();
18561
18874
  }, []);
@@ -18570,6 +18883,7 @@ function HtmlPreview({ source }) {
18570
18883
  pendingHeightRef.current = null;
18571
18884
  if (next2 <= 0) return;
18572
18885
  setIframeHeight(next2);
18886
+ setMeasured(true);
18573
18887
  };
18574
18888
  const scheduleHeight = (height) => {
18575
18889
  pendingHeightRef.current = height;
@@ -18661,19 +18975,35 @@ function HtmlPreview({ source }) {
18661
18975
  )
18662
18976
  ] })
18663
18977
  ] }),
18664
- /* @__PURE__ */ jsx(
18665
- "iframe",
18978
+ /* @__PURE__ */ jsxs(
18979
+ "div",
18666
18980
  {
18667
- ref: iframeRef,
18668
- className: "ss-code-block__iframe",
18669
- sandbox: "allow-scripts",
18670
- loading: "lazy",
18671
- title: "HTML preview",
18672
- srcDoc: srcdoc,
18981
+ className: "ss-code-block__preview",
18673
18982
  style: {
18674
18983
  display: showingPreview ? "block" : "none",
18675
- ...iframeHeight != null ? { height: `${iframeHeight}px` } : {}
18676
- }
18984
+ ...loading ? { minHeight: 180 } : {}
18985
+ },
18986
+ children: [
18987
+ loading && /* @__PURE__ */ jsxs("div", { className: "ss-code-block__loading", children: [
18988
+ /* @__PURE__ */ jsx("div", { className: "ss-code-block__spinner" }),
18989
+ /* @__PURE__ */ jsx("span", { children: "Rendering preview\u2026" })
18990
+ ] }),
18991
+ /* @__PURE__ */ jsx(
18992
+ "iframe",
18993
+ {
18994
+ ref: iframeRef,
18995
+ className: "ss-code-block__iframe",
18996
+ sandbox: "allow-scripts",
18997
+ loading: "lazy",
18998
+ title: "HTML preview",
18999
+ srcDoc: srcdoc,
19000
+ style: {
19001
+ opacity: loading ? 0 : 1,
19002
+ ...loading ? { position: "absolute", inset: 0, height: "100%" } : iframeHeight != null ? { height: `${iframeHeight}px` } : {}
19003
+ }
19004
+ }
19005
+ )
19006
+ ]
18677
19007
  }
18678
19008
  ),
18679
19009
  /* @__PURE__ */ jsx(
@@ -18760,7 +19090,8 @@ function SsImage(props) {
18760
19090
  alt: alt ?? "",
18761
19091
  title,
18762
19092
  width: finalWidth,
18763
- height: finalHeight
19093
+ height: finalHeight,
19094
+ style: !resolvedSrc && !errored ? { visibility: "hidden" } : void 0
18764
19095
  }
18765
19096
  )
18766
19097
  ]
@@ -19036,7 +19367,7 @@ function getAvatarColour(name) {
19036
19367
  const textColor = brightness > 128 ? "#000000" : "#FFFFFF";
19037
19368
  return { backgroundColor, textColor };
19038
19369
  }
19039
- var Avatar = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
19370
+ var Avatar = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
19040
19371
  "div",
19041
19372
  {
19042
19373
  ref,
@@ -19048,7 +19379,7 @@ var Avatar = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
19048
19379
  }
19049
19380
  ));
19050
19381
  Avatar.displayName = "Avatar";
19051
- var AvatarImage = React8.forwardRef(({ className, alt, src, children: children2, ...props }, _ref) => /* @__PURE__ */ jsx(
19382
+ var AvatarImage = React9.forwardRef(({ className, alt, src, children: children2, ...props }, _ref) => /* @__PURE__ */ jsx(
19052
19383
  MuiAvatar,
19053
19384
  {
19054
19385
  className: cn("aspect-square h-full w-full", className),
@@ -19059,7 +19390,7 @@ var AvatarImage = React8.forwardRef(({ className, alt, src, children: children2,
19059
19390
  }
19060
19391
  ));
19061
19392
  AvatarImage.displayName = "AvatarImage";
19062
- var AvatarFallback = React8.forwardRef(
19393
+ var AvatarFallback = React9.forwardRef(
19063
19394
  ({ className, colored = true, ...props }, ref) => {
19064
19395
  const childText = String(props.children ?? "");
19065
19396
  const colours = colored ? getAvatarColour(childText) : void 0;
@@ -19111,23 +19442,115 @@ function getUserPhotoUrl(userId) {
19111
19442
  if (!base2) return void 0;
19112
19443
  return `${base2}/users/${userId}/photo`;
19113
19444
  }
19445
+
19446
+ // src/messages/smartCopy.ts
19447
+ async function copyMessageRich(container, markdown) {
19448
+ try {
19449
+ const canRichWrite = typeof ClipboardItem !== "undefined" && !!navigator.clipboard?.write;
19450
+ if (!canRichWrite) return copyText(markdown);
19451
+ const { html: html4, images } = await buildHtmlPayload(container);
19452
+ const htmlWithMarker = `${html4}${encodeMarkdownMarker(markdown)}`;
19453
+ const parts = {
19454
+ "text/html": new Blob([htmlWithMarker], { type: "text/html" }),
19455
+ "text/plain": new Blob([markdown], { type: "text/plain" })
19456
+ };
19457
+ if (images.length === 1) {
19458
+ const pngBlob = await dataUrlToBlob(images[0].dataUrl);
19459
+ if (pngBlob) parts["image/png"] = pngBlob;
19460
+ }
19461
+ const withCustom = {
19462
+ ...parts,
19463
+ [SS_MARKDOWN_CLIPBOARD_TYPE]: new Blob([markdown], {
19464
+ type: SS_MARKDOWN_CLIPBOARD_TYPE
19465
+ })
19466
+ };
19467
+ if (await tryWrite(withCustom)) return true;
19468
+ if (await tryWrite(parts)) return true;
19469
+ return copyText(markdown);
19470
+ } catch {
19471
+ return copyText(markdown);
19472
+ }
19473
+ }
19474
+ async function tryWrite(parts) {
19475
+ try {
19476
+ await navigator.clipboard.write([new ClipboardItem(parts)]);
19477
+ return true;
19478
+ } catch {
19479
+ return false;
19480
+ }
19481
+ }
19482
+ async function buildHtmlPayload(container) {
19483
+ const liveBlocks = Array.from(
19484
+ container.querySelectorAll(".ss-code-block--previewable")
19485
+ );
19486
+ const snapshots = await Promise.all(
19487
+ liveBlocks.map((block) => {
19488
+ const iframe = block.querySelector("iframe");
19489
+ return iframe ? snapshotIframe(iframe) : Promise.resolve([]);
19490
+ })
19491
+ );
19492
+ const clone = container.cloneNode(true);
19493
+ const cloneBlocks = Array.from(
19494
+ clone.querySelectorAll(".ss-code-block--previewable")
19495
+ );
19496
+ const allImages = [];
19497
+ cloneBlocks.forEach((block, i) => {
19498
+ const images = snapshots[i] ?? [];
19499
+ if (images.length > 0) {
19500
+ allImages.push(...images);
19501
+ const frag = block.ownerDocument.createElement("div");
19502
+ for (const img of images) {
19503
+ const el = block.ownerDocument.createElement("img");
19504
+ el.src = img.dataUrl;
19505
+ el.setAttribute("width", String(img.cssWidth));
19506
+ el.setAttribute("height", String(img.cssHeight));
19507
+ el.style.maxWidth = "100%";
19508
+ frag.appendChild(el);
19509
+ }
19510
+ block.replaceWith(frag);
19511
+ } else {
19512
+ const source = block.querySelector("code")?.textContent ?? "";
19513
+ const div = block.ownerDocument.createElement("div");
19514
+ div.innerHTML = stripScripts(source);
19515
+ block.replaceWith(div);
19516
+ }
19517
+ });
19518
+ clone.querySelectorAll(
19519
+ "iframe, button, .ss-code-block__header, .ss-code-block__copy, .ss-code-block__toggle"
19520
+ ).forEach((el) => el.remove());
19521
+ const html4 = `<meta charset="utf-8">${clone.innerHTML}`;
19522
+ return { html: html4, images: allImages };
19523
+ }
19524
+ function stripScripts(html4) {
19525
+ return html4.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
19526
+ }
19527
+ async function dataUrlToBlob(dataUrl) {
19528
+ try {
19529
+ const res = await fetch(dataUrl);
19530
+ return await res.blob();
19531
+ } catch {
19532
+ return null;
19533
+ }
19534
+ }
19114
19535
  var RESET_DELAY = 1e3;
19115
19536
  function ChatMessageCopyButton({
19116
- content
19537
+ content,
19538
+ contentRef
19117
19539
  }) {
19118
19540
  const [state, setState] = useState("idle" /* IDLE */);
19119
19541
  const handleCopy = async () => {
19120
19542
  if (!content) return;
19121
19543
  const textToCopy = content.filter((item) => !!item.text).map((item) => item.text).join("\n");
19122
19544
  if (!textToCopy) return;
19123
- try {
19124
- await navigator.clipboard.writeText(textToCopy);
19545
+ const container = contentRef?.current;
19546
+ const ok3 = container ? await copyMessageRich(container, textToCopy) : await copyText(textToCopy);
19547
+ if (ok3) {
19125
19548
  setState("success" /* SUCCESS */);
19126
19549
  setTimeout(() => {
19127
19550
  setState("idle" /* IDLE */);
19128
19551
  }, RESET_DELAY);
19129
- } catch (err) {
19130
- console.error("Failed to copy text:", err);
19552
+ } else {
19553
+ console.error("Failed to copy message content");
19131
19554
  }
19132
19555
  };
19133
19556
  const getIcon = () => {
@@ -19279,17 +19702,12 @@ function getSafeUrl(url) {
19279
19702
  return null;
19280
19703
  }
19281
19704
  }
19282
- function getDisplayName(source) {
19283
- if (source.file?.name) return source.file.name;
19284
- if (source.url) {
19285
- try {
19286
- const parsed = new URL(source.url);
19287
- return parsed.hostname + (parsed.pathname !== "/" ? parsed.pathname : "");
19288
- } catch {
19289
- return source.url;
19290
- }
19705
+ function getHost(url) {
19706
+ try {
19707
+ return new URL(url).hostname.replace(/^www\./, "");
19708
+ } catch {
19709
+ return url;
19291
19710
  }
19292
- return `Source ${source.index}`;
19293
19711
  }
19294
19712
  function isFileSource(s2) {
19295
19713
  return s2.sourceType === "File" /* File */ && !!s2.file;
@@ -19297,15 +19715,82 @@ function isFileSource(s2) {
19297
19715
  function isLinkSource(s2) {
19298
19716
  return (s2.sourceType === "URL" /* URL */ || s2.sourceType === "WebExternal" /* WebExternal */) && !!s2.url;
19299
19717
  }
19718
+ function UnverifiedBadge({
19719
+ attribution,
19720
+ compact = false
19721
+ }) {
19722
+ if (attribution !== "unsupported" /* Unsupported */) return null;
19723
+ return /* @__PURE__ */ jsxs(
19724
+ "span",
19725
+ {
19726
+ title: "This citation couldn't be verified against the source text \u2014 double-check it.",
19727
+ className: cn(
19728
+ "inline-flex items-center gap-0.5 rounded text-[10px] font-medium text-amber-600 dark:text-amber-400 flex-shrink-0",
19729
+ !compact && "px-1 py-px bg-amber-500/10"
19730
+ ),
19731
+ children: [
19732
+ /* @__PURE__ */ jsx(ShieldAlert, { className: "h-3 w-3" }),
19733
+ !compact && "Unverified"
19734
+ ]
19735
+ }
19736
+ );
19737
+ }
19738
+ function IndexChip({ index }) {
19739
+ 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 });
19740
+ }
19741
+ function SourceFavicon({ host }) {
19742
+ const [failed, setFailed] = useState(false);
19743
+ if (failed) {
19744
+ return /* @__PURE__ */ jsx(Globe, { className: "h-4 w-4 flex-shrink-0 text-muted-foreground" });
19745
+ }
19746
+ return /* @__PURE__ */ jsx(
19747
+ "img",
19748
+ {
19749
+ src: `https://www.google.com/s2/favicons?domain=${encodeURIComponent(
19750
+ host
19751
+ )}&sz=64`,
19752
+ alt: "",
19753
+ width: 16,
19754
+ height: 16,
19755
+ loading: "lazy",
19756
+ onError: () => setFailed(true),
19757
+ className: "h-4 w-4 flex-shrink-0 rounded-sm"
19758
+ }
19759
+ );
19760
+ }
19761
+ function CitedQuote({ text: text6 }) {
19762
+ if (!text6) return null;
19763
+ return /* @__PURE__ */ jsxs("p", { className: "m-0 text-xs leading-snug text-muted-foreground", children: [
19764
+ "\u201C",
19765
+ text6,
19766
+ "\u201D"
19767
+ ] });
19768
+ }
19769
+ function SectionLabel({ children: children2 }) {
19770
+ return /* @__PURE__ */ jsx("p", { className: "m-0 px-0.5 pb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground", children: children2 });
19771
+ }
19772
+ 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";
19773
+ var expandedCardClasses = "w-full rounded-lg border border-border/60 bg-background p-2.5 flex flex-col gap-1.5";
19300
19774
  function ChatMessageSources({
19301
19775
  sources
19302
19776
  }) {
19303
19777
  const { workspaceId, threadId } = useChatContext();
19304
19778
  const { downloadFileMutation } = useFileMutations({ workspaceId, threadId });
19305
19779
  const [isExpanded, setIsExpanded] = useState(true);
19780
+ const [openIndexes, setOpenIndexes] = useState(
19781
+ /* @__PURE__ */ new Set()
19782
+ );
19783
+ const toggleOpen = (index) => setOpenIndexes((prev) => {
19784
+ const next2 = new Set(prev);
19785
+ if (next2.has(index)) next2.delete(index);
19786
+ else next2.add(index);
19787
+ return next2;
19788
+ });
19306
19789
  const displaySources = (sources ?? []).filter(
19307
19790
  (s2) => isFileSource(s2) || isLinkSource(s2)
19308
19791
  );
19792
+ const urlSources = displaySources.filter((s2) => !isFileSource(s2));
19793
+ const fileSources = displaySources.filter(isFileSource);
19309
19794
  if (displaySources.length === 0) return null;
19310
19795
  return /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-lg border border-border bg-muted/30 overflow-hidden", children: [
19311
19796
  /* @__PURE__ */ jsxs(
@@ -19331,72 +19816,148 @@ function ChatMessageSources({
19331
19816
  ]
19332
19817
  }
19333
19818
  ),
19334
- isExpanded && /* @__PURE__ */ jsx("div", { className: "bg-background px-3 py-0.5", children: /* @__PURE__ */ jsx(
19335
- "ul",
19336
- {
19337
- className: "list-none space-y-1 m-0 p-0",
19338
- style: { paddingLeft: 0, marginLeft: 0 },
19339
- children: displaySources.map((source) => {
19340
- const displayName = getDisplayName(source);
19341
- if (isFileSource(source)) {
19342
- const Icon = getFileIcon3(source.file.name);
19819
+ isExpanded && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2.5 p-2.5", children: [
19820
+ urlSources.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
19821
+ /* @__PURE__ */ jsx(SectionLabel, { children: "URLs" }),
19822
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: urlSources.map((source) => {
19823
+ const host = getHost(source.url ?? "");
19824
+ const safeHref = source.url ? getSafeUrl(source.url) : null;
19825
+ if (!openIndexes.has(source.index)) {
19343
19826
  return /* @__PURE__ */ jsxs(
19344
- "li",
19827
+ "button",
19345
19828
  {
19346
- className: "list-none text-sm text-foreground m-0 p-0 flex items-center gap-1.5",
19347
- style: { paddingLeft: 0, marginLeft: 0 },
19829
+ type: "button",
19830
+ "aria-expanded": false,
19831
+ title: source.url ?? host,
19832
+ onClick: () => toggleOpen(source.index),
19833
+ className: pillClasses,
19348
19834
  children: [
19349
- /* @__PURE__ */ jsxs("span", { children: [
19350
- `(${source.index})`,
19351
- " : "
19352
- ] }),
19353
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 text-muted-foreground flex-shrink-0" }),
19835
+ /* @__PURE__ */ jsx(SourceFavicon, { host }),
19836
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: host }),
19837
+ /* @__PURE__ */ jsx(
19838
+ UnverifiedBadge,
19839
+ {
19840
+ attribution: source.attribution,
19841
+ compact: true
19842
+ }
19843
+ ),
19844
+ /* @__PURE__ */ jsx(IndexChip, { index: source.index })
19845
+ ]
19846
+ },
19847
+ `url-${source.index}`
19848
+ );
19849
+ }
19850
+ return /* @__PURE__ */ jsxs(
19851
+ "div",
19852
+ {
19853
+ className: expandedCardClasses,
19854
+ children: [
19855
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
19856
+ /* @__PURE__ */ jsx(SourceFavicon, { host }),
19857
+ safeHref ? /* @__PURE__ */ jsx(
19858
+ "a",
19859
+ {
19860
+ href: safeHref,
19861
+ target: "_blank",
19862
+ rel: "noreferrer",
19863
+ className: "min-w-0 truncate text-sm font-medium text-foreground no-underline hover:underline underline-offset-2",
19864
+ children: host
19865
+ }
19866
+ ) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-sm font-medium text-foreground", children: host }),
19867
+ /* @__PURE__ */ jsx(UnverifiedBadge, { attribution: source.attribution }),
19868
+ /* @__PURE__ */ jsx(IndexChip, { index: source.index }),
19354
19869
  /* @__PURE__ */ jsx(
19355
19870
  "button",
19356
19871
  {
19357
19872
  type: "button",
19358
- title: displayName,
19359
- onClick: () => downloadFileMutation.mutate(source.file),
19360
- className: "text-foreground hover:bg-muted/50 rounded px-1 py-0.5 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed",
19361
- disabled: downloadFileMutation.isPending,
19362
- children: displayName
19873
+ "aria-expanded": true,
19874
+ "aria-label": "Collapse source",
19875
+ onClick: () => toggleOpen(source.index),
19876
+ className: "ml-auto rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground",
19877
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" })
19363
19878
  }
19364
19879
  )
19880
+ ] }),
19881
+ source.url && /* @__PURE__ */ jsx("p", { className: "m-0 truncate text-xs text-muted-foreground/80", children: source.url }),
19882
+ /* @__PURE__ */ jsx(CitedQuote, { text: source.citedText })
19883
+ ]
19884
+ },
19885
+ `url-${source.index}`
19886
+ );
19887
+ }) })
19888
+ ] }),
19889
+ fileSources.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
19890
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Files" }),
19891
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: fileSources.map((source) => {
19892
+ const Icon = getFileIcon3(source.file.name);
19893
+ if (!openIndexes.has(source.index)) {
19894
+ return /* @__PURE__ */ jsxs(
19895
+ "button",
19896
+ {
19897
+ type: "button",
19898
+ "aria-expanded": false,
19899
+ title: source.file.name,
19900
+ onClick: () => toggleOpen(source.index),
19901
+ className: pillClasses,
19902
+ children: [
19903
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 flex-shrink-0 text-muted-foreground" }),
19904
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: source.file.name }),
19905
+ /* @__PURE__ */ jsx(
19906
+ UnverifiedBadge,
19907
+ {
19908
+ attribution: source.attribution,
19909
+ compact: true
19910
+ }
19911
+ ),
19912
+ /* @__PURE__ */ jsx(IndexChip, { index: source.index })
19365
19913
  ]
19366
19914
  },
19367
19915
  `${source.file.id}-${source.index}`
19368
19916
  );
19369
19917
  }
19370
- const safeHref = source.url ? getSafeUrl(source.url) : null;
19371
19918
  return /* @__PURE__ */ jsxs(
19372
- "li",
19919
+ "div",
19373
19920
  {
19374
- className: "list-none text-sm text-foreground m-0 p-0 flex items-center gap-1.5",
19375
- style: { paddingLeft: 0, marginLeft: 0 },
19921
+ className: expandedCardClasses,
19376
19922
  children: [
19377
- /* @__PURE__ */ jsxs("span", { children: [
19378
- `(${source.index})`,
19379
- " : "
19923
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
19924
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 flex-shrink-0 text-muted-foreground" }),
19925
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-sm font-medium text-foreground", children: source.file.name }),
19926
+ /* @__PURE__ */ jsx(UnverifiedBadge, { attribution: source.attribution }),
19927
+ /* @__PURE__ */ jsx(IndexChip, { index: source.index }),
19928
+ /* @__PURE__ */ jsx(
19929
+ "button",
19930
+ {
19931
+ type: "button",
19932
+ "aria-expanded": true,
19933
+ "aria-label": "Collapse source",
19934
+ onClick: () => toggleOpen(source.index),
19935
+ className: "ml-auto rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground",
19936
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-3.5 w-3.5" })
19937
+ }
19938
+ )
19380
19939
  ] }),
19381
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5 text-muted-foreground flex-shrink-0" }),
19382
- safeHref ? /* @__PURE__ */ jsx(
19383
- "a",
19940
+ /* @__PURE__ */ jsx(CitedQuote, { text: source.citedText }),
19941
+ /* @__PURE__ */ jsxs(
19942
+ "button",
19384
19943
  {
19385
- title: source.url ?? displayName,
19386
- href: safeHref,
19387
- target: "_blank",
19388
- rel: "noreferrer",
19389
- className: "text-foreground hover:bg-muted/50 rounded px-1 py-0.5 underline underline-offset-2",
19390
- children: displayName
19944
+ type: "button",
19945
+ onClick: () => downloadFileMutation.mutate(source.file),
19946
+ disabled: downloadFileMutation.isPending,
19947
+ 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",
19948
+ children: [
19949
+ /* @__PURE__ */ jsx(Download, { className: "h-3 w-3" }),
19950
+ "Download"
19951
+ ]
19391
19952
  }
19392
- ) : /* @__PURE__ */ jsx("span", { className: "text-foreground rounded px-1 py-0.5", children: displayName })
19953
+ )
19393
19954
  ]
19394
19955
  },
19395
- `url-${source.index}`
19956
+ `${source.file.id}-${source.index}`
19396
19957
  );
19397
- })
19398
- }
19399
- ) })
19958
+ }) })
19959
+ ] })
19960
+ ] })
19400
19961
  ] });
19401
19962
  }
19402
19963
  var MessageBubble = (props) => {
@@ -19415,6 +19976,7 @@ var MessageBubble = (props) => {
19415
19976
  } = props;
19416
19977
  const [responseFormData, setResponseFormData] = useState(userInput);
19417
19978
  const [responseFormValid, setResponseFormValid] = useState(false);
19979
+ const contentRef = useRef(null);
19418
19980
  const isBotResponse = type === "Output" /* OUTPUT */;
19419
19981
  const showForm = userOutput;
19420
19982
  useEffect(() => {
@@ -19452,12 +20014,12 @@ var MessageBubble = (props) => {
19452
20014
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: createdAt ? parseDateTime(createdAt, "Do MMMM YYYY, h:mm a") : "" })
19453
20015
  ] })
19454
20016
  ] }),
19455
- /* @__PURE__ */ jsx(ChatMessageCopyButton, { content })
20017
+ /* @__PURE__ */ jsx(ChatMessageCopyButton, { content, contentRef })
19456
20018
  ]
19457
20019
  }
19458
20020
  ),
19459
20021
  /* @__PURE__ */ jsxs("div", { className: cn(isBotResponse ? "p-4" : "px-4 py-2"), children: [
19460
- contentIsList && content?.map(
20022
+ /* @__PURE__ */ jsx("div", { ref: contentRef, children: contentIsList && content?.map(
19461
20023
  (item, i) => item.text ? /* @__PURE__ */ jsx(
19462
20024
  "div",
19463
20025
  {
@@ -19466,7 +20028,7 @@ var MessageBubble = (props) => {
19466
20028
  },
19467
20029
  `content-${i}`
19468
20030
  ) : item.image ? /* @__PURE__ */ jsx("div", { className: "mb-3 last:mb-0", children: /* @__PURE__ */ jsx(ChatMessageImage, { image: item.image }) }, `image-${i}`) : null
19469
- ),
20031
+ ) }),
19470
20032
  files.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ss-chat-message__attachments mt-4 space-y-2", children: [
19471
20033
  /* @__PURE__ */ jsx("h4", { className: "text-xs font-semibold text-muted-foreground mb-1", children: "Attachments" }),
19472
20034
  files.map((file, idx) => {
@@ -19542,7 +20104,14 @@ function pushContent(items, value) {
19542
20104
  return;
19543
20105
  }
19544
20106
  if (Array.isArray(value)) {
19545
- items.push(...value);
20107
+ const looksLikeContent = value.every(
20108
+ (it) => it != null && typeof it === "object" && ("text" in it || "image" in it)
20109
+ );
20110
+ if (looksLikeContent) {
20111
+ items.push(...value);
20112
+ } else {
20113
+ items.push({ text: "```json\n" + JSON.stringify(value, null, 2) + "\n```" });
20114
+ }
19546
20115
  return;
19547
20116
  }
19548
20117
  if (typeof value === "object") {
@@ -19617,25 +20186,28 @@ var MessageItem = ({
19617
20186
  let groupOpen = false;
19618
20187
  let keyCounter = 0;
19619
20188
  let lastStatusNode = null;
20189
+ const groupHasAnything = () => groupContent.length > 0 || groupFiles.length > 0 || groupSources.length > 0;
19620
20190
  const flush = (nextType) => {
19621
- bubbles.push(
19622
- /* @__PURE__ */ jsx(
19623
- MessageBubble,
19624
- {
19625
- createdBy: lastCreatedBy,
19626
- createdByUserId: lastCreatedByUserId,
19627
- createdAt: lastCreatedAt,
19628
- type: groupType,
19629
- content: groupContent,
19630
- files: groupFiles,
19631
- sources: groupSources,
19632
- chatbotName,
19633
- userOutput: null,
19634
- userInput: null
19635
- },
19636
- `bubble-${message.id ?? "msg"}-${keyCounter++}`
19637
- )
19638
- );
20191
+ if (groupHasAnything()) {
20192
+ bubbles.push(
20193
+ /* @__PURE__ */ jsx(
20194
+ MessageBubble,
20195
+ {
20196
+ createdBy: lastCreatedBy,
20197
+ createdByUserId: lastCreatedByUserId,
20198
+ createdAt: lastCreatedAt,
20199
+ type: groupType,
20200
+ content: groupContent,
20201
+ files: groupFiles,
20202
+ sources: groupSources,
20203
+ chatbotName,
20204
+ userOutput: null,
20205
+ userInput: null
20206
+ },
20207
+ `bubble-${message.id ?? "msg"}-${keyCounter++}`
20208
+ )
20209
+ );
20210
+ }
19639
20211
  groupContent = [];
19640
20212
  groupFiles = [];
19641
20213
  groupSources = [];
@@ -19649,7 +20221,8 @@ var MessageItem = ({
19649
20221
  groupType = v.type;
19650
20222
  const name = v.name.toLowerCase();
19651
20223
  switch (name) {
19652
- case "variables": {
20224
+ case "variables":
20225
+ case "userinfo": {
19653
20226
  continue;
19654
20227
  }
19655
20228
  case "status": {
@@ -19666,6 +20239,9 @@ var MessageItem = ({
19666
20239
  case "prompt":
19667
20240
  case "response":
19668
20241
  case "content": {
20242
+ if (v.value === "") {
20243
+ continue;
20244
+ }
19669
20245
  lastStatusNode = null;
19670
20246
  if (groupContent.length > 0) flush(v.type);
19671
20247
  if (v.value == null) {
@@ -19720,7 +20296,7 @@ var MessageItem = ({
19720
20296
  }
19721
20297
  case "sources": {
19722
20298
  groupSources = coerceSources(v.value);
19723
- groupOpen = true;
20299
+ if (groupSources.length > 0) groupOpen = true;
19724
20300
  break;
19725
20301
  }
19726
20302
  default: {
@@ -19734,7 +20310,7 @@ var MessageItem = ({
19734
20310
  lastCreatedBy = v.createdBy;
19735
20311
  lastCreatedByUserId = v.createdByUserId;
19736
20312
  }
19737
- if (groupOpen) {
20313
+ if (groupOpen && groupHasAnything()) {
19738
20314
  bubbles.push(
19739
20315
  /* @__PURE__ */ jsx(
19740
20316
  MessageBubble,
@@ -19790,6 +20366,10 @@ function MessageList({
19790
20366
  const messagesEndRef = useRef(null);
19791
20367
  const prevMessageCountRef = useRef(0);
19792
20368
  const hasInitialScrollRef = useRef(false);
20369
+ const everHadMessagesRef = useRef({
20370
+ threadId: "",
20371
+ had: false
20372
+ });
19793
20373
  const isMobile = useIsMobile();
19794
20374
  const { data: activeWorkspace } = useWorkspace(workspaceId);
19795
20375
  const [isAtBottom, setIsAtBottom] = useState(true);
@@ -19861,8 +20441,15 @@ function MessageList({
19861
20441
  ro.observe(content);
19862
20442
  return () => ro.disconnect();
19863
20443
  }, [isAtBottom, scrollToBottom]);
20444
+ const safeMessages = messages ?? [];
20445
+ if (everHadMessagesRef.current.threadId !== threadId) {
20446
+ everHadMessagesRef.current = { threadId, had: safeMessages.length > 0 };
20447
+ } else if (safeMessages.length > 0) {
20448
+ everHadMessagesRef.current.had = true;
20449
+ }
20450
+ const hadMessagesBefore = everHadMessagesRef.current.had;
19864
20451
  const isLoading = isChoosingThread || (threadPending || threadFetching) && !thread || (messagesPending || messagesFetching) && messages === void 0;
19865
- if (isLoading) {
20452
+ if (isLoading && !hadMessagesBefore) {
19866
20453
  return /* @__PURE__ */ jsx(
19867
20454
  "div",
19868
20455
  {
@@ -19878,7 +20465,7 @@ function MessageList({
19878
20465
  }
19879
20466
  );
19880
20467
  }
19881
- if (threadError || messagesError) {
20468
+ if ((threadError || messagesError) && !hadMessagesBefore) {
19882
20469
  return /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-md space-y-3", children: [
19883
20470
  threadError && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-destructive", children: [
19884
20471
  /* @__PURE__ */ jsx(AlertTriangle, { className: "h-4 w-4" }),
@@ -19890,8 +20477,7 @@ function MessageList({
19890
20477
  ] })
19891
20478
  ] }) });
19892
20479
  }
19893
- const safeMessages = messages ?? [];
19894
- if (safeMessages.length === 0) {
20480
+ if (safeMessages.length === 0 && !hadMessagesBefore) {
19895
20481
  return /* @__PURE__ */ jsxs("div", { className: "flex overflow-auto flex-shrink-10 flex-col p-8 text-center", children: [
19896
20482
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-medium mb-2", children: activeWorkspace?.name ?? "No messages yet" }),
19897
20483
  activeWorkspace?.firstPrompt && /* @__PURE__ */ jsx("div", { className: "max-w-3xl mx-auto p-4", children: /* @__PURE__ */ jsx(MessageMarkdown, { value: activeWorkspace.firstPrompt }) })
@@ -20109,6 +20695,7 @@ var computeAvatar = (name, fallback) => {
20109
20695
  function mapMentionUserDtoToModel(dto) {
20110
20696
  return {
20111
20697
  id: dto.id,
20698
+ userId: dto.userId,
20112
20699
  displayName: dto.displayName ?? "",
20113
20700
  initials: getInitials(dto.displayName ?? "")
20114
20701
  };
@@ -20132,26 +20719,25 @@ function mapWorkspaceDtoToModel(dto) {
20132
20719
  id: dto.id ?? "",
20133
20720
  name: dto.name ?? "",
20134
20721
  tags: dto.tags ?? [],
20135
- showSources: dto.showSources ?? void 0,
20136
- dataSpaces: Array.isArray(dto.dataSpaces) ? dto.dataSpaces : void 0,
20722
+ showSources: truthy(dto.showSources),
20723
+ dataSpaces: Array.isArray(dto.dataSpaces) ? dto.dataSpaces : [],
20137
20724
  createdByUserId: dto.createdByUserId ?? void 0,
20138
20725
  createdAt: dto.createdAt != null ? utcDate(dto.createdAt) : void 0,
20139
20726
  modifiedByUserId: dto.modifiedByUserId ?? void 0,
20140
20727
  modifiedAt: dto.modifiedAt != null ? utcDate(dto.modifiedAt) : void 0,
20141
20728
  favorited: truthy(dto.favorited),
20142
- summary: dto.summary ?? void 0,
20143
- firstPrompt: dto.firstPrompt ?? void 0,
20729
+ summary: dto.summary ?? "",
20730
+ firstPrompt: dto.firstPrompt ?? "",
20144
20731
  outputSchema: dto.outputSchema ?? void 0,
20145
- isPromptAndResponseLoggingEnabled: dto.isPromptAndResponseLoggingEnabled ?? void 0,
20146
20732
  inputs: dto.inputs ?? void 0,
20147
20733
  variables,
20148
20734
  sandBoxThreadId: dto.sandBoxThreadId ?? void 0,
20149
- supportsFiles: dto.supportsFiles ?? void 0,
20735
+ supportsFiles: truthy(dto.supportsFiles),
20150
20736
  avatarName: computeAvatar(dto.name ?? "")
20151
20737
  };
20152
20738
  }
20153
20739
  var mapWorkspacesDtoToModels = (arr) => arr.map(mapWorkspaceDtoToModel);
20154
20740
 
20155
- export { ChatProvider, ChatVariablesForm, DRAFT_THREAD_PREFIX, DateFromApi, MarkdownEditor, MessageComposer, MessageList, MessageListSkeleton, MessageMarkdown, MessageValueType, NEW_THREAD_ID, THREAD_LIST_PAGE_SIZE, applyDeltaToMessage, applyThreadToCache, createDraftThreadId, createThreadId, downloadFileBlobOptions, filesKeys, flowRunsKeys, getModelIcon, getThreadPlaceholderFromListCache, getUserPhotoUrl, invalidateWorkspaceThreadLists, isDraftThreadId, mapFileInfoDtoToModel, mapMentionUserDtoToModel, mapMessageDtoToModel, mapMessageErrorDtoToModel, mapMessageValueDtoToModel, mapMessagesDtoToModels, mapSignalRThreadSummaryToModel, mapThreadDtoToModel, mapThreadsResponseDtoToModel, mapWorkspaceDtoToModel, mapWorkspacesDtoToModels, markDraftThreadId, messagesKeys, messagesListOptions, messagesMutationsKeys, modelsKeys, parseDateTime, parseDateTimeHuman, setThreadOptimisticRunning, setThreadRunningInLists, taggableUsersOptions, threadDetailOptions, threadsKeys, unmarkDraftThreadId, useAddInputToMessage, useChatContext, useChatIdentity, useChatService, useDownloadFileBlobQuery, useFileMutations, useFlowRunVariables, useMessages, useModels, useSendMessage, useTaggableWorkspaceUsers, useThread, useThreadIsRunning, useUpdateFlowRunVariable, useWorkspace, utcDate, workspaceDetailOptions, workspaceKeys };
20741
+ export { ChatProvider, ChatVariablesForm, DRAFT_THREAD_PREFIX, DateFromApi, MarkdownEditor, MessageComposer, MessageList, MessageListSkeleton, MessageMarkdown, MessageValueType, NEW_THREAD_ID, THREAD_LIST_PAGE_SIZE, applyDeltaToMessage, applyThreadToCache, createDraftThreadId, createThreadId, downloadFileBlobOptions, filesKeys, flowRunsKeys, getModelIcon, getThreadPlaceholderFromListCache, getUserPhotoUrl, invalidateWorkspaceThreadLists, isDraftThreadId, mapFileInfoDtoToModel, mapMentionUserDtoToModel, mapMessageDtoToModel, mapMessageErrorDtoToModel, mapMessageValueDtoToModel, mapMessagesDtoToModels, mapSignalRThreadSummaryToModel, mapThreadDtoToModel, mapThreadsResponseDtoToModel, mapWorkspaceDtoToModel, mapWorkspacesDtoToModels, markDraftThreadId, messagesKeys, messagesListOptions, messagesMutationsKeys, modelsKeys, parseDateTime, parseDateTimeHuman, randomUUID, setThreadOptimisticRunning, setThreadRunningInLists, taggableUsersOptions, threadDetailOptions, threadsKeys, unmarkDraftThreadId, useAddInputToMessage, useChatContext, useChatIdentity, useChatService, useDownloadFileBlobQuery, useFileMutations, useFlowRunVariables, useMessages, useModels, useSendMessage, useTaggableWorkspaceUsers, useThread, useThreadIsRunning, useUpdateFlowRunVariable, useWorkspace, utcDate, workspaceDetailOptions, workspaceKeys };
20156
20742
  //# sourceMappingURL=index.js.map
20157
20743
  //# sourceMappingURL=index.js.map