@smartspace/chat-ui 1.13.1-dev.c6d0f32 → 1.13.1-dev.dc3bd10

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,10 +1,10 @@
1
1
  import MuiButton from '@mui/material/Button';
2
2
  import IconButton from '@mui/material/IconButton';
3
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';
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
10
  import { Editor, rootCtx, defaultValueCtx, editorViewOptionsCtx, editorViewCtx, serializerCtx, SchemaReady, nodeViewCtx, markViewCtx, schemaCtx, prosePluginsCtx, nodesCtx } from '@milkdown/core';
@@ -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,
@@ -871,7 +880,7 @@ var ssImageView = $view(ssImageNode, (ctx) => (node2) => {
871
880
  removeBtn.type = "button";
872
881
  removeBtn.className = "ss-attach__remove";
873
882
  removeBtn.setAttribute("aria-label", "Remove image");
874
- removeBtn.textContent = "\xD7";
883
+ 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
884
  removeBtn.addEventListener("mousedown", (e) => {
876
885
  e.preventDefault();
877
886
  e.stopPropagation();
@@ -890,16 +899,19 @@ var ssImageView = $view(ssImageNode, (ctx) => (node2) => {
890
899
  }
891
900
  });
892
901
  img.style.background = "rgba(0,0,0,0.04)";
902
+ img.style.visibility = "hidden";
893
903
  let hasSetRealSrc = false;
894
904
  img.addEventListener("load", () => {
895
905
  if (!hasSetRealSrc) return;
896
906
  spinner.remove();
897
907
  img.style.background = "";
908
+ img.style.visibility = "";
898
909
  });
899
910
  img.addEventListener("error", () => {
900
911
  if (!hasSetRealSrc) return;
901
912
  spinner.remove();
902
913
  img.style.background = "rgba(255,0,0,0.06)";
914
+ img.style.visibility = "";
903
915
  });
904
916
  try {
905
917
  const anyWin = window;
@@ -1675,7 +1687,7 @@ var buttonVariants = cva(
1675
1687
  }
1676
1688
  }
1677
1689
  );
1678
- var Button = React8.forwardRef(
1690
+ var Button = React9.forwardRef(
1679
1691
  ({ className, variant, size, asChild = false, ...props }, ref) => {
1680
1692
  const Comp = asChild ? Slot : "button";
1681
1693
  return /* @__PURE__ */ jsx(
@@ -2890,6 +2902,128 @@ var modelIdRendererTester = rankWith(
2890
2902
  }
2891
2903
  );
2892
2904
  var ModelIdRendererControl = withJsonFormsControlProps(ModelIdRenderer);
2905
+ var NumberRenderer = ({
2906
+ data,
2907
+ handleChange,
2908
+ path: path2,
2909
+ label,
2910
+ description,
2911
+ errors,
2912
+ schema,
2913
+ uischema,
2914
+ visible,
2915
+ enabled,
2916
+ required
2917
+ }) => {
2918
+ const isInteger = schema?.type === "integer";
2919
+ const handleInputChange = useCallback(
2920
+ (event) => {
2921
+ const raw2 = event.target.value;
2922
+ if (raw2 === "") {
2923
+ handleChange(path2, void 0);
2924
+ return;
2925
+ }
2926
+ const parsed = isInteger ? parseInt(raw2, 10) : parseFloat(raw2);
2927
+ if (Number.isNaN(parsed)) {
2928
+ handleChange(path2, void 0);
2929
+ return;
2930
+ }
2931
+ handleChange(path2, parsed);
2932
+ },
2933
+ [handleChange, path2, isInteger]
2934
+ );
2935
+ if (!visible) return null;
2936
+ const readOnly = uischema?.access === "Read";
2937
+ const isDisabled = !enabled || readOnly;
2938
+ const hasError = !!errors && errors.length > 0;
2939
+ const fieldSchema = schema;
2940
+ const min = fieldSchema?.minimum;
2941
+ const max = fieldSchema?.maximum;
2942
+ const step = isInteger ? 1 : fieldSchema?.multipleOf ?? "any";
2943
+ return /* @__PURE__ */ jsxs(
2944
+ "div",
2945
+ {
2946
+ className: "ss-jsonforms-field ss-jsonforms-number",
2947
+ style: {
2948
+ display: "inline-flex",
2949
+ flexDirection: "row",
2950
+ alignItems: "center",
2951
+ gap: 8,
2952
+ minHeight: "40px"
2953
+ },
2954
+ children: [
2955
+ label && /* @__PURE__ */ jsxs(
2956
+ "label",
2957
+ {
2958
+ htmlFor: `number-${path2}`,
2959
+ style: {
2960
+ color: hasError ? "#ef4444" : "#475569",
2961
+ fontSize: "0.875rem",
2962
+ fontWeight: 500,
2963
+ whiteSpace: "nowrap",
2964
+ lineHeight: "24px"
2965
+ },
2966
+ children: [
2967
+ label,
2968
+ required && /* @__PURE__ */ jsx("span", { style: { color: "#ef4444", marginLeft: "0.25rem" }, children: "*" })
2969
+ ]
2970
+ }
2971
+ ),
2972
+ /* @__PURE__ */ jsx(
2973
+ "input",
2974
+ {
2975
+ id: `number-${path2}`,
2976
+ type: "number",
2977
+ value: data ?? "",
2978
+ onChange: handleInputChange,
2979
+ disabled: isDisabled,
2980
+ min,
2981
+ max,
2982
+ step,
2983
+ style: {
2984
+ width: "80px",
2985
+ height: "24px",
2986
+ padding: "0 0.5rem",
2987
+ border: hasError ? "2px solid #ef4444" : "1px solid #d1d5db",
2988
+ borderRadius: "6px",
2989
+ fontSize: "0.875rem",
2990
+ lineHeight: "24px",
2991
+ fontFamily: "inherit",
2992
+ backgroundColor: isDisabled ? "#f9fafb" : "#ffffff",
2993
+ color: isDisabled ? "#9ca3af" : "#111827",
2994
+ outline: "none",
2995
+ boxSizing: "border-box"
2996
+ }
2997
+ }
2998
+ ),
2999
+ hasError && /* @__PURE__ */ jsx(
3000
+ "div",
3001
+ {
3002
+ style: {
3003
+ color: "#ef4444",
3004
+ fontSize: "0.75rem"
3005
+ },
3006
+ children: errors
3007
+ }
3008
+ )
3009
+ ]
3010
+ }
3011
+ );
3012
+ };
3013
+ var numberRendererTester = rankWith(
3014
+ 40,
3015
+ (uischema, schema) => {
3016
+ if (uischema.type !== "Control") return false;
3017
+ const propertyPath = uischema.scope.replace(
3018
+ "#/properties/",
3019
+ ""
3020
+ );
3021
+ const fieldSchema = schema?.properties?.[propertyPath];
3022
+ if (!fieldSchema) return false;
3023
+ return fieldSchema.type === "integer" || fieldSchema.type === "number";
3024
+ }
3025
+ );
3026
+ var NumberRendererControl = withJsonFormsControlProps(NumberRenderer);
2893
3027
  var TextareaRenderer = ({
2894
3028
  data,
2895
3029
  handleChange,
@@ -3059,6 +3193,7 @@ var renderers = [
3059
3193
  { tester: modelIdRendererTester, renderer: ModelIdRendererControl },
3060
3194
  { tester: booleanRendererTester, renderer: BooleanRendererControl },
3061
3195
  { tester: dropdownRendererTester, renderer: DropdownRendererControl },
3196
+ { tester: numberRendererTester, renderer: NumberRendererControl },
3062
3197
  { tester: textareaRendererTester, renderer: TextareaRendererControl },
3063
3198
  ...vanillaRenderers,
3064
3199
  { tester: jsonEditorTester, renderer: JsonEditorRendererControl }
@@ -3112,26 +3247,26 @@ function useChatVariablesFormVm({
3112
3247
  const { mutate: updateVariableMutation } = useUpdateFlowRunVariable();
3113
3248
  const querySettled = !isLoading && (threadVars !== void 0 || isError);
3114
3249
  const shouldUseDefaults = isError || threadVars && Object.keys(threadVars).length === 0;
3115
- const built = React8.useMemo(() => {
3250
+ const built = React9.useMemo(() => {
3116
3251
  return buildSimpleSchemaAndUi(
3117
3252
  workspace.variables,
3118
3253
  threadVars,
3119
3254
  shouldUseDefaults ?? false
3120
3255
  );
3121
3256
  }, [workspace.variables, threadVars, shouldUseDefaults]);
3122
- const [data, setData] = React8.useState(null);
3123
- React8.useEffect(() => {
3257
+ const [data, setData] = React9.useState(null);
3258
+ React9.useEffect(() => {
3124
3259
  if (querySettled) {
3125
3260
  setData(built.initialData);
3126
3261
  setVariables(built.initialData);
3127
3262
  }
3128
3263
  }, [querySettled, built.initialData, setVariables]);
3129
- const ajv = React8.useMemo(() => createAjv({ useDefaults: false }), []);
3130
- const prevRef = React8.useRef(null);
3131
- React8.useEffect(() => {
3264
+ const ajv = React9.useMemo(() => createAjv({ useDefaults: false }), []);
3265
+ const prevRef = React9.useRef(null);
3266
+ React9.useEffect(() => {
3132
3267
  prevRef.current = data;
3133
3268
  }, [data]);
3134
- const onChange = React8.useCallback(
3269
+ const onChange = React9.useCallback(
3135
3270
  ({ data: next2 }) => {
3136
3271
  if (prevRef.current && !isDraftThreadId(threadId)) {
3137
3272
  const keys2 = Object.keys(workspace.variables || {});
@@ -3152,7 +3287,7 @@ function useChatVariablesFormVm({
3152
3287
  },
3153
3288
  [workspace.variables, setVariables, updateVariableMutation, threadId]
3154
3289
  );
3155
- const config = React8.useMemo(
3290
+ const config = React9.useMemo(
3156
3291
  () => ({
3157
3292
  restrict: true,
3158
3293
  trim: false,
@@ -3221,7 +3356,20 @@ var threadsKeys = {
3221
3356
  };
3222
3357
 
3223
3358
  // src/domains/threads/cache.ts
3359
+ function isStaleSummary(incoming, existing) {
3360
+ if (!existing) return false;
3361
+ if (typeof existing.summaryEmittedAt !== "number") return false;
3362
+ if (typeof incoming.summaryEmittedAt !== "number") return false;
3363
+ if (incoming.summaryEmittedAt >= existing.summaryEmittedAt) return false;
3364
+ return existing.isFlowRunning === false && incoming.isFlowRunning === true;
3365
+ }
3224
3366
  function applyThreadToCache(qc, thread) {
3367
+ const existingDetail = qc.getQueryData(
3368
+ threadsKeys.detail(thread.workSpaceId, thread.id)
3369
+ );
3370
+ if (isStaleSummary(thread, existingDetail)) {
3371
+ return false;
3372
+ }
3225
3373
  qc.setQueryData(
3226
3374
  threadsKeys.detail(thread.workSpaceId, thread.id),
3227
3375
  (old) => ({ ...old ?? thread, ...thread })
@@ -3242,6 +3390,7 @@ function applyThreadToCache(qc, thread) {
3242
3390
  if (!page?.data) return page;
3243
3391
  const idx2 = page.data.findIndex((t) => t.id === thread.id);
3244
3392
  if (idx2 === -1) return page;
3393
+ if (isStaleSummary(thread, page.data[idx2])) return page;
3245
3394
  changed = true;
3246
3395
  foundInList = true;
3247
3396
  const nextData2 = page.data.slice();
@@ -3254,6 +3403,7 @@ function applyThreadToCache(qc, thread) {
3254
3403
  if (!list2.data) return old;
3255
3404
  const idx = list2.data.findIndex((t) => t.id === thread.id);
3256
3405
  if (idx === -1) return old;
3406
+ if (isStaleSummary(thread, list2.data[idx])) return old;
3257
3407
  foundInList = true;
3258
3408
  const nextData = list2.data.slice();
3259
3409
  nextData[idx] = { ...nextData[idx], ...thread };
@@ -3310,7 +3460,7 @@ function utcDate(value) {
3310
3460
  }
3311
3461
  return new Date(value);
3312
3462
  }
3313
- z.preprocess((val) => {
3463
+ var DateFromApi = z.preprocess((val) => {
3314
3464
  if (typeof val === "string" && !hasTimezone(val)) {
3315
3465
  return val + "Z";
3316
3466
  }
@@ -3323,36 +3473,40 @@ var {
3323
3473
  messageThreadsGetMessageThreadWorkspacesWorkspaceIdMessagethreadsIdResponse: threadResponseSchema
3324
3474
  } = ChatZod;
3325
3475
  function mapThreadDtoToModel(dto) {
3476
+ const lastUpdatedAt = utcDate(dto.lastUpdatedAt);
3326
3477
  return {
3327
3478
  id: dto.id,
3328
3479
  createdAt: utcDate(dto.createdAt),
3329
3480
  createdBy: dto.createdBy ?? "",
3330
3481
  createdByUserId: dto.createdByUserId,
3331
3482
  isFlowRunning: dto.isFlowRunning,
3332
- lastUpdatedAt: utcDate(dto.lastUpdatedAt),
3483
+ lastUpdatedAt,
3333
3484
  lastUpdatedByUserId: dto.lastUpdatedByUserId,
3334
3485
  name: dto.name ?? "",
3335
3486
  totalMessages: dto.totalMessages,
3336
3487
  pinned: dto.favorited,
3337
- workSpaceId: dto.workSpaceId
3488
+ workSpaceId: dto.workSpaceId,
3489
+ summaryEmittedAt: lastUpdatedAt.getTime()
3338
3490
  };
3339
3491
  }
3340
3492
  function mapThreadsResponseDtoToModel(dto) {
3341
3493
  return { data: dto.data.map(mapThreadDtoToModel), total: dto.total };
3342
3494
  }
3343
3495
  function mapSignalRThreadSummaryToModel(summary) {
3496
+ const lastUpdatedAt = utcDate(summary.lastUpdatedAt);
3344
3497
  return {
3345
3498
  id: summary.id,
3346
3499
  createdAt: utcDate(summary.createdAt),
3347
3500
  createdBy: summary.createdBy ?? "",
3348
3501
  createdByUserId: summary.createdByUserId,
3349
3502
  isFlowRunning: summary.isFlowRunning,
3350
- lastUpdatedAt: utcDate(summary.lastUpdatedAt),
3503
+ lastUpdatedAt,
3351
3504
  lastUpdatedByUserId: summary.lastUpdatedByUserId,
3352
3505
  name: summary.name ?? "",
3353
3506
  totalMessages: summary.totalMessages,
3354
3507
  pinned: summary.favorited,
3355
- workSpaceId: summary.workSpaceId
3508
+ workSpaceId: summary.workSpaceId,
3509
+ summaryEmittedAt: lastUpdatedAt.getTime()
3356
3510
  };
3357
3511
  }
3358
3512
  var threadDetailOptions = ({
@@ -3406,11 +3560,12 @@ var useThread = ({
3406
3560
  });
3407
3561
  };
3408
3562
  var useThreadIsRunning = (workspaceId, threadId) => {
3409
- const { data: thread } = useThread({
3410
- workspaceId: workspaceId ?? "",
3411
- threadId: threadId ?? "",
3412
- enabled: !!workspaceId && !!threadId
3563
+ const queryClient = useQueryClient();
3564
+ const { data: detailThread } = useQuery({
3565
+ queryKey: threadsKeys.detail(workspaceId ?? "", threadId ?? ""),
3566
+ queryFn: skipToken
3413
3567
  });
3568
+ const listThread = workspaceId && threadId ? getThreadPlaceholderFromListCache(queryClient, workspaceId, threadId) : void 0;
3414
3569
  const { data: optimistic } = useQuery({
3415
3570
  queryKey: threadsKeys.optimisticRunning(threadId ?? ""),
3416
3571
  queryFn: () => false,
@@ -3418,7 +3573,7 @@ var useThreadIsRunning = (workspaceId, threadId) => {
3418
3573
  staleTime: Infinity,
3419
3574
  enabled: !!threadId
3420
3575
  });
3421
- return !!optimistic || !!thread?.isFlowRunning;
3576
+ return !!optimistic || !!(detailThread ?? listThread)?.isFlowRunning;
3422
3577
  };
3423
3578
 
3424
3579
  // src/domains/messages/enums.ts
@@ -3444,6 +3599,15 @@ var messagesMutationsKeys = {
3444
3599
  };
3445
3600
 
3446
3601
  // src/domains/messages/mutations.ts
3602
+ function reconcileWithMessage(old, incoming, onDuplicate = "keep-existing") {
3603
+ const stable = old.filter((m) => !m.optimistic);
3604
+ const idx = stable.findIndex((m) => m.id === incoming.id);
3605
+ if (idx === -1) return [...stable, incoming];
3606
+ if (onDuplicate === "keep-existing") return stable;
3607
+ const copy = stable.slice();
3608
+ copy[idx] = incoming;
3609
+ return copy;
3610
+ }
3447
3611
  function useSendMessage() {
3448
3612
  const qc = useQueryClient();
3449
3613
  const { userId, displayName: userName } = useChatIdentity();
@@ -3459,10 +3623,10 @@ function useSendMessage() {
3459
3623
  if (!threadId) throw new Error("Thread ID is required");
3460
3624
  if (!workspaceId) throw new Error("Workspace ID is required");
3461
3625
  const optimistic = {
3462
- id: `temp-${Date.now()}`,
3626
+ id: `temp-${crypto.randomUUID()}`,
3463
3627
  values: [
3464
3628
  {
3465
- id: `temp-${Date.now()}-prompt`,
3629
+ id: `temp-${crypto.randomUUID()}-prompt`,
3466
3630
  type: "Input" /* INPUT */,
3467
3631
  name: "prompt",
3468
3632
  value: contentList,
@@ -3473,7 +3637,7 @@ function useSendMessage() {
3473
3637
  },
3474
3638
  ...files?.length ? [
3475
3639
  {
3476
- id: `temp-${Date.now()}-files`,
3640
+ id: `temp-${crypto.randomUUID()}-files`,
3477
3641
  type: "Input" /* INPUT */,
3478
3642
  name: "files",
3479
3643
  value: files,
@@ -3485,7 +3649,7 @@ function useSendMessage() {
3485
3649
  ] : [],
3486
3650
  ...variables && Object.keys(variables).length ? [
3487
3651
  {
3488
- id: `temp-${Date.now()}-vars`,
3652
+ id: `temp-${crypto.randomUUID()}-vars`,
3489
3653
  type: "Input" /* INPUT */,
3490
3654
  name: "variables",
3491
3655
  value: variables,
@@ -3527,13 +3691,10 @@ function useSendMessage() {
3527
3691
  toast.error("There was an error posting your message");
3528
3692
  throw err;
3529
3693
  }
3530
- qc.setQueryData(messagesKeys.list(threadId), (old = []) => {
3531
- const withoutOptimistic = old.filter((m) => !m.optimistic);
3532
- const alreadyPresent = withoutOptimistic.some(
3533
- (m) => m.id === realMessage.id
3534
- );
3535
- return alreadyPresent ? withoutOptimistic : [...withoutOptimistic, realMessage];
3536
- });
3694
+ qc.setQueryData(
3695
+ messagesKeys.list(threadId),
3696
+ (old = []) => reconcileWithMessage(old, realMessage)
3697
+ );
3537
3698
  qc.setQueryData(
3538
3699
  threadsKeys.detail(workspaceId, threadId),
3539
3700
  (old) => old ? { ...old, isFlowRunning: true } : old
@@ -3589,14 +3750,10 @@ function useAddInputToMessage() {
3589
3750
  });
3590
3751
  },
3591
3752
  onSuccess: (message, { threadId }) => {
3592
- qc.setQueryData(messagesKeys.list(threadId), (old = []) => {
3593
- const stable = old.filter((x) => !x.optimistic);
3594
- const idx = stable.findIndex((x) => x.id === message.id);
3595
- if (idx === -1) return [...stable, message];
3596
- const copy = stable.slice();
3597
- copy[idx] = message;
3598
- return copy;
3599
- });
3753
+ qc.setQueryData(
3754
+ messagesKeys.list(threadId),
3755
+ (old = []) => reconcileWithMessage(old, message, "replace")
3756
+ );
3600
3757
  },
3601
3758
  onError: (_e, { threadId }) => {
3602
3759
  qc.setQueryData(
@@ -3633,7 +3790,10 @@ var workspaceDetailOptions = ({
3633
3790
  });
3634
3791
  function useWorkspace(workspaceId) {
3635
3792
  const service = useChatService();
3636
- return useQuery(workspaceDetailOptions({ service, workspaceId }));
3793
+ return useQuery({
3794
+ ...workspaceDetailOptions({ service, workspaceId }),
3795
+ enabled: !!workspaceId
3796
+ });
3637
3797
  }
3638
3798
  var taggableUsersOptions = ({
3639
3799
  service,
@@ -3821,11 +3981,13 @@ function MessageComposer({
3821
3981
  workspaceId,
3822
3982
  threadId: isDraftThread ? void 0 : threadId
3823
3983
  });
3824
- if (typeof window !== "undefined") {
3825
- window.__ssDownloadFile = async (id) => {
3826
- return await getFileBlobUrl(id);
3984
+ useEffect(() => {
3985
+ if (typeof window === "undefined") return;
3986
+ window.__ssDownloadFile = (id) => getFileBlobUrl(id);
3987
+ return () => {
3988
+ if (window.__ssDownloadFile) delete window.__ssDownloadFile;
3827
3989
  };
3828
- }
3990
+ }, [getFileBlobUrl]);
3829
3991
  const onUploadFiles = async (files) => {
3830
3992
  const res = await uploadFilesMutation.mutateAsync(files);
3831
3993
  return res.map(({ id, name }) => ({ id, name }));
@@ -4233,43 +4395,14 @@ function MessageComposer({
4233
4395
  )
4234
4396
  ] });
4235
4397
  }
4236
- function getPromptSignature(m) {
4237
- const prompt = m.values?.find(
4238
- (v) => v.type === "Input" /* INPUT */ && v.name === "prompt"
4239
- );
4240
- if (!prompt) return null;
4241
- try {
4242
- return JSON.stringify(prompt.value ?? null);
4243
- } catch {
4244
- return null;
4245
- }
4246
- }
4247
- function mergeFetchedWithOptimistics(current, fetched) {
4248
- if (!current?.length) return fetched;
4249
- const optimistics = current.filter((m) => m.optimistic);
4250
- if (!optimistics.length) return fetched;
4251
- const fetchedPromptSigs = new Set(
4252
- fetched.map((m) => getPromptSignature(m)).filter((s2) => typeof s2 === "string" && s2.length > 0)
4253
- );
4254
- const dedupedOptimistics = optimistics.filter((o) => {
4255
- const sig = getPromptSignature(o);
4256
- if (!sig) return true;
4257
- return !fetchedPromptSigs.has(sig);
4258
- });
4259
- return [...fetched, ...dedupedOptimistics];
4260
- }
4261
4398
  var messagesListOptions = (service, threadId, opts) => queryOptions({
4262
4399
  queryKey: threadId ? messagesKeys.list(threadId) : messagesKeys.lists(),
4263
4400
  // NOTE: queryKey intentionally does NOT include opts. This keeps cache updates from
4264
4401
  // message mutations (which write to messagesKeys.list(threadId)) working.
4265
4402
  // If opts changes (e.g. user clicks "Load full history"), we manually refetch.
4266
- queryFn: async (ctx) => {
4403
+ queryFn: async () => {
4267
4404
  if (!threadId) return [];
4268
- const fetched = (await service.fetchMessages(threadId, opts)).reverse();
4269
- const current = ctx.client.getQueryData(
4270
- messagesKeys.list(threadId)
4271
- );
4272
- return mergeFetchedWithOptimistics(current, fetched);
4405
+ return (await service.fetchMessages(threadId, opts)).reverse();
4273
4406
  },
4274
4407
  retry: false,
4275
4408
  refetchOnWindowFocus: false,
@@ -4277,15 +4410,13 @@ var messagesListOptions = (service, threadId, opts) => queryOptions({
4277
4410
  // Avoid re-fetching the entire thread on every small navigation.
4278
4411
  staleTime: 3e4
4279
4412
  });
4280
- function useMessages(threadId, opts) {
4413
+ function useMessages(threadId) {
4281
4414
  const service = useChatService();
4282
4415
  const isDraft = isDraftThreadId(threadId);
4283
- const skipFetch = opts?.skipWhenNewThread || !threadId || isDraft;
4284
- const listOpts = opts?.take != null || opts?.skip != null ? { take: opts.take, skip: opts.skip } : void 0;
4285
4416
  return useQuery({
4286
- ...messagesListOptions(service, threadId, listOpts),
4287
- enabled: !opts?.skipWhenNewThread && !!threadId && !isDraft,
4288
- initialData: skipFetch ? [] : void 0
4417
+ ...messagesListOptions(service, threadId),
4418
+ enabled: !!threadId && !isDraft,
4419
+ initialData: !threadId || isDraft ? [] : void 0
4289
4420
  });
4290
4421
  }
4291
4422
 
@@ -18792,7 +18923,8 @@ function SsImage(props) {
18792
18923
  alt: alt ?? "",
18793
18924
  title,
18794
18925
  width: finalWidth,
18795
- height: finalHeight
18926
+ height: finalHeight,
18927
+ style: !resolvedSrc && !errored ? { visibility: "hidden" } : void 0
18796
18928
  }
18797
18929
  )
18798
18930
  ]
@@ -19068,7 +19200,7 @@ function getAvatarColour(name) {
19068
19200
  const textColor = brightness > 128 ? "#000000" : "#FFFFFF";
19069
19201
  return { backgroundColor, textColor };
19070
19202
  }
19071
- var Avatar = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
19203
+ var Avatar = React9.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
19072
19204
  "div",
19073
19205
  {
19074
19206
  ref,
@@ -19080,7 +19212,7 @@ var Avatar = React8.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
19080
19212
  }
19081
19213
  ));
19082
19214
  Avatar.displayName = "Avatar";
19083
- var AvatarImage = React8.forwardRef(({ className, alt, src, children: children2, ...props }, _ref) => /* @__PURE__ */ jsx(
19215
+ var AvatarImage = React9.forwardRef(({ className, alt, src, children: children2, ...props }, _ref) => /* @__PURE__ */ jsx(
19084
19216
  MuiAvatar,
19085
19217
  {
19086
19218
  className: cn("aspect-square h-full w-full", className),
@@ -19091,7 +19223,7 @@ var AvatarImage = React8.forwardRef(({ className, alt, src, children: children2,
19091
19223
  }
19092
19224
  ));
19093
19225
  AvatarImage.displayName = "AvatarImage";
19094
- var AvatarFallback = React8.forwardRef(
19226
+ var AvatarFallback = React9.forwardRef(
19095
19227
  ({ className, colored = true, ...props }, ref) => {
19096
19228
  const childText = String(props.children ?? "");
19097
19229
  const colours = colored ? getAvatarColour(childText) : void 0;
@@ -19118,14 +19250,20 @@ dayjs.extend(relativeTime);
19118
19250
  dayjs.extend(advancedFormat);
19119
19251
  function parseDateTime(date, customFormat) {
19120
19252
  const d = dayjs.utc(date).local();
19121
- return d.format(customFormat);
19253
+ if (customFormat === "X") return Math.floor(d.valueOf() / 1e3).toString();
19254
+ if (customFormat === "x") return d.valueOf().toString();
19255
+ return d.format(customFormat ?? "YYYY-MM-DD HH:mm:ss");
19256
+ }
19257
+ function parseDateTimeHuman(date) {
19258
+ return dayjs.utc(date).local().fromNow();
19122
19259
  }
19123
19260
 
19124
19261
  // src/shared/utils/userPhoto.ts
19125
19262
  function getChatApiBaseUrl() {
19126
19263
  try {
19127
19264
  const w = window;
19128
- const cfg = w?.ssconfig?.Chat_Api_Uri ?? import.meta.env.VITE_CHAT_API_URI;
19265
+ const env2 = import.meta.env;
19266
+ const cfg = w?.ssconfig?.Chat_Api_Uri ?? env2?.VITE_CHAT_API_URI;
19129
19267
  return typeof cfg === "string" && cfg.trim() ? cfg.trim() : "";
19130
19268
  } catch {
19131
19269
  return "";
@@ -19619,7 +19757,19 @@ var MessageItem = ({
19619
19757
  const t = d.getTime();
19620
19758
  return Number.isFinite(t) ? t : 0;
19621
19759
  };
19622
- const values = (message.values ?? []).slice().sort((a, b) => safeTime(a.createdAt) - safeTime(b.createdAt));
19760
+ const sortedValues = (message.values ?? []).slice().sort((a, b) => safeTime(a.createdAt) - safeTime(b.createdAt));
19761
+ const slotByKey = /* @__PURE__ */ new Map();
19762
+ const values = [];
19763
+ for (const v of sortedValues) {
19764
+ const key = `${v.name}|${v.type}`;
19765
+ const existing = slotByKey.get(key);
19766
+ if (existing !== void 0) {
19767
+ values[existing] = v;
19768
+ } else {
19769
+ slotByKey.set(key, values.length);
19770
+ values.push(v);
19771
+ }
19772
+ }
19623
19773
  const bubbles = [];
19624
19774
  let groupContent = [];
19625
19775
  let groupSources = [];
@@ -19663,7 +19813,8 @@ var MessageItem = ({
19663
19813
  groupType = v.type;
19664
19814
  const name = v.name.toLowerCase();
19665
19815
  switch (name) {
19666
- case "variables": {
19816
+ case "variables":
19817
+ case "userinfo": {
19667
19818
  continue;
19668
19819
  }
19669
19820
  case "status": {
@@ -19804,6 +19955,10 @@ function MessageList({
19804
19955
  const messagesEndRef = useRef(null);
19805
19956
  const prevMessageCountRef = useRef(0);
19806
19957
  const hasInitialScrollRef = useRef(false);
19958
+ const everHadMessagesRef = useRef({
19959
+ threadId: "",
19960
+ had: false
19961
+ });
19807
19962
  const isMobile = useIsMobile();
19808
19963
  const { data: activeWorkspace } = useWorkspace(workspaceId);
19809
19964
  const [isAtBottom, setIsAtBottom] = useState(true);
@@ -19875,8 +20030,15 @@ function MessageList({
19875
20030
  ro.observe(content);
19876
20031
  return () => ro.disconnect();
19877
20032
  }, [isAtBottom, scrollToBottom]);
20033
+ const safeMessages = messages ?? [];
20034
+ if (everHadMessagesRef.current.threadId !== threadId) {
20035
+ everHadMessagesRef.current = { threadId, had: safeMessages.length > 0 };
20036
+ } else if (safeMessages.length > 0) {
20037
+ everHadMessagesRef.current.had = true;
20038
+ }
20039
+ const hadMessagesBefore = everHadMessagesRef.current.had;
19878
20040
  const isLoading = isChoosingThread || (threadPending || threadFetching) && !thread || (messagesPending || messagesFetching) && messages === void 0;
19879
- if (isLoading) {
20041
+ if (isLoading && !hadMessagesBefore) {
19880
20042
  return /* @__PURE__ */ jsx(
19881
20043
  "div",
19882
20044
  {
@@ -19892,7 +20054,7 @@ function MessageList({
19892
20054
  }
19893
20055
  );
19894
20056
  }
19895
- if (threadError || messagesError) {
20057
+ if ((threadError || messagesError) && !hadMessagesBefore) {
19896
20058
  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: [
19897
20059
  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: [
19898
20060
  /* @__PURE__ */ jsx(AlertTriangle, { className: "h-4 w-4" }),
@@ -19904,8 +20066,7 @@ function MessageList({
19904
20066
  ] })
19905
20067
  ] }) });
19906
20068
  }
19907
- const safeMessages = messages ?? [];
19908
- if (safeMessages.length === 0) {
20069
+ if (safeMessages.length === 0 && !hadMessagesBefore) {
19909
20070
  return /* @__PURE__ */ jsxs("div", { className: "flex overflow-auto flex-shrink-10 flex-col p-8 text-center", children: [
19910
20071
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-medium mb-2", children: activeWorkspace?.name ?? "No messages yet" }),
19911
20072
  activeWorkspace?.firstPrompt && /* @__PURE__ */ jsx("div", { className: "max-w-3xl mx-auto p-4", children: /* @__PURE__ */ jsx(MessageMarkdown, { value: activeWorkspace.firstPrompt }) })
@@ -20146,26 +20307,28 @@ function mapWorkspaceDtoToModel(dto) {
20146
20307
  id: dto.id ?? "",
20147
20308
  name: dto.name ?? "",
20148
20309
  tags: dto.tags ?? [],
20149
- showSources: dto.showSources ?? void 0,
20150
- dataSpaces: Array.isArray(dto.dataSpaces) ? dto.dataSpaces : void 0,
20310
+ showSources: truthy(dto.showSources),
20311
+ dataSpaces: Array.isArray(dto.dataSpaces) ? dto.dataSpaces : [],
20151
20312
  createdByUserId: dto.createdByUserId ?? void 0,
20152
20313
  createdAt: dto.createdAt != null ? utcDate(dto.createdAt) : void 0,
20153
20314
  modifiedByUserId: dto.modifiedByUserId ?? void 0,
20154
20315
  modifiedAt: dto.modifiedAt != null ? utcDate(dto.modifiedAt) : void 0,
20155
20316
  favorited: truthy(dto.favorited),
20156
- summary: dto.summary ?? void 0,
20157
- firstPrompt: dto.firstPrompt ?? void 0,
20317
+ summary: dto.summary ?? "",
20318
+ firstPrompt: dto.firstPrompt ?? "",
20158
20319
  outputSchema: dto.outputSchema ?? void 0,
20159
- isPromptAndResponseLoggingEnabled: dto.isPromptAndResponseLoggingEnabled ?? void 0,
20160
20320
  inputs: dto.inputs ?? void 0,
20321
+ isPromptAndResponseLoggingEnabled: truthy(
20322
+ dto.isPromptAndResponseLoggingEnabled
20323
+ ),
20161
20324
  variables,
20162
20325
  sandBoxThreadId: dto.sandBoxThreadId ?? void 0,
20163
- supportsFiles: dto.supportsFiles ?? void 0,
20326
+ supportsFiles: truthy(dto.supportsFiles),
20164
20327
  avatarName: computeAvatar(dto.name ?? "")
20165
20328
  };
20166
20329
  }
20167
20330
  var mapWorkspacesDtoToModels = (arr) => arr.map(mapWorkspaceDtoToModel);
20168
20331
 
20169
- export { ChatProvider, ChatVariablesForm, DRAFT_THREAD_PREFIX, MarkdownEditor, MessageComposer, MessageList, MessageListSkeleton, MessageMarkdown, MessageValueType, NEW_THREAD_ID, THREAD_LIST_PAGE_SIZE, applyDeltaToMessage, applyThreadToCache, createDraftThreadId, createThreadId, downloadFileBlobOptions, filesKeys, flowRunsKeys, getModelIcon, getThreadPlaceholderFromListCache, invalidateWorkspaceThreadLists, isDraftThreadId, mapFileInfoDtoToModel, mapMentionUserDtoToModel, mapMessageDtoToModel, mapMessageErrorDtoToModel, mapMessageValueDtoToModel, mapMessagesDtoToModels, mapSignalRThreadSummaryToModel, mapThreadDtoToModel, mapThreadsResponseDtoToModel, mapWorkspaceDtoToModel, mapWorkspacesDtoToModels, markDraftThreadId, messagesKeys, messagesListOptions, messagesMutationsKeys, modelsKeys, setThreadOptimisticRunning, setThreadRunningInLists, taggableUsersOptions, threadDetailOptions, threadsKeys, unmarkDraftThreadId, useAddInputToMessage, useChatContext, useChatIdentity, useChatService, useDownloadFileBlobQuery, useFileMutations, useFlowRunVariables, useMessages, useModels, useSendMessage, useTaggableWorkspaceUsers, useThread, useThreadIsRunning, useUpdateFlowRunVariable, useWorkspace, workspaceDetailOptions, workspaceKeys };
20332
+ 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 };
20170
20333
  //# sourceMappingURL=index.js.map
20171
20334
  //# sourceMappingURL=index.js.map