@smartspace/chat-ui 1.13.1-dev.aa44752 → 1.13.1-dev.b2f9256
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.d.ts +8 -14
- package/dist/index.js +54 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -238,6 +238,14 @@ type MessageThread = {
|
|
|
238
238
|
totalMessages: number;
|
|
239
239
|
pinned: boolean;
|
|
240
240
|
workSpaceId: string;
|
|
241
|
+
/**
|
|
242
|
+
* Monotonic version of when this summary was emitted (epoch ms). Used by
|
|
243
|
+
* `applyThreadToCache` to reject stale writes — e.g. a SignalR summary
|
|
244
|
+
* landing after a fresher SSE thread frame because the server's DB write
|
|
245
|
+
* lagged Redis. Mappers derive this from `lastUpdatedAt`; client-side
|
|
246
|
+
* writers like `ensureDraftThread` use `Date.now()`.
|
|
247
|
+
*/
|
|
248
|
+
summaryEmittedAt: number;
|
|
241
249
|
};
|
|
242
250
|
type ThreadsResponse = {
|
|
243
251
|
data: MessageThread[];
|
|
@@ -969,20 +977,6 @@ declare function mapMentionUserDtoToModel(dto: MentionUserDto): MentionUser;
|
|
|
969
977
|
declare function mapWorkspaceDtoToModel(dto: WorkspaceDto): Workspace;
|
|
970
978
|
declare const mapWorkspacesDtoToModels: (arr: WorkspacesListItemDto[]) => Workspace[];
|
|
971
979
|
|
|
972
|
-
/**
|
|
973
|
-
* Write a freshly-observed thread (from SignalR or an SSE thread frame)
|
|
974
|
-
* directly into the relevant query caches so subscribers paint without a
|
|
975
|
-
* refetch roundtrip.
|
|
976
|
-
*
|
|
977
|
-
* - Merges into `threadsKeys.detail(workspaceId, thread.id)`.
|
|
978
|
-
* - Splices into every threads-list cache for the workspace, handling both
|
|
979
|
-
* finite `ThreadsResponse` and infinite `{ pages, pageParams }` shapes.
|
|
980
|
-
*
|
|
981
|
-
* Returns `true` when the thread was found in at least one list cache.
|
|
982
|
-
* Callers that need to surface brand-new threads (e.g. another user just
|
|
983
|
-
* created one) can fall back to invalidating the list queries when this
|
|
984
|
-
* returns `false`.
|
|
985
|
-
*/
|
|
986
980
|
declare function applyThreadToCache(qc: QueryClient, thread: MessageThread): boolean;
|
|
987
981
|
/**
|
|
988
982
|
* Invalidate every threads-list cache for a workspace. Use as a fallback when
|
package/dist/index.js
CHANGED
|
@@ -3233,7 +3233,20 @@ var threadsKeys = {
|
|
|
3233
3233
|
};
|
|
3234
3234
|
|
|
3235
3235
|
// src/domains/threads/cache.ts
|
|
3236
|
+
function isStaleSummary(incoming, existing) {
|
|
3237
|
+
if (!existing) return false;
|
|
3238
|
+
if (typeof existing.summaryEmittedAt !== "number") return false;
|
|
3239
|
+
if (typeof incoming.summaryEmittedAt !== "number") return false;
|
|
3240
|
+
if (incoming.summaryEmittedAt >= existing.summaryEmittedAt) return false;
|
|
3241
|
+
return existing.isFlowRunning === false && incoming.isFlowRunning === true;
|
|
3242
|
+
}
|
|
3236
3243
|
function applyThreadToCache(qc, thread) {
|
|
3244
|
+
const existingDetail = qc.getQueryData(
|
|
3245
|
+
threadsKeys.detail(thread.workSpaceId, thread.id)
|
|
3246
|
+
);
|
|
3247
|
+
if (isStaleSummary(thread, existingDetail)) {
|
|
3248
|
+
return false;
|
|
3249
|
+
}
|
|
3237
3250
|
qc.setQueryData(
|
|
3238
3251
|
threadsKeys.detail(thread.workSpaceId, thread.id),
|
|
3239
3252
|
(old) => ({ ...old ?? thread, ...thread })
|
|
@@ -3254,6 +3267,7 @@ function applyThreadToCache(qc, thread) {
|
|
|
3254
3267
|
if (!page?.data) return page;
|
|
3255
3268
|
const idx2 = page.data.findIndex((t) => t.id === thread.id);
|
|
3256
3269
|
if (idx2 === -1) return page;
|
|
3270
|
+
if (isStaleSummary(thread, page.data[idx2])) return page;
|
|
3257
3271
|
changed = true;
|
|
3258
3272
|
foundInList = true;
|
|
3259
3273
|
const nextData2 = page.data.slice();
|
|
@@ -3266,6 +3280,7 @@ function applyThreadToCache(qc, thread) {
|
|
|
3266
3280
|
if (!list2.data) return old;
|
|
3267
3281
|
const idx = list2.data.findIndex((t) => t.id === thread.id);
|
|
3268
3282
|
if (idx === -1) return old;
|
|
3283
|
+
if (isStaleSummary(thread, list2.data[idx])) return old;
|
|
3269
3284
|
foundInList = true;
|
|
3270
3285
|
const nextData = list2.data.slice();
|
|
3271
3286
|
nextData[idx] = { ...nextData[idx], ...thread };
|
|
@@ -3335,36 +3350,40 @@ var {
|
|
|
3335
3350
|
messageThreadsGetMessageThreadWorkspacesWorkspaceIdMessagethreadsIdResponse: threadResponseSchema
|
|
3336
3351
|
} = ChatZod;
|
|
3337
3352
|
function mapThreadDtoToModel(dto) {
|
|
3353
|
+
const lastUpdatedAt = utcDate(dto.lastUpdatedAt);
|
|
3338
3354
|
return {
|
|
3339
3355
|
id: dto.id,
|
|
3340
3356
|
createdAt: utcDate(dto.createdAt),
|
|
3341
3357
|
createdBy: dto.createdBy ?? "",
|
|
3342
3358
|
createdByUserId: dto.createdByUserId,
|
|
3343
3359
|
isFlowRunning: dto.isFlowRunning,
|
|
3344
|
-
lastUpdatedAt
|
|
3360
|
+
lastUpdatedAt,
|
|
3345
3361
|
lastUpdatedByUserId: dto.lastUpdatedByUserId,
|
|
3346
3362
|
name: dto.name ?? "",
|
|
3347
3363
|
totalMessages: dto.totalMessages,
|
|
3348
3364
|
pinned: dto.favorited,
|
|
3349
|
-
workSpaceId: dto.workSpaceId
|
|
3365
|
+
workSpaceId: dto.workSpaceId,
|
|
3366
|
+
summaryEmittedAt: lastUpdatedAt.getTime()
|
|
3350
3367
|
};
|
|
3351
3368
|
}
|
|
3352
3369
|
function mapThreadsResponseDtoToModel(dto) {
|
|
3353
3370
|
return { data: dto.data.map(mapThreadDtoToModel), total: dto.total };
|
|
3354
3371
|
}
|
|
3355
3372
|
function mapSignalRThreadSummaryToModel(summary) {
|
|
3373
|
+
const lastUpdatedAt = utcDate(summary.lastUpdatedAt);
|
|
3356
3374
|
return {
|
|
3357
3375
|
id: summary.id,
|
|
3358
3376
|
createdAt: utcDate(summary.createdAt),
|
|
3359
3377
|
createdBy: summary.createdBy ?? "",
|
|
3360
3378
|
createdByUserId: summary.createdByUserId,
|
|
3361
3379
|
isFlowRunning: summary.isFlowRunning,
|
|
3362
|
-
lastUpdatedAt
|
|
3380
|
+
lastUpdatedAt,
|
|
3363
3381
|
lastUpdatedByUserId: summary.lastUpdatedByUserId,
|
|
3364
3382
|
name: summary.name ?? "",
|
|
3365
3383
|
totalMessages: summary.totalMessages,
|
|
3366
3384
|
pinned: summary.favorited,
|
|
3367
|
-
workSpaceId: summary.workSpaceId
|
|
3385
|
+
workSpaceId: summary.workSpaceId,
|
|
3386
|
+
summaryEmittedAt: lastUpdatedAt.getTime()
|
|
3368
3387
|
};
|
|
3369
3388
|
}
|
|
3370
3389
|
var threadDetailOptions = ({
|
|
@@ -3457,6 +3476,15 @@ var messagesMutationsKeys = {
|
|
|
3457
3476
|
};
|
|
3458
3477
|
|
|
3459
3478
|
// src/domains/messages/mutations.ts
|
|
3479
|
+
function reconcileWithMessage(old, incoming, onDuplicate = "keep-existing") {
|
|
3480
|
+
const stable = old.filter((m) => !m.optimistic);
|
|
3481
|
+
const idx = stable.findIndex((m) => m.id === incoming.id);
|
|
3482
|
+
if (idx === -1) return [...stable, incoming];
|
|
3483
|
+
if (onDuplicate === "keep-existing") return stable;
|
|
3484
|
+
const copy = stable.slice();
|
|
3485
|
+
copy[idx] = incoming;
|
|
3486
|
+
return copy;
|
|
3487
|
+
}
|
|
3460
3488
|
function useSendMessage() {
|
|
3461
3489
|
const qc = useQueryClient();
|
|
3462
3490
|
const { userId, displayName: userName } = useChatIdentity();
|
|
@@ -3540,7 +3568,10 @@ function useSendMessage() {
|
|
|
3540
3568
|
toast.error("There was an error posting your message");
|
|
3541
3569
|
throw err;
|
|
3542
3570
|
}
|
|
3543
|
-
qc.setQueryData(
|
|
3571
|
+
qc.setQueryData(
|
|
3572
|
+
messagesKeys.list(threadId),
|
|
3573
|
+
(old = []) => reconcileWithMessage(old, realMessage)
|
|
3574
|
+
);
|
|
3544
3575
|
qc.setQueryData(
|
|
3545
3576
|
threadsKeys.detail(workspaceId, threadId),
|
|
3546
3577
|
(old) => old ? { ...old, isFlowRunning: true } : old
|
|
@@ -3596,14 +3627,10 @@ function useAddInputToMessage() {
|
|
|
3596
3627
|
});
|
|
3597
3628
|
},
|
|
3598
3629
|
onSuccess: (message, { threadId }) => {
|
|
3599
|
-
qc.setQueryData(
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
const copy = stable.slice();
|
|
3604
|
-
copy[idx] = message;
|
|
3605
|
-
return copy;
|
|
3606
|
-
});
|
|
3630
|
+
qc.setQueryData(
|
|
3631
|
+
messagesKeys.list(threadId),
|
|
3632
|
+
(old = []) => reconcileWithMessage(old, message, "replace")
|
|
3633
|
+
);
|
|
3607
3634
|
},
|
|
3608
3635
|
onError: (_e, { threadId }) => {
|
|
3609
3636
|
qc.setQueryData(
|
|
@@ -19804,6 +19831,10 @@ function MessageList({
|
|
|
19804
19831
|
const messagesEndRef = useRef(null);
|
|
19805
19832
|
const prevMessageCountRef = useRef(0);
|
|
19806
19833
|
const hasInitialScrollRef = useRef(false);
|
|
19834
|
+
const everHadMessagesRef = useRef({
|
|
19835
|
+
threadId: "",
|
|
19836
|
+
had: false
|
|
19837
|
+
});
|
|
19807
19838
|
const isMobile = useIsMobile();
|
|
19808
19839
|
const { data: activeWorkspace } = useWorkspace(workspaceId);
|
|
19809
19840
|
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
@@ -19875,8 +19906,15 @@ function MessageList({
|
|
|
19875
19906
|
ro.observe(content);
|
|
19876
19907
|
return () => ro.disconnect();
|
|
19877
19908
|
}, [isAtBottom, scrollToBottom]);
|
|
19909
|
+
const safeMessages = messages ?? [];
|
|
19910
|
+
if (everHadMessagesRef.current.threadId !== threadId) {
|
|
19911
|
+
everHadMessagesRef.current = { threadId, had: safeMessages.length > 0 };
|
|
19912
|
+
} else if (safeMessages.length > 0) {
|
|
19913
|
+
everHadMessagesRef.current.had = true;
|
|
19914
|
+
}
|
|
19915
|
+
const hadMessagesBefore = everHadMessagesRef.current.had;
|
|
19878
19916
|
const isLoading = isChoosingThread || (threadPending || threadFetching) && !thread || (messagesPending || messagesFetching) && messages === void 0;
|
|
19879
|
-
if (isLoading) {
|
|
19917
|
+
if (isLoading && !hadMessagesBefore) {
|
|
19880
19918
|
return /* @__PURE__ */ jsx(
|
|
19881
19919
|
"div",
|
|
19882
19920
|
{
|
|
@@ -19892,7 +19930,7 @@ function MessageList({
|
|
|
19892
19930
|
}
|
|
19893
19931
|
);
|
|
19894
19932
|
}
|
|
19895
|
-
if (threadError || messagesError) {
|
|
19933
|
+
if ((threadError || messagesError) && !hadMessagesBefore) {
|
|
19896
19934
|
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
19935
|
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
19936
|
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-4 w-4" }),
|
|
@@ -19904,8 +19942,7 @@ function MessageList({
|
|
|
19904
19942
|
] })
|
|
19905
19943
|
] }) });
|
|
19906
19944
|
}
|
|
19907
|
-
|
|
19908
|
-
if (safeMessages.length === 0) {
|
|
19945
|
+
if (safeMessages.length === 0 && !hadMessagesBefore) {
|
|
19909
19946
|
return /* @__PURE__ */ jsxs("div", { className: "flex overflow-auto flex-shrink-10 flex-col p-8 text-center", children: [
|
|
19910
19947
|
/* @__PURE__ */ jsx("h3", { className: "text-lg font-medium mb-2", children: activeWorkspace?.name ?? "No messages yet" }),
|
|
19911
19948
|
activeWorkspace?.firstPrompt && /* @__PURE__ */ jsx("div", { className: "max-w-3xl mx-auto p-4", children: /* @__PURE__ */ jsx(MessageMarkdown, { value: activeWorkspace.firstPrompt }) })
|