@smartspace/chat-ui 1.13.1-dev.b2f9256 → 1.13.1-dev.b555ebd

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, ExternalLink, Copy, Download, 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
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';
@@ -424,6 +424,29 @@ var fileTag = $node("fileTag", () => ({
424
424
  var MAX_IFRAME_HEIGHT = 5e3;
425
425
  var HEIGHT_MESSAGE = "ss-html-preview-height";
426
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
+ }
427
450
  var HEIGHT_REPORTER_SCRIPT = `
428
451
  <script>(function(){
429
452
  try {
@@ -448,6 +471,48 @@ var HEIGHT_REPORTER_SCRIPT = `
448
471
  window.addEventListener('unhandledrejection', function(ev){
449
472
  reportError(ev && ev.reason && ev.reason.message);
450
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
+ });
451
516
  var lastSent = -1;
452
517
  function send(){
453
518
  try {
@@ -533,6 +598,38 @@ async function copyText(text6) {
533
598
  return false;
534
599
  }
535
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
+ }
536
633
 
537
634
  // src/shared/markdown/extensions/htmlPreview.ts
538
635
  var PREVIEW_LANGUAGES = /* @__PURE__ */ new Set(["html"]);
@@ -756,7 +853,7 @@ var mention = $node("mention", () => ({
756
853
  match: (node2) => node2.type.name === "mention",
757
854
  runner: (state, node2) => {
758
855
  const { id, label } = node2.attrs;
759
- state.addNode("mention", void 0, `${id}|${label}`);
856
+ state.addNode("text", void 0, label || `@${id}`);
760
857
  }
761
858
  }
762
859
  }));
@@ -982,6 +1079,7 @@ function EditorInner({
982
1079
  const [_isDragging, setIsDragging] = useState(false);
983
1080
  const viewRef = useRef(null);
984
1081
  const serializerRef = useRef(null);
1082
+ const parserRef = useRef(null);
985
1083
  function guessImageExt(mime) {
986
1084
  const t = (mime || "").toLowerCase();
987
1085
  if (t === "image/jpeg") return "jpg";
@@ -1100,6 +1198,10 @@ function EditorInner({
1100
1198
  serializerRef.current = anyCtx.get(serializerCtx);
1101
1199
  } catch {
1102
1200
  }
1201
+ try {
1202
+ parserRef.current = anyCtx.get(parserCtx);
1203
+ } catch {
1204
+ }
1103
1205
  const handle2 = () => {
1104
1206
  try {
1105
1207
  if (enableMentions) updateMentionFromView();
@@ -1189,6 +1291,19 @@ function EditorInner({
1189
1291
  const tr = view.state.tr.replaceWith(from, to, node2);
1190
1292
  view.dispatch(tr.scrollIntoView());
1191
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
+ }
1192
1307
  async function insertUploadedFilesIntoEditor(files) {
1193
1308
  const view = viewRef.current;
1194
1309
  if (!isEditable || !view) return false;
@@ -1461,6 +1576,17 @@ function EditorInner({
1461
1576
  },
1462
1577
  onPasteCapture: (e) => {
1463
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
+ }
1464
1590
  const items = e.clipboardData?.items;
1465
1591
  if (!items) return;
1466
1592
  const imageFiles = [];
@@ -1687,7 +1813,7 @@ var buttonVariants = cva(
1687
1813
  }
1688
1814
  }
1689
1815
  );
1690
- var Button = React8.forwardRef(
1816
+ var Button = React9.forwardRef(
1691
1817
  ({ className, variant, size, asChild = false, ...props }, ref) => {
1692
1818
  const Comp = asChild ? Slot : "button";
1693
1819
  return /* @__PURE__ */ jsx(
@@ -2902,6 +3028,128 @@ var modelIdRendererTester = rankWith(
2902
3028
  }
2903
3029
  );
2904
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);
2905
3153
  var TextareaRenderer = ({
2906
3154
  data,
2907
3155
  handleChange,
@@ -3071,6 +3319,7 @@ var renderers = [
3071
3319
  { tester: modelIdRendererTester, renderer: ModelIdRendererControl },
3072
3320
  { tester: booleanRendererTester, renderer: BooleanRendererControl },
3073
3321
  { tester: dropdownRendererTester, renderer: DropdownRendererControl },
3322
+ { tester: numberRendererTester, renderer: NumberRendererControl },
3074
3323
  { tester: textareaRendererTester, renderer: TextareaRendererControl },
3075
3324
  ...vanillaRenderers,
3076
3325
  { tester: jsonEditorTester, renderer: JsonEditorRendererControl }
@@ -3124,26 +3373,26 @@ function useChatVariablesFormVm({
3124
3373
  const { mutate: updateVariableMutation } = useUpdateFlowRunVariable();
3125
3374
  const querySettled = !isLoading && (threadVars !== void 0 || isError);
3126
3375
  const shouldUseDefaults = isError || threadVars && Object.keys(threadVars).length === 0;
3127
- const built = React8.useMemo(() => {
3376
+ const built = React9.useMemo(() => {
3128
3377
  return buildSimpleSchemaAndUi(
3129
3378
  workspace.variables,
3130
3379
  threadVars,
3131
3380
  shouldUseDefaults ?? false
3132
3381
  );
3133
3382
  }, [workspace.variables, threadVars, shouldUseDefaults]);
3134
- const [data, setData] = React8.useState(null);
3135
- React8.useEffect(() => {
3383
+ const [data, setData] = React9.useState(null);
3384
+ React9.useEffect(() => {
3136
3385
  if (querySettled) {
3137
3386
  setData(built.initialData);
3138
3387
  setVariables(built.initialData);
3139
3388
  }
3140
3389
  }, [querySettled, built.initialData, setVariables]);
3141
- const ajv = React8.useMemo(() => createAjv({ useDefaults: false }), []);
3142
- const prevRef = React8.useRef(null);
3143
- React8.useEffect(() => {
3390
+ const ajv = React9.useMemo(() => createAjv({ useDefaults: false }), []);
3391
+ const prevRef = React9.useRef(null);
3392
+ React9.useEffect(() => {
3144
3393
  prevRef.current = data;
3145
3394
  }, [data]);
3146
- const onChange = React8.useCallback(
3395
+ const onChange = React9.useCallback(
3147
3396
  ({ data: next2 }) => {
3148
3397
  if (prevRef.current && !isDraftThreadId(threadId)) {
3149
3398
  const keys2 = Object.keys(workspace.variables || {});
@@ -3164,7 +3413,7 @@ function useChatVariablesFormVm({
3164
3413
  },
3165
3414
  [workspace.variables, setVariables, updateVariableMutation, threadId]
3166
3415
  );
3167
- const config = React8.useMemo(
3416
+ const config = React9.useMemo(
3168
3417
  () => ({
3169
3418
  restrict: true,
3170
3419
  trim: false,
@@ -3453,6 +3702,12 @@ var useThreadIsRunning = (workspaceId, threadId) => {
3453
3702
  return !!optimistic || !!(detailThread ?? listThread)?.isFlowRunning;
3454
3703
  };
3455
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
+
3456
3711
  // src/domains/messages/enums.ts
3457
3712
  var MessageValueType = /* @__PURE__ */ ((MessageValueType2) => {
3458
3713
  MessageValueType2["OUTPUT"] = "Output";
@@ -3500,10 +3755,10 @@ function useSendMessage() {
3500
3755
  if (!threadId) throw new Error("Thread ID is required");
3501
3756
  if (!workspaceId) throw new Error("Workspace ID is required");
3502
3757
  const optimistic = {
3503
- id: `temp-${crypto.randomUUID()}`,
3758
+ id: `temp-${randomUUID()}`,
3504
3759
  values: [
3505
3760
  {
3506
- id: `temp-${crypto.randomUUID()}-prompt`,
3761
+ id: `temp-${randomUUID()}-prompt`,
3507
3762
  type: "Input" /* INPUT */,
3508
3763
  name: "prompt",
3509
3764
  value: contentList,
@@ -3514,7 +3769,7 @@ function useSendMessage() {
3514
3769
  },
3515
3770
  ...files?.length ? [
3516
3771
  {
3517
- id: `temp-${crypto.randomUUID()}-files`,
3772
+ id: `temp-${randomUUID()}-files`,
3518
3773
  type: "Input" /* INPUT */,
3519
3774
  name: "files",
3520
3775
  value: files,
@@ -3526,7 +3781,7 @@ function useSendMessage() {
3526
3781
  ] : [],
3527
3782
  ...variables && Object.keys(variables).length ? [
3528
3783
  {
3529
- id: `temp-${crypto.randomUUID()}-vars`,
3784
+ id: `temp-${randomUUID()}-vars`,
3530
3785
  type: "Input" /* INPUT */,
3531
3786
  name: "variables",
3532
3787
  value: variables,
@@ -3595,8 +3850,9 @@ function useAddInputToMessage() {
3595
3850
  const { userId, displayName: userName } = useChatIdentity();
3596
3851
  const service = useChatService();
3597
3852
  const addInputToMessageMutation = useMutation({
3598
- mutationFn: async ({ threadId, messageId, name, value, channels }) => {
3599
- 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)) ?? [];
3600
3856
  qc.setQueryData(
3601
3857
  messagesKeys.list(threadId),
3602
3858
  (old = []) => old.map(
@@ -3605,7 +3861,7 @@ function useAddInputToMessage() {
3605
3861
  values: [
3606
3862
  ...m.values ?? [],
3607
3863
  {
3608
- id: `temp-${Date.now()}-add`,
3864
+ id: `temp-${randomUUID()}-add`,
3609
3865
  type: "Input" /* INPUT */,
3610
3866
  name,
3611
3867
  value,
@@ -3618,7 +3874,10 @@ function useAddInputToMessage() {
3618
3874
  } : m
3619
3875
  )
3620
3876
  );
3621
- 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");
3622
3881
  return await service.addInputToMessage({
3623
3882
  messageId,
3624
3883
  name,
@@ -3632,11 +3891,13 @@ function useAddInputToMessage() {
3632
3891
  (old = []) => reconcileWithMessage(old, message, "replace")
3633
3892
  );
3634
3893
  },
3635
- onError: (_e, { threadId }) => {
3636
- qc.setQueryData(
3637
- messagesKeys.list(threadId),
3638
- (old = []) => old.filter((m) => !m.optimistic)
3639
- );
3894
+ onError: (_e, { threadId }, context) => {
3895
+ if (context) {
3896
+ qc.setQueryData(
3897
+ messagesKeys.list(threadId),
3898
+ context.previousMessages
3899
+ );
3900
+ }
3640
3901
  toast.error("There was an error posting your form input");
3641
3902
  },
3642
3903
  retry: false
@@ -18585,17 +18846,29 @@ function CodeBlock({ language, source }) {
18585
18846
  /* @__PURE__ */ jsx("pre", { ...language ? { "data-language": language } : {}, children: /* @__PURE__ */ jsx("code", { className: codeClass, children: source }) })
18586
18847
  ] });
18587
18848
  }
18849
+ var STREAM_SETTLE_MS = 250;
18588
18850
  function HtmlPreview({ source }) {
18589
18851
  const [showingPreview, setShowingPreview] = useState(true);
18590
18852
  const [copyLabel, setCopyLabel] = useState(
18591
18853
  "Copy"
18592
18854
  );
18593
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);
18594
18859
  const iframeRef = useRef(null);
18595
18860
  const rafIdRef = useRef(null);
18596
18861
  const pendingHeightRef = useRef(null);
18597
18862
  const copyResetTimerRef = useRef(null);
18598
- 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]);
18599
18872
  useEffect(() => {
18600
18873
  ensureGlobalListener();
18601
18874
  }, []);
@@ -18610,6 +18883,7 @@ function HtmlPreview({ source }) {
18610
18883
  pendingHeightRef.current = null;
18611
18884
  if (next2 <= 0) return;
18612
18885
  setIframeHeight(next2);
18886
+ setMeasured(true);
18613
18887
  };
18614
18888
  const scheduleHeight = (height) => {
18615
18889
  pendingHeightRef.current = height;
@@ -18701,19 +18975,35 @@ function HtmlPreview({ source }) {
18701
18975
  )
18702
18976
  ] })
18703
18977
  ] }),
18704
- /* @__PURE__ */ jsx(
18705
- "iframe",
18978
+ /* @__PURE__ */ jsxs(
18979
+ "div",
18706
18980
  {
18707
- ref: iframeRef,
18708
- className: "ss-code-block__iframe",
18709
- sandbox: "allow-scripts",
18710
- loading: "lazy",
18711
- title: "HTML preview",
18712
- srcDoc: srcdoc,
18981
+ className: "ss-code-block__preview",
18713
18982
  style: {
18714
18983
  display: showingPreview ? "block" : "none",
18715
- ...iframeHeight != null ? { height: `${iframeHeight}px` } : {}
18716
- }
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
+ ]
18717
19007
  }
18718
19008
  ),
18719
19009
  /* @__PURE__ */ jsx(
@@ -19077,7 +19367,7 @@ function getAvatarColour(name) {
19077
19367
  const textColor = brightness > 128 ? "#000000" : "#FFFFFF";
19078
19368
  return { backgroundColor, textColor };
19079
19369
  }
19080
- var Avatar = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
19370
+ var Avatar = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
19081
19371
  "div",
19082
19372
  {
19083
19373
  ref,
@@ -19089,7 +19379,7 @@ var Avatar = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
19089
19379
  }
19090
19380
  ));
19091
19381
  Avatar.displayName = "Avatar";
19092
- 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(
19093
19383
  MuiAvatar,
19094
19384
  {
19095
19385
  className: cn("aspect-square h-full w-full", className),
@@ -19100,7 +19390,7 @@ var AvatarImage = React8.forwardRef(({ className, alt, src, children: children2,
19100
19390
  }
19101
19391
  ));
19102
19392
  AvatarImage.displayName = "AvatarImage";
19103
- var AvatarFallback = React8.forwardRef(
19393
+ var AvatarFallback = React9.forwardRef(
19104
19394
  ({ className, colored = true, ...props }, ref) => {
19105
19395
  const childText = String(props.children ?? "");
19106
19396
  const colours = colored ? getAvatarColour(childText) : void 0;
@@ -19152,23 +19442,115 @@ function getUserPhotoUrl(userId) {
19152
19442
  if (!base2) return void 0;
19153
19443
  return `${base2}/users/${userId}/photo`;
19154
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
+ }
19155
19535
  var RESET_DELAY = 1e3;
19156
19536
  function ChatMessageCopyButton({
19157
- content
19537
+ content,
19538
+ contentRef
19158
19539
  }) {
19159
19540
  const [state, setState] = useState("idle" /* IDLE */);
19160
19541
  const handleCopy = async () => {
19161
19542
  if (!content) return;
19162
19543
  const textToCopy = content.filter((item) => !!item.text).map((item) => item.text).join("\n");
19163
19544
  if (!textToCopy) return;
19164
- try {
19165
- await navigator.clipboard.writeText(textToCopy);
19545
+ const container = contentRef?.current;
19546
+ const ok3 = container ? await copyMessageRich(container, textToCopy) : await copyText(textToCopy);
19547
+ if (ok3) {
19166
19548
  setState("success" /* SUCCESS */);
19167
19549
  setTimeout(() => {
19168
19550
  setState("idle" /* IDLE */);
19169
19551
  }, RESET_DELAY);
19170
- } catch (err) {
19171
- console.error("Failed to copy text:", err);
19552
+ } else {
19553
+ console.error("Failed to copy message content");
19172
19554
  }
19173
19555
  };
19174
19556
  const getIcon = () => {
@@ -19338,6 +19720,37 @@ function isFileSource(s2) {
19338
19720
  function isLinkSource(s2) {
19339
19721
  return (s2.sourceType === "URL" /* URL */ || s2.sourceType === "WebExternal" /* WebExternal */) && !!s2.url;
19340
19722
  }
19723
+ function UnverifiedBadge({
19724
+ attribution
19725
+ }) {
19726
+ if (attribution !== "unsupported" /* Unsupported */) return null;
19727
+ return /* @__PURE__ */ jsxs(
19728
+ "span",
19729
+ {
19730
+ title: "This citation couldn't be verified against the source text \u2014 double-check it.",
19731
+ className: "inline-flex items-center gap-0.5 rounded px-1 py-px text-[10px] font-medium text-amber-600 dark:text-amber-400 bg-amber-500/10 flex-shrink-0",
19732
+ children: [
19733
+ /* @__PURE__ */ jsx(ShieldAlert, { className: "h-3 w-3" }),
19734
+ "Unverified"
19735
+ ]
19736
+ }
19737
+ );
19738
+ }
19739
+ function CitedQuote({ source }) {
19740
+ if (!source.citedText) return null;
19741
+ return /* @__PURE__ */ jsxs(
19742
+ "p",
19743
+ {
19744
+ title: source.citedText,
19745
+ className: "m-0 pl-6 pr-2 pb-0.5 text-xs italic text-muted-foreground line-clamp-2 border-l-2 border-border ml-1",
19746
+ children: [
19747
+ "\u201C",
19748
+ source.citedText,
19749
+ "\u201D"
19750
+ ]
19751
+ }
19752
+ );
19753
+ }
19341
19754
  function ChatMessageSources({
19342
19755
  sources
19343
19756
  }) {
@@ -19384,25 +19797,29 @@ function ChatMessageSources({
19384
19797
  return /* @__PURE__ */ jsxs(
19385
19798
  "li",
19386
19799
  {
19387
- className: "list-none text-sm text-foreground m-0 p-0 flex items-center gap-1.5",
19800
+ className: "list-none text-sm text-foreground m-0 p-0",
19388
19801
  style: { paddingLeft: 0, marginLeft: 0 },
19389
19802
  children: [
19390
- /* @__PURE__ */ jsxs("span", { children: [
19391
- `(${source.index})`,
19392
- " : "
19803
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
19804
+ /* @__PURE__ */ jsxs("span", { children: [
19805
+ `(${source.index})`,
19806
+ " : "
19807
+ ] }),
19808
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 text-muted-foreground flex-shrink-0" }),
19809
+ /* @__PURE__ */ jsx(
19810
+ "button",
19811
+ {
19812
+ type: "button",
19813
+ title: displayName,
19814
+ onClick: () => downloadFileMutation.mutate(source.file),
19815
+ className: "text-foreground hover:bg-muted/50 rounded px-1 py-0.5 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed truncate",
19816
+ disabled: downloadFileMutation.isPending,
19817
+ children: displayName
19818
+ }
19819
+ ),
19820
+ /* @__PURE__ */ jsx(UnverifiedBadge, { attribution: source.attribution })
19393
19821
  ] }),
19394
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 text-muted-foreground flex-shrink-0" }),
19395
- /* @__PURE__ */ jsx(
19396
- "button",
19397
- {
19398
- type: "button",
19399
- title: displayName,
19400
- onClick: () => downloadFileMutation.mutate(source.file),
19401
- className: "text-foreground hover:bg-muted/50 rounded px-1 py-0.5 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed",
19402
- disabled: downloadFileMutation.isPending,
19403
- children: displayName
19404
- }
19405
- )
19822
+ /* @__PURE__ */ jsx(CitedQuote, { source })
19406
19823
  ]
19407
19824
  },
19408
19825
  `${source.file.id}-${source.index}`
@@ -19412,25 +19829,29 @@ function ChatMessageSources({
19412
19829
  return /* @__PURE__ */ jsxs(
19413
19830
  "li",
19414
19831
  {
19415
- className: "list-none text-sm text-foreground m-0 p-0 flex items-center gap-1.5",
19832
+ className: "list-none text-sm text-foreground m-0 p-0",
19416
19833
  style: { paddingLeft: 0, marginLeft: 0 },
19417
19834
  children: [
19418
- /* @__PURE__ */ jsxs("span", { children: [
19419
- `(${source.index})`,
19420
- " : "
19835
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
19836
+ /* @__PURE__ */ jsxs("span", { children: [
19837
+ `(${source.index})`,
19838
+ " : "
19839
+ ] }),
19840
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5 text-muted-foreground flex-shrink-0" }),
19841
+ safeHref ? /* @__PURE__ */ jsx(
19842
+ "a",
19843
+ {
19844
+ title: source.url ?? displayName,
19845
+ href: safeHref,
19846
+ target: "_blank",
19847
+ rel: "noreferrer",
19848
+ className: "text-foreground hover:bg-muted/50 rounded px-1 py-0.5 underline underline-offset-2 truncate",
19849
+ children: displayName
19850
+ }
19851
+ ) : /* @__PURE__ */ jsx("span", { className: "text-foreground rounded px-1 py-0.5 truncate", children: displayName }),
19852
+ /* @__PURE__ */ jsx(UnverifiedBadge, { attribution: source.attribution })
19421
19853
  ] }),
19422
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5 text-muted-foreground flex-shrink-0" }),
19423
- safeHref ? /* @__PURE__ */ jsx(
19424
- "a",
19425
- {
19426
- title: source.url ?? displayName,
19427
- href: safeHref,
19428
- target: "_blank",
19429
- rel: "noreferrer",
19430
- className: "text-foreground hover:bg-muted/50 rounded px-1 py-0.5 underline underline-offset-2",
19431
- children: displayName
19432
- }
19433
- ) : /* @__PURE__ */ jsx("span", { className: "text-foreground rounded px-1 py-0.5", children: displayName })
19854
+ /* @__PURE__ */ jsx(CitedQuote, { source })
19434
19855
  ]
19435
19856
  },
19436
19857
  `url-${source.index}`
@@ -19456,6 +19877,7 @@ var MessageBubble = (props) => {
19456
19877
  } = props;
19457
19878
  const [responseFormData, setResponseFormData] = useState(userInput);
19458
19879
  const [responseFormValid, setResponseFormValid] = useState(false);
19880
+ const contentRef = useRef(null);
19459
19881
  const isBotResponse = type === "Output" /* OUTPUT */;
19460
19882
  const showForm = userOutput;
19461
19883
  useEffect(() => {
@@ -19493,12 +19915,12 @@ var MessageBubble = (props) => {
19493
19915
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: createdAt ? parseDateTime(createdAt, "Do MMMM YYYY, h:mm a") : "" })
19494
19916
  ] })
19495
19917
  ] }),
19496
- /* @__PURE__ */ jsx(ChatMessageCopyButton, { content })
19918
+ /* @__PURE__ */ jsx(ChatMessageCopyButton, { content, contentRef })
19497
19919
  ]
19498
19920
  }
19499
19921
  ),
19500
19922
  /* @__PURE__ */ jsxs("div", { className: cn(isBotResponse ? "p-4" : "px-4 py-2"), children: [
19501
- contentIsList && content?.map(
19923
+ /* @__PURE__ */ jsx("div", { ref: contentRef, children: contentIsList && content?.map(
19502
19924
  (item, i) => item.text ? /* @__PURE__ */ jsx(
19503
19925
  "div",
19504
19926
  {
@@ -19507,7 +19929,7 @@ var MessageBubble = (props) => {
19507
19929
  },
19508
19930
  `content-${i}`
19509
19931
  ) : item.image ? /* @__PURE__ */ jsx("div", { className: "mb-3 last:mb-0", children: /* @__PURE__ */ jsx(ChatMessageImage, { image: item.image }) }, `image-${i}`) : null
19510
- ),
19932
+ ) }),
19511
19933
  files.length > 0 && /* @__PURE__ */ jsxs("div", { className: "ss-chat-message__attachments mt-4 space-y-2", children: [
19512
19934
  /* @__PURE__ */ jsx("h4", { className: "text-xs font-semibold text-muted-foreground mb-1", children: "Attachments" }),
19513
19935
  files.map((file, idx) => {
@@ -19690,7 +20112,8 @@ var MessageItem = ({
19690
20112
  groupType = v.type;
19691
20113
  const name = v.name.toLowerCase();
19692
20114
  switch (name) {
19693
- case "variables": {
20115
+ case "variables":
20116
+ case "userinfo": {
19694
20117
  continue;
19695
20118
  }
19696
20119
  case "status": {
@@ -20160,6 +20583,7 @@ var computeAvatar = (name, fallback) => {
20160
20583
  function mapMentionUserDtoToModel(dto) {
20161
20584
  return {
20162
20585
  id: dto.id,
20586
+ userId: dto.userId,
20163
20587
  displayName: dto.displayName ?? "",
20164
20588
  initials: getInitials(dto.displayName ?? "")
20165
20589
  };
@@ -20194,9 +20618,6 @@ function mapWorkspaceDtoToModel(dto) {
20194
20618
  firstPrompt: dto.firstPrompt ?? "",
20195
20619
  outputSchema: dto.outputSchema ?? void 0,
20196
20620
  inputs: dto.inputs ?? void 0,
20197
- isPromptAndResponseLoggingEnabled: truthy(
20198
- dto.isPromptAndResponseLoggingEnabled
20199
- ),
20200
20621
  variables,
20201
20622
  sandBoxThreadId: dto.sandBoxThreadId ?? void 0,
20202
20623
  supportsFiles: truthy(dto.supportsFiles),
@@ -20205,6 +20626,6 @@ function mapWorkspaceDtoToModel(dto) {
20205
20626
  }
20206
20627
  var mapWorkspacesDtoToModels = (arr) => arr.map(mapWorkspaceDtoToModel);
20207
20628
 
20208
- 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 };
20629
+ 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 };
20209
20630
  //# sourceMappingURL=index.js.map
20210
20631
  //# sourceMappingURL=index.js.map