@xinghunm/ai-chat 1.3.2 → 1.4.0

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
@@ -46,7 +46,7 @@ __export(src_exports, {
46
46
  module.exports = __toCommonJS(src_exports);
47
47
 
48
48
  // src/components/ai-chat/index.tsx
49
- var import_react20 = require("react");
49
+ var import_react21 = require("react");
50
50
  var import_styled17 = __toESM(require("@emotion/styled"));
51
51
  var import_compass_ui4 = require("@xinghunm/compass-ui");
52
52
 
@@ -100,7 +100,10 @@ var DEFAULT_AI_CHAT_LABELS = {
100
100
  modelUnavailable: "No model available",
101
101
  skillLoading: "Loading skills...",
102
102
  skillEmpty: "No matching skills",
103
- removeSkillAriaLabel: "Remove skill"
103
+ removeSkillAriaLabel: "Remove skill",
104
+ sessionHistoryLoading: "Loading conversations...",
105
+ sessionHistoryLoadFailed: "Failed to load conversations",
106
+ sessionHistoryEmpty: "No conversations yet"
104
107
  };
105
108
 
106
109
  // src/lib/chat-session.ts
@@ -128,6 +131,20 @@ var createDraftChatSession = ({
128
131
  // src/store/chat-store.ts
129
132
  var DEFAULT_CHAT_SESSION_TITLE = "New Chat";
130
133
  var IMAGE_MESSAGE_SESSION_TITLE = "Image message";
134
+ var createHistoryMessagePaginationState = (page) => ({
135
+ previousCursor: page?.previousCursor ?? null,
136
+ hasMorePrevious: page?.hasMorePrevious ?? Boolean(page && page.previousCursor !== null),
137
+ isLoadingPrevious: false,
138
+ error: null
139
+ });
140
+ var normalizeHistoryMessages = (sessionId, messages) => messages.map((message) => ({ ...message, sessionId }));
141
+ var mergeOlderHistoryMessages = (sessionId, olderMessages, currentMessages) => {
142
+ const currentMessageIds = new Set(currentMessages.map((message) => message.id));
143
+ const uniqueOlderMessages = normalizeHistoryMessages(sessionId, olderMessages).filter(
144
+ (message) => !currentMessageIds.has(message.id)
145
+ );
146
+ return [...uniqueOlderMessages, ...currentMessages];
147
+ };
131
148
  var resolveSessionTitleFromMessage = (message) => {
132
149
  const trimmedContent = message.content.trim();
133
150
  if (trimmedContent) {
@@ -230,6 +247,9 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
230
247
  isStreamingBySession: {},
231
248
  isStoppingBySession: {},
232
249
  errorBySession: {},
250
+ sessionMessageLoadStatusBySession: {},
251
+ sessionMessageLoadErrorBySession: {},
252
+ historyMessagePaginationBySession: {},
233
253
  // ---- Session management ------------------------------------------------
234
254
  createSession: (session) => {
235
255
  const state = get();
@@ -243,6 +263,15 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
243
263
  const nextErrorBySession = { ...state.errorBySession };
244
264
  const nextIsStreamingBySession = { ...state.isStreamingBySession };
245
265
  const nextIsStoppingBySession = { ...state.isStoppingBySession };
266
+ const nextSessionMessageLoadStatusBySession = {
267
+ ...state.sessionMessageLoadStatusBySession
268
+ };
269
+ const nextSessionMessageLoadErrorBySession = {
270
+ ...state.sessionMessageLoadErrorBySession
271
+ };
272
+ const nextHistoryMessagePaginationBySession = {
273
+ ...state.historyMessagePaginationBySession
274
+ };
246
275
  const sid = session.sessionId;
247
276
  if (nextMessagesBySession[sid] === void 0)
248
277
  nextMessagesBySession[sid] = [];
@@ -252,13 +281,25 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
252
281
  nextIsStreamingBySession[sid] = false;
253
282
  if (nextIsStoppingBySession[sid] === void 0)
254
283
  nextIsStoppingBySession[sid] = false;
284
+ if (nextSessionMessageLoadStatusBySession[sid] === void 0) {
285
+ nextSessionMessageLoadStatusBySession[sid] = "loaded";
286
+ }
287
+ if (nextSessionMessageLoadErrorBySession[sid] === void 0) {
288
+ nextSessionMessageLoadErrorBySession[sid] = null;
289
+ }
290
+ if (nextHistoryMessagePaginationBySession[sid] === void 0) {
291
+ nextHistoryMessagePaginationBySession[sid] = createHistoryMessagePaginationState();
292
+ }
255
293
  set({
256
294
  sessions: nextSessions,
257
295
  activeSessionId: sid,
258
296
  messagesBySession: nextMessagesBySession,
259
297
  errorBySession: nextErrorBySession,
260
298
  isStreamingBySession: nextIsStreamingBySession,
261
- isStoppingBySession: nextIsStoppingBySession
299
+ isStoppingBySession: nextIsStoppingBySession,
300
+ sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
301
+ sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
302
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
262
303
  });
263
304
  },
264
305
  startNewChat: () => {
@@ -313,6 +354,27 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
313
354
  nextErrorBySession[nextSessionId] = nextErrorBySession[previousSessionId] ?? null;
314
355
  delete nextErrorBySession[previousSessionId];
315
356
  }
357
+ const nextSessionMessageLoadStatusBySession = {
358
+ ...state.sessionMessageLoadStatusBySession
359
+ };
360
+ if (previousSessionId in nextSessionMessageLoadStatusBySession) {
361
+ nextSessionMessageLoadStatusBySession[nextSessionId] = nextSessionMessageLoadStatusBySession[previousSessionId] ?? "idle";
362
+ delete nextSessionMessageLoadStatusBySession[previousSessionId];
363
+ }
364
+ const nextSessionMessageLoadErrorBySession = {
365
+ ...state.sessionMessageLoadErrorBySession
366
+ };
367
+ if (previousSessionId in nextSessionMessageLoadErrorBySession) {
368
+ nextSessionMessageLoadErrorBySession[nextSessionId] = nextSessionMessageLoadErrorBySession[previousSessionId] ?? null;
369
+ delete nextSessionMessageLoadErrorBySession[previousSessionId];
370
+ }
371
+ const nextHistoryMessagePaginationBySession = {
372
+ ...state.historyMessagePaginationBySession
373
+ };
374
+ if (previousSessionId in nextHistoryMessagePaginationBySession) {
375
+ nextHistoryMessagePaginationBySession[nextSessionId] = nextHistoryMessagePaginationBySession[previousSessionId] ?? createHistoryMessagePaginationState();
376
+ delete nextHistoryMessagePaginationBySession[previousSessionId];
377
+ }
316
378
  const nextActiveSessionId = state.activeSessionId === previousSessionId ? nextSessionId : state.activeSessionId;
317
379
  set({
318
380
  sessions: nextSessions,
@@ -321,6 +383,9 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
321
383
  isStreamingBySession: nextIsStreamingBySession,
322
384
  isStoppingBySession: nextIsStoppingBySession,
323
385
  errorBySession: nextErrorBySession,
386
+ sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
387
+ sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
388
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
324
389
  activeSessionId: nextActiveSessionId
325
390
  });
326
391
  },
@@ -334,6 +399,61 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
334
399
  );
335
400
  set({ sessions: nextSessions });
336
401
  },
402
+ hydrateHistorySessions: (sessions) => {
403
+ const state = get();
404
+ const localSessions = state.sessions.filter(
405
+ (session) => isDraftChatSessionId(session.sessionId)
406
+ );
407
+ const localSessionIds = new Set(localSessions.map((session) => session.sessionId));
408
+ const nextSessions = [
409
+ ...localSessions,
410
+ ...sessions.filter((session) => !localSessionIds.has(session.sessionId)).map((session) => ({
411
+ ...session,
412
+ mode: session.mode ?? DEFAULT_CHAT_AGENT_MODE
413
+ }))
414
+ ];
415
+ const nextMessagesBySession = { ...state.messagesBySession };
416
+ const nextErrorBySession = { ...state.errorBySession };
417
+ const nextIsStreamingBySession = { ...state.isStreamingBySession };
418
+ const nextIsStoppingBySession = { ...state.isStoppingBySession };
419
+ const nextSessionMessageLoadStatusBySession = {
420
+ ...state.sessionMessageLoadStatusBySession
421
+ };
422
+ const nextSessionMessageLoadErrorBySession = {
423
+ ...state.sessionMessageLoadErrorBySession
424
+ };
425
+ const nextHistoryMessagePaginationBySession = {
426
+ ...state.historyMessagePaginationBySession
427
+ };
428
+ nextSessions.forEach((session) => {
429
+ const sid = session.sessionId;
430
+ if (nextErrorBySession[sid] === void 0)
431
+ nextErrorBySession[sid] = null;
432
+ if (nextIsStreamingBySession[sid] === void 0)
433
+ nextIsStreamingBySession[sid] = false;
434
+ if (nextIsStoppingBySession[sid] === void 0)
435
+ nextIsStoppingBySession[sid] = false;
436
+ if (nextSessionMessageLoadStatusBySession[sid] === void 0) {
437
+ nextSessionMessageLoadStatusBySession[sid] = "idle";
438
+ }
439
+ if (nextSessionMessageLoadErrorBySession[sid] === void 0) {
440
+ nextSessionMessageLoadErrorBySession[sid] = null;
441
+ }
442
+ if (nextHistoryMessagePaginationBySession[sid] === void 0) {
443
+ nextHistoryMessagePaginationBySession[sid] = createHistoryMessagePaginationState();
444
+ }
445
+ });
446
+ set({
447
+ sessions: nextSessions,
448
+ messagesBySession: nextMessagesBySession,
449
+ errorBySession: nextErrorBySession,
450
+ isStreamingBySession: nextIsStreamingBySession,
451
+ isStoppingBySession: nextIsStoppingBySession,
452
+ sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
453
+ sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
454
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
455
+ });
456
+ },
337
457
  // ---- Message operations ------------------------------------------------
338
458
  appendMessage: (sessionId, message) => {
339
459
  const state = get();
@@ -360,6 +480,92 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
360
480
  isStreamingBySession: nextIsStreamingBySession
361
481
  });
362
482
  },
483
+ hydrateHistorySessionMessages: (sessionId, messages) => {
484
+ const state = get();
485
+ set({
486
+ messagesBySession: {
487
+ ...state.messagesBySession,
488
+ [sessionId]: normalizeHistoryMessages(sessionId, messages)
489
+ },
490
+ sessionMessageLoadStatusBySession: {
491
+ ...state.sessionMessageLoadStatusBySession,
492
+ [sessionId]: "loaded"
493
+ },
494
+ sessionMessageLoadErrorBySession: {
495
+ ...state.sessionMessageLoadErrorBySession,
496
+ [sessionId]: null
497
+ },
498
+ historyMessagePaginationBySession: {
499
+ ...state.historyMessagePaginationBySession,
500
+ [sessionId]: createHistoryMessagePaginationState()
501
+ }
502
+ });
503
+ },
504
+ hydrateHistorySessionMessagesPage: (sessionId, page) => {
505
+ const state = get();
506
+ set({
507
+ messagesBySession: {
508
+ ...state.messagesBySession,
509
+ [sessionId]: normalizeHistoryMessages(sessionId, page.messages)
510
+ },
511
+ sessionMessageLoadStatusBySession: {
512
+ ...state.sessionMessageLoadStatusBySession,
513
+ [sessionId]: "loaded"
514
+ },
515
+ sessionMessageLoadErrorBySession: {
516
+ ...state.sessionMessageLoadErrorBySession,
517
+ [sessionId]: null
518
+ },
519
+ historyMessagePaginationBySession: {
520
+ ...state.historyMessagePaginationBySession,
521
+ [sessionId]: createHistoryMessagePaginationState(page)
522
+ }
523
+ });
524
+ },
525
+ prependHistorySessionMessagesPage: (sessionId, page) => {
526
+ const state = get();
527
+ set({
528
+ messagesBySession: {
529
+ ...state.messagesBySession,
530
+ [sessionId]: mergeOlderHistoryMessages(
531
+ sessionId,
532
+ page.messages,
533
+ state.messagesBySession[sessionId] ?? []
534
+ )
535
+ },
536
+ historyMessagePaginationBySession: {
537
+ ...state.historyMessagePaginationBySession,
538
+ [sessionId]: createHistoryMessagePaginationState(page)
539
+ }
540
+ });
541
+ },
542
+ setHistorySessionPreviousMessagesLoadStatus: (sessionId, isLoading, error2 = null) => {
543
+ const state = get();
544
+ const current = state.historyMessagePaginationBySession[sessionId] ?? createHistoryMessagePaginationState();
545
+ set({
546
+ historyMessagePaginationBySession: {
547
+ ...state.historyMessagePaginationBySession,
548
+ [sessionId]: {
549
+ ...current,
550
+ isLoadingPrevious: isLoading,
551
+ error: error2
552
+ }
553
+ }
554
+ });
555
+ },
556
+ setHistorySessionMessageLoadStatus: (sessionId, status, error2 = null) => {
557
+ const state = get();
558
+ set({
559
+ sessionMessageLoadStatusBySession: {
560
+ ...state.sessionMessageLoadStatusBySession,
561
+ [sessionId]: status
562
+ },
563
+ sessionMessageLoadErrorBySession: {
564
+ ...state.sessionMessageLoadErrorBySession,
565
+ [sessionId]: error2
566
+ }
567
+ });
568
+ },
363
569
  startStreamingMessage: (sessionId, message) => {
364
570
  const state = get();
365
571
  set({
@@ -807,6 +1013,10 @@ var AiChatProvider = (props) => {
807
1013
  handleQuestionnaireSubmit,
808
1014
  handleConfirmationSubmit,
809
1015
  messageRenderOrder,
1016
+ historySessionList,
1017
+ onLoadMoreSessions,
1018
+ onSelectHistorySession,
1019
+ onLoadMoreHistoryMessages,
810
1020
  enableImageAttachments = true,
811
1021
  children
812
1022
  } = props;
@@ -883,7 +1093,11 @@ var AiChatProvider = (props) => {
883
1093
  handleConfirmationSubmit,
884
1094
  messageRenderOrder,
885
1095
  transformStreamPacket: defaultTransformStreamPacket,
886
- enableImageAttachments
1096
+ enableImageAttachments,
1097
+ historySessionList,
1098
+ onLoadMoreSessions,
1099
+ onSelectHistorySession,
1100
+ onLoadMoreHistoryMessages
887
1101
  }),
888
1102
  [
889
1103
  axiosInstance,
@@ -893,8 +1107,12 @@ var AiChatProvider = (props) => {
893
1107
  enableImageAttachments,
894
1108
  handleConfirmationSubmit,
895
1109
  handleQuestionnaireSubmit,
1110
+ historySessionList,
896
1111
  labels,
897
1112
  messageRenderOrder,
1113
+ onLoadMoreSessions,
1114
+ onLoadMoreHistoryMessages,
1115
+ onSelectHistorySession,
898
1116
  renderMessageBlock,
899
1117
  sendRef,
900
1118
  retryRef,
@@ -1338,6 +1556,14 @@ var createTimelineAnchorState = ({
1338
1556
  timelineBlockAnchors: {},
1339
1557
  visibleTimelineBlockKeys: {}
1340
1558
  });
1559
+ var createInitialTimelineAnchorState = ({
1560
+ messageId
1561
+ }) => ({
1562
+ messageId,
1563
+ previousBlockKeys: [],
1564
+ timelineBlockAnchors: {},
1565
+ visibleTimelineBlockKeys: {}
1566
+ });
1341
1567
  var timelineAnchorReducer = (state, action) => {
1342
1568
  switch (action.type) {
1343
1569
  case "reset-message":
@@ -1427,7 +1653,7 @@ var useTimelineBlockAnchors = ({
1427
1653
  messageId: message.id,
1428
1654
  currentBlockKeys: currentTimelineBlockKeys
1429
1655
  },
1430
- createTimelineAnchorState
1656
+ createInitialTimelineAnchorState
1431
1657
  );
1432
1658
  const effectiveTimelineBlockAnchors = (0, import_react6.useMemo)(() => {
1433
1659
  if (messageRenderOrder !== "timeline" || !isAssistantStreaming) {
@@ -3198,15 +3424,11 @@ var Bubble = import_styled7.default.article`
3198
3424
 
3199
3425
  &[data-role='user'] {
3200
3426
  width: auto;
3201
- max-width: min(760px, 100%);
3427
+ max-width: 100%;
3202
3428
  margin-left: auto;
3203
- padding: 14px 16px;
3204
- border-radius: 22px;
3205
- background: linear-gradient(180deg, rgba(59, 59, 63, 0.9) 0%, rgba(42, 43, 46, 0.92) 100%);
3206
- border: 1px solid rgba(255, 255, 255, 0.07);
3207
- box-shadow:
3208
- inset 0 1px 0 rgba(255, 255, 255, 0.03),
3209
- 0 12px 30px rgba(0, 0, 0, 0.18);
3429
+ padding: 8px 12px;
3430
+ background: #282825;
3431
+ border-radius: 16px;
3210
3432
  }
3211
3433
  `;
3212
3434
  var Header2 = import_styled7.default.div`
@@ -3538,6 +3760,7 @@ var HeroSubtitle = import_styled8.default.p`
3538
3760
  // src/components/chat-thread/index.tsx
3539
3761
  var import_jsx_runtime10 = require("@emotion/react/jsx-runtime");
3540
3762
  var CHAT_THREAD_PINNED_THRESHOLD_PX = 32;
3763
+ var CHAT_THREAD_LOAD_PREVIOUS_THRESHOLD_PX = 80;
3541
3764
  var isThreadPinnedToBottom = (container) => container.scrollHeight - container.clientHeight - container.scrollTop <= CHAT_THREAD_PINNED_THRESHOLD_PX;
3542
3765
  var renderChatMessage = ({
3543
3766
  message,
@@ -3611,9 +3834,13 @@ var ChatThreadView = ({
3611
3834
  historyMessages,
3612
3835
  streamingMessage,
3613
3836
  error: error2,
3837
+ isLoadingPreviousMessages = false,
3838
+ previousMessagesError,
3614
3839
  retryButtonLabel,
3615
3840
  scrollToLatestLabel,
3841
+ sessionHistoryLoadingLabel,
3616
3842
  onRetry,
3843
+ onLoadPreviousMessages,
3617
3844
  onConfirmationSubmit,
3618
3845
  onQuestionnaireSubmit,
3619
3846
  renderMessageBlock
@@ -3624,11 +3851,11 @@ var ChatThreadView = ({
3624
3851
  [historyMessages, streamingMessage]
3625
3852
  );
3626
3853
  const latestTurn = conversationTurns[conversationTurns.length - 1];
3627
- const previousTurns = conversationTurns.slice(0, -1);
3628
3854
  const latestUserMessageId = latestTurn?.userMessage?.id;
3629
3855
  const latestHistoryMessage = historyMessages[historyMessages.length - 1];
3630
3856
  const latestTurnRef = (0, import_react11.useRef)(null);
3631
3857
  const reservedSpaceFrameRef = (0, import_react11.useRef)(null);
3858
+ const isLoadingPreviousRef = (0, import_react11.useRef)(false);
3632
3859
  const isPinnedRef = (0, import_react11.useRef)(true);
3633
3860
  const lastHistoryMessageIdRef = (0, import_react11.useRef)(latestHistoryMessage?.id);
3634
3861
  const lastStreamingMessageIdRef = (0, import_react11.useRef)(streamingMessage?.id);
@@ -3675,17 +3902,45 @@ var ChatThreadView = ({
3675
3902
  },
3676
3903
  [markThreadPinned, scrollToBottom]
3677
3904
  );
3905
+ const handleLoadPreviousMessages = (0, import_react11.useCallback)(async () => {
3906
+ const container = containerRef.current;
3907
+ if (!container || !onLoadPreviousMessages || isLoadingPreviousMessages) {
3908
+ return;
3909
+ }
3910
+ if (isLoadingPreviousRef.current) {
3911
+ return;
3912
+ }
3913
+ isLoadingPreviousRef.current = true;
3914
+ const previousScrollHeight = container.scrollHeight;
3915
+ const previousScrollTop = container.scrollTop;
3916
+ try {
3917
+ await onLoadPreviousMessages();
3918
+ } catch {
3919
+ return;
3920
+ } finally {
3921
+ isLoadingPreviousRef.current = false;
3922
+ }
3923
+ window.requestAnimationFrame(() => {
3924
+ const nextContainer = containerRef.current;
3925
+ if (!nextContainer)
3926
+ return;
3927
+ nextContainer.scrollTop = nextContainer.scrollHeight - previousScrollHeight + previousScrollTop;
3928
+ });
3929
+ }, [isLoadingPreviousMessages, onLoadPreviousMessages]);
3678
3930
  const handleContainerScroll = (0, import_react11.useCallback)(() => {
3679
3931
  const container = containerRef.current;
3680
3932
  if (!container)
3681
3933
  return;
3934
+ if (onLoadPreviousMessages && container.scrollTop <= CHAT_THREAD_LOAD_PREVIOUS_THRESHOLD_PX) {
3935
+ void handleLoadPreviousMessages();
3936
+ }
3682
3937
  const nextPinned = isThreadPinnedToBottom(container);
3683
3938
  isPinnedRef.current = nextPinned;
3684
3939
  setIsDetached(!nextPinned);
3685
3940
  if (nextPinned) {
3686
3941
  setPendingNewMessageCount(0);
3687
3942
  }
3688
- }, []);
3943
+ }, [handleLoadPreviousMessages, onLoadPreviousMessages]);
3689
3944
  (0, import_react11.useLayoutEffect)(() => {
3690
3945
  const nextHistoryMessageId = latestHistoryMessage?.id;
3691
3946
  if (lastHistoryMessageIdRef.current === nextHistoryMessageId) {
@@ -3813,54 +4068,44 @@ var ChatThreadView = ({
3813
4068
  }, [latestTurn, scrollToBottom]);
3814
4069
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ThreadViewport, { children: [
3815
4070
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Container, { ref: containerRef, "data-testid": "chat-thread", onScroll: handleContainerScroll, children: [
3816
- previousTurns.map((turn) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ConversationTurn, { "data-testid": "chat-thread-turn", children: [
3817
- turn.userMessage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageSlot, { children: renderChatMessage({
3818
- message: turn.userMessage,
3819
- mode: activeSessionMode,
3820
- onConfirmationSubmit,
3821
- onQuestionnaireSubmit,
3822
- renderMessageBlock
3823
- }) }) : null,
3824
- turn.responseMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageSlot, { children: renderChatMessage({
3825
- message,
3826
- mode: activeSessionMode,
3827
- onConfirmationSubmit,
3828
- onQuestionnaireSubmit,
3829
- renderMessageBlock
3830
- }) }, message.id))
3831
- ] }, turn.id)),
3832
- latestTurn ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3833
- ConversationTurn,
3834
- {
3835
- ref: latestTurnRef,
3836
- "data-testid": "chat-thread-latest-turn",
3837
- style: latestTurnMinHeight > 0 ? { minHeight: `${latestTurnMinHeight}px` } : void 0,
3838
- children: [
3839
- latestTurn.userMessage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3840
- MessageSlot,
3841
- {
3842
- "data-testid": "chat-latest-user-anchor",
3843
- style: { scrollMarginTop: `${CHAT_THREAD_SCROLL_TOP_GAP}px` },
3844
- children: renderChatMessage({
3845
- message: latestTurn.userMessage,
3846
- mode: activeSessionMode,
3847
- onConfirmationSubmit,
3848
- onQuestionnaireSubmit,
3849
- renderMessageBlock
3850
- })
3851
- }
3852
- ) : null,
3853
- latestTurn.responseMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageSlot, { children: renderChatMessage({
3854
- message,
3855
- mode: activeSessionMode,
3856
- onConfirmationSubmit,
3857
- onQuestionnaireSubmit,
3858
- renderMessageBlock
3859
- }) }, message.id)),
3860
- error2 ? renderErrorState({ error: error2, onRetry, retryButtonLabel }) : null
3861
- ]
3862
- }
3863
- ) : null,
4071
+ isLoadingPreviousMessages ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PreviousMessagesStateRow, { "data-testid": "chat-thread-loading-previous", children: sessionHistoryLoadingLabel }) : null,
4072
+ previousMessagesError ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PreviousMessagesStateRow, { "data-testid": "chat-thread-load-previous-error", children: previousMessagesError }) : null,
4073
+ conversationTurns.map((turn, turnIndex) => {
4074
+ const isLatestTurn = turnIndex === conversationTurns.length - 1;
4075
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4076
+ ConversationTurn,
4077
+ {
4078
+ ref: isLatestTurn ? latestTurnRef : null,
4079
+ "data-testid": isLatestTurn ? "chat-thread-latest-turn" : "chat-thread-turn",
4080
+ style: isLatestTurn && latestTurnMinHeight > 0 ? { minHeight: `${latestTurnMinHeight}px` } : void 0,
4081
+ children: [
4082
+ turn.userMessage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4083
+ MessageSlot,
4084
+ {
4085
+ "data-testid": isLatestTurn ? "chat-latest-user-anchor" : void 0,
4086
+ style: isLatestTurn ? { scrollMarginTop: `${CHAT_THREAD_SCROLL_TOP_GAP}px` } : void 0,
4087
+ children: renderChatMessage({
4088
+ message: turn.userMessage,
4089
+ mode: activeSessionMode,
4090
+ onConfirmationSubmit,
4091
+ onQuestionnaireSubmit,
4092
+ renderMessageBlock
4093
+ })
4094
+ }
4095
+ ) : null,
4096
+ turn.responseMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageSlot, { children: renderChatMessage({
4097
+ message,
4098
+ mode: activeSessionMode,
4099
+ onConfirmationSubmit,
4100
+ onQuestionnaireSubmit,
4101
+ renderMessageBlock
4102
+ }) }, message.id)),
4103
+ isLatestTurn && error2 ? renderErrorState({ error: error2, onRetry, retryButtonLabel }) : null
4104
+ ]
4105
+ },
4106
+ turn.id
4107
+ );
4108
+ }),
3864
4109
  !latestTurn && error2 ? renderErrorState({ error: error2, onRetry, retryButtonLabel }) : null
3865
4110
  ] }),
3866
4111
  isDetached ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ScrollToLatestOverlay, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
@@ -3881,14 +4126,25 @@ var EMPTY_MESSAGES = [];
3881
4126
  var ChatThread = () => {
3882
4127
  const activeSessionId = useChatStore((s) => s.activeSessionId);
3883
4128
  const hasSessions = useChatStore((s) => s.sessions.length > 0);
3884
- const activeSessionMode = useChatStore(
3885
- (s) => s.sessions.find((x) => x.sessionId === s.activeSessionId)?.mode ?? DEFAULT_CHAT_AGENT_MODE
4129
+ const activeSession = useChatStore(
4130
+ (s) => s.sessions.find((session) => session.sessionId === s.activeSessionId)
3886
4131
  );
4132
+ const activeSessionMode = activeSession?.mode ?? DEFAULT_CHAT_AGENT_MODE;
3887
4133
  const messages = useChatStore(
3888
4134
  (s) => s.messagesBySession[s.activeSessionId ?? ""] ?? EMPTY_MESSAGES
3889
4135
  );
4136
+ const sessionMessageLoadStatus = useChatStore(
4137
+ (s) => s.sessionMessageLoadStatusBySession[s.activeSessionId ?? ""]
4138
+ );
3890
4139
  const streamingMessage = useChatStore((s) => s.streamingMessageBySession[s.activeSessionId ?? ""]);
3891
4140
  const error2 = useChatStore((s) => s.errorBySession[s.activeSessionId ?? ""]);
4141
+ const historyMessagePagination = useChatStore(
4142
+ (s) => s.historyMessagePaginationBySession[s.activeSessionId ?? ""]
4143
+ );
4144
+ const prependHistorySessionMessagesPage = useChatStore((s) => s.prependHistorySessionMessagesPage);
4145
+ const setHistorySessionPreviousMessagesLoadStatus = useChatStore(
4146
+ (s) => s.setHistorySessionPreviousMessagesLoadStatus
4147
+ );
3892
4148
  const updateQA = useChatStore((s) => s.updateQuestionnaireAnswers);
3893
4149
  const clearSessionError = useChatStore((s) => s.clearSessionError);
3894
4150
  const {
@@ -3897,6 +4153,7 @@ var ChatThread = () => {
3897
4153
  renderMessageBlock,
3898
4154
  handleQuestionnaireSubmit: customQuestionnaireSubmit,
3899
4155
  handleConfirmationSubmit: customConfirmationSubmit,
4156
+ onLoadMoreHistoryMessages,
3900
4157
  labels
3901
4158
  } = useChatContext();
3902
4159
  const handleRetry = (0, import_react11.useCallback)(() => {
@@ -3959,6 +4216,42 @@ var ChatThread = () => {
3959
4216
  },
3960
4217
  [activeSessionId, activeSessionMode, sendRef, customConfirmationSubmit]
3961
4218
  );
4219
+ const handleLoadPreviousMessages = (0, import_react11.useCallback)(async () => {
4220
+ if (!activeSession || !onLoadMoreHistoryMessages || !historyMessagePagination?.hasMorePrevious || !historyMessagePagination.previousCursor || historyMessagePagination.isLoadingPrevious) {
4221
+ return;
4222
+ }
4223
+ setHistorySessionPreviousMessagesLoadStatus(activeSession.sessionId, true);
4224
+ try {
4225
+ const page = await onLoadMoreHistoryMessages({
4226
+ session: activeSession,
4227
+ cursor: historyMessagePagination.previousCursor
4228
+ });
4229
+ if (page) {
4230
+ prependHistorySessionMessagesPage(activeSession.sessionId, page);
4231
+ return;
4232
+ }
4233
+ setHistorySessionPreviousMessagesLoadStatus(activeSession.sessionId, false);
4234
+ } catch (error3) {
4235
+ setHistorySessionPreviousMessagesLoadStatus(
4236
+ activeSession.sessionId,
4237
+ false,
4238
+ error3 instanceof Error ? error3.message : String(error3)
4239
+ );
4240
+ throw error3;
4241
+ }
4242
+ }, [
4243
+ activeSession,
4244
+ historyMessagePagination,
4245
+ onLoadMoreHistoryMessages,
4246
+ prependHistorySessionMessagesPage,
4247
+ setHistorySessionPreviousMessagesLoadStatus
4248
+ ]);
4249
+ const canLoadPreviousMessages = Boolean(
4250
+ activeSession && onLoadMoreHistoryMessages && historyMessagePagination?.hasMorePrevious && historyMessagePagination.previousCursor && !historyMessagePagination.isLoadingPrevious
4251
+ );
4252
+ if (hasSessions && sessionMessageLoadStatus === "loading" && messages.length === 0 && !streamingMessage) {
4253
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ThreadStateViewport, { "data-testid": "chat-thread-loading-state", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ThreadStateText, { children: labels.sessionHistoryLoading }) });
4254
+ }
3962
4255
  if (!hasSessions || messages.length === 0 && !streamingMessage) {
3963
4256
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatThreadEmptyState, {});
3964
4257
  }
@@ -3969,9 +4262,13 @@ var ChatThread = () => {
3969
4262
  historyMessages: messages,
3970
4263
  streamingMessage,
3971
4264
  error: error2,
4265
+ isLoadingPreviousMessages: historyMessagePagination?.isLoadingPrevious,
4266
+ previousMessagesError: historyMessagePagination?.error,
3972
4267
  retryButtonLabel: labels.retryButton,
3973
4268
  scrollToLatestLabel: labels.scrollToLatest,
4269
+ sessionHistoryLoadingLabel: labels.sessionHistoryLoading,
3974
4270
  onRetry: handleRetry,
4271
+ onLoadPreviousMessages: canLoadPreviousMessages ? handleLoadPreviousMessages : void 0,
3975
4272
  onConfirmationSubmit: handleConfirmation,
3976
4273
  onQuestionnaireSubmit: handleQuestionnaireSubmit,
3977
4274
  renderMessageBlock
@@ -3985,6 +4282,18 @@ var ThreadViewport = import_styled9.default.div`
3985
4282
  flex: 1;
3986
4283
  min-height: 0;
3987
4284
  `;
4285
+ var ThreadStateViewport = import_styled9.default.div`
4286
+ display: flex;
4287
+ flex: 1;
4288
+ min-height: 0;
4289
+ align-items: center;
4290
+ justify-content: center;
4291
+ padding: 24px;
4292
+ `;
4293
+ var ThreadStateText = import_styled9.default.div`
4294
+ color: var(--text-secondary, rgba(255, 255, 255, 0.64));
4295
+ font-size: 14px;
4296
+ `;
3988
4297
  var Container = import_styled9.default.div`
3989
4298
  display: flex;
3990
4299
  flex: 1;
@@ -3992,7 +4301,7 @@ var Container = import_styled9.default.div`
3992
4301
  gap: 18px;
3993
4302
  min-height: 0;
3994
4303
  overflow: auto;
3995
- padding: 24px 24px 88px;
4304
+ padding: 24px 16px 88px;
3996
4305
  overscroll-behavior: contain;
3997
4306
 
3998
4307
  &::-webkit-scrollbar {
@@ -4011,10 +4320,23 @@ var Container = import_styled9.default.div`
4011
4320
  var MessageSlot = import_styled9.default.div`
4012
4321
  display: flex;
4013
4322
  `;
4323
+ var PreviousMessagesStateRow = import_styled9.default.div`
4324
+ width: 100%;
4325
+ max-width: var(--chat-content-max-width, 48rem);
4326
+ margin-right: auto;
4327
+ margin-left: auto;
4328
+ color: var(--text-secondary, rgba(255, 255, 255, 0.64));
4329
+ font-size: 13px;
4330
+ text-align: center;
4331
+ `;
4014
4332
  var ConversationTurn = import_styled9.default.div`
4015
4333
  display: flex;
4016
4334
  flex-direction: column;
4017
4335
  gap: 18px;
4336
+ width: 100%;
4337
+ max-width: var(--chat-content-max-width, 48rem);
4338
+ margin-right: auto;
4339
+ margin-left: auto;
4018
4340
  `;
4019
4341
  var ErrorText = import_styled9.default.div`
4020
4342
  color: #ff7b72;
@@ -7802,7 +8124,7 @@ var PrimaryButton = (0, import_styled13.default)(import_compass_ui3.Button)`
7802
8124
  min-width: 24px;
7803
8125
  width: 24px;
7804
8126
  height: 24px;
7805
- background: ${({ $canSend }) => $canSend ? "#fcfbf8" : "rgba(255, 255, 255, 0.3)"};
8127
+ background: ${({ $canSend }) => $canSend ? "#fcfbf8" : "rgba(252,251,248,0.3);"};
7806
8128
  color: ${({ $canSend }) => $canSend ? "#5b5448" : "rgba(255, 255, 255, 0.72)"};
7807
8129
  border-radius: 12px;
7808
8130
  border: 1px solid ${({ $canSend }) => $canSend ? "rgba(198, 188, 170, 0.38)" : "transparent"};
@@ -7813,7 +8135,7 @@ var PrimaryButton = (0, import_styled13.default)(import_compass_ui3.Button)`
7813
8135
  }
7814
8136
 
7815
8137
  &:hover:not(:disabled) {
7816
- background: ${({ $canSend }) => $canSend ? "#f7f4ec" : "rgba(255, 255, 255, 0.3)"};
8138
+ background: ${({ $canSend }) => $canSend ? "#f7f4ec" : "rgba(252,251,248,0.3);"};
7817
8139
  color: ${({ $canSend }) => $canSend ? "#4f493f" : "rgba(255, 255, 255, 0.72)"};
7818
8140
  border-color: ${({ $canSend }) => $canSend ? "rgba(198, 188, 170, 0.46)" : "transparent"};
7819
8141
  }
@@ -7881,7 +8203,7 @@ var StopSpinner = import_styled13.default.span`
7881
8203
  var import_jsx_runtime16 = require("@emotion/react/jsx-runtime");
7882
8204
  var CHAT_COMPOSER_LINE_HEIGHT_PX = 20;
7883
8205
  var CHAT_COMPOSER_MAX_ROWS = 7;
7884
- var CHAT_COMPOSER_PADDING_TOP_PX = 8;
8206
+ var CHAT_COMPOSER_PADDING_TOP_PX = 12;
7885
8207
  var CHAT_COMPOSER_PADDING_BOTTOM_PX = 12;
7886
8208
  var CHAT_COMPOSER_PADDING_BLOCK_PX = CHAT_COMPOSER_PADDING_TOP_PX + CHAT_COMPOSER_PADDING_BOTTOM_PX;
7887
8209
  var CHAT_COMPOSER_MIN_ROWS = 4;
@@ -8116,6 +8438,10 @@ var ChatComposerView = ({
8116
8438
  setActiveSkillNavigation({ queryKey: "", index: 0 });
8117
8439
  };
8118
8440
  const handleKeyDown = (event) => {
8441
+ const isImeComposing = event.nativeEvent.isComposing || event.keyCode === 229;
8442
+ if (event.key === "Enter" && isImeComposing) {
8443
+ return;
8444
+ }
8119
8445
  if (skillQueryMatch) {
8120
8446
  if (event.key === "ArrowDown" && filteredSkills.length > 0) {
8121
8447
  event.preventDefault();
@@ -8393,7 +8719,7 @@ var Surface = import_styled14.default.div`
8393
8719
  'input'
8394
8720
  'footer';
8395
8721
  width: 100%;
8396
- max-width: 760px;
8722
+ max-width: var(--chat-content-max-width, 48rem);
8397
8723
  margin: 0 auto;
8398
8724
  background: var(--border-color);
8399
8725
  border-radius: 20px;
@@ -8654,6 +8980,7 @@ var SkillButton = import_styled14.default.button`
8654
8980
  `;
8655
8981
 
8656
8982
  // src/components/chat-conversation-list/index.tsx
8983
+ var import_react20 = require("react");
8657
8984
  var import_styled16 = __toESM(require("@emotion/styled"));
8658
8985
 
8659
8986
  // src/components/chat-conversation-list/components/chat-session-item.tsx
@@ -8667,6 +8994,7 @@ var ChatSessionItem = (0, import_react19.memo)(
8667
8994
  {
8668
8995
  type: "button",
8669
8996
  "data-active": isActive,
8997
+ "data-testid": `chat-session-item-${session.sessionId}`,
8670
8998
  onClick: () => onClick(session.sessionId),
8671
8999
  children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(SessionMeta, { children: [
8672
9000
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SessionTitle, { children: session.title }),
@@ -8714,14 +9042,130 @@ var ModeBadge = import_styled15.default.span`
8714
9042
  background: rgba(255, 255, 255, 0.04);
8715
9043
  `;
8716
9044
 
9045
+ // src/components/chat-conversation-list/lib/history-session-selection.ts
9046
+ var shouldLoadHistorySessionMessages = ({
9047
+ sessionId,
9048
+ messagesBySession,
9049
+ loadStatusBySession,
9050
+ isStreamingBySession
9051
+ }) => {
9052
+ if (isStreamingBySession[sessionId])
9053
+ return false;
9054
+ if (loadStatusBySession[sessionId] === "loading")
9055
+ return false;
9056
+ if (loadStatusBySession[sessionId] === "error")
9057
+ return true;
9058
+ if (loadStatusBySession[sessionId] === "loaded")
9059
+ return false;
9060
+ return messagesBySession[sessionId] === void 0;
9061
+ };
9062
+
8717
9063
  // src/components/chat-conversation-list/index.tsx
8718
9064
  var import_jsx_runtime18 = require("@emotion/react/jsx-runtime");
9065
+ var SCROLL_LOAD_MORE_THRESHOLD_PX = 80;
9066
+ var shouldLoadMoreSessions = ({
9067
+ scrollTop,
9068
+ clientHeight,
9069
+ scrollHeight,
9070
+ threshold = SCROLL_LOAD_MORE_THRESHOLD_PX
9071
+ }) => scrollHeight - scrollTop - clientHeight <= threshold;
9072
+ var isHistorySessionMessagesPage = (value) => typeof value === "object" && value !== null && Array.isArray(value.messages);
8719
9073
  var ChatConversationList = () => {
8720
- const { labels } = useChatContext();
8721
- const sessions = useChatStore((s) => s.sessions);
9074
+ const { labels, historySessionList, onLoadMoreSessions, onSelectHistorySession, store } = useChatContext();
9075
+ const localSessions = useChatStore((s) => s.sessions);
8722
9076
  const activeSessionId = useChatStore((s) => s.activeSessionId);
8723
9077
  const startNewChat = useChatStore((s) => s.startNewChat);
8724
9078
  const setActiveSession = useChatStore((s) => s.setActiveSession);
9079
+ const hydrateHistorySessions = useChatStore((s) => s.hydrateHistorySessions);
9080
+ const hydrateHistorySessionMessages = useChatStore((s) => s.hydrateHistorySessionMessages);
9081
+ const hydrateHistorySessionMessagesPage = useChatStore((s) => s.hydrateHistorySessionMessagesPage);
9082
+ const setHistorySessionMessageLoadStatus = useChatStore(
9083
+ (s) => s.setHistorySessionMessageLoadStatus
9084
+ );
9085
+ const isLoadingMoreRef = (0, import_react20.useRef)(false);
9086
+ const hasSeenLoadingMoreRef = (0, import_react20.useRef)(false);
9087
+ (0, import_react20.useEffect)(() => {
9088
+ if (!historySessionList)
9089
+ return;
9090
+ hydrateHistorySessions(historySessionList.sessions);
9091
+ }, [historySessionList, hydrateHistorySessions]);
9092
+ (0, import_react20.useEffect)(() => {
9093
+ if (historySessionList?.isLoading) {
9094
+ hasSeenLoadingMoreRef.current = true;
9095
+ return;
9096
+ }
9097
+ if (hasSeenLoadingMoreRef.current) {
9098
+ hasSeenLoadingMoreRef.current = false;
9099
+ isLoadingMoreRef.current = false;
9100
+ }
9101
+ }, [historySessionList?.isLoading]);
9102
+ (0, import_react20.useEffect)(() => {
9103
+ isLoadingMoreRef.current = false;
9104
+ hasSeenLoadingMoreRef.current = false;
9105
+ }, [historySessionList?.sessions.length, historySessionList?.hasMore]);
9106
+ const sessions = (0, import_react20.useMemo)(() => {
9107
+ if (!historySessionList) {
9108
+ return localSessions;
9109
+ }
9110
+ const localSessionIds = new Set(localSessions.map((session) => session.sessionId));
9111
+ return [
9112
+ ...localSessions,
9113
+ ...historySessionList.sessions.filter((session) => !localSessionIds.has(session.sessionId))
9114
+ ];
9115
+ }, [historySessionList, localSessions]);
9116
+ const handleSessionListScroll = (event) => {
9117
+ if (!historySessionList?.hasMore || historySessionList.isLoading || !onLoadMoreSessions || isLoadingMoreRef.current) {
9118
+ return;
9119
+ }
9120
+ const target = event.currentTarget;
9121
+ if (shouldLoadMoreSessions({
9122
+ scrollTop: target.scrollTop,
9123
+ clientHeight: target.clientHeight,
9124
+ scrollHeight: target.scrollHeight
9125
+ })) {
9126
+ isLoadingMoreRef.current = true;
9127
+ void Promise.resolve(onLoadMoreSessions()).catch(() => {
9128
+ isLoadingMoreRef.current = false;
9129
+ hasSeenLoadingMoreRef.current = false;
9130
+ });
9131
+ }
9132
+ };
9133
+ const handleSelectSession = async (sessionId) => {
9134
+ setActiveSession(sessionId);
9135
+ const session = sessions.find((item) => item.sessionId === sessionId);
9136
+ if (!session || !onSelectHistorySession) {
9137
+ return;
9138
+ }
9139
+ const state = store.getState();
9140
+ const shouldLoad = shouldLoadHistorySessionMessages({
9141
+ sessionId,
9142
+ messagesBySession: state.messagesBySession,
9143
+ loadStatusBySession: state.sessionMessageLoadStatusBySession,
9144
+ isStreamingBySession: state.isStreamingBySession
9145
+ });
9146
+ if (!shouldLoad) {
9147
+ return;
9148
+ }
9149
+ setHistorySessionMessageLoadStatus(sessionId, "loading");
9150
+ try {
9151
+ const result = await onSelectHistorySession(session);
9152
+ if (Array.isArray(result)) {
9153
+ hydrateHistorySessionMessages(sessionId, result);
9154
+ return;
9155
+ }
9156
+ if (isHistorySessionMessagesPage(result)) {
9157
+ hydrateHistorySessionMessagesPage(sessionId, result);
9158
+ return;
9159
+ }
9160
+ setHistorySessionMessageLoadStatus(sessionId, "loaded");
9161
+ } catch (error2) {
9162
+ setHistorySessionMessageLoadStatus(
9163
+ sessionId,
9164
+ "error",
9165
+ error2 instanceof Error ? error2.message : String(error2)
9166
+ );
9167
+ }
9168
+ };
8725
9169
  const modeLabels = {
8726
9170
  ask: labels.modeLabelAsk,
8727
9171
  plan: labels.modeLabelPlan,
@@ -8732,16 +9176,21 @@ var ChatConversationList = () => {
8732
9176
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Title3, { children: "Sessions" }),
8733
9177
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(CreateButton, { type: "button", "data-testid": "chat-create-session", onClick: startNewChat, children: labels.newChat })
8734
9178
  ] }),
8735
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(List2, { "data-testid": "chat-session-list", children: sessions.map((session) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
8736
- ChatSessionItem,
8737
- {
8738
- session,
8739
- isActive: activeSessionId === session.sessionId,
8740
- modeLabel: modeLabels[session.mode ?? DEFAULT_CHAT_AGENT_MODE] ?? "",
8741
- onClick: setActiveSession
8742
- },
8743
- session.sessionId
8744
- )) })
9179
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(List2, { "data-testid": "chat-session-list", onScroll: handleSessionListScroll, children: [
9180
+ sessions.map((session) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
9181
+ ChatSessionItem,
9182
+ {
9183
+ session,
9184
+ isActive: activeSessionId === session.sessionId,
9185
+ modeLabel: modeLabels[session.mode ?? DEFAULT_CHAT_AGENT_MODE] ?? "",
9186
+ onClick: (sessionId) => void handleSelectSession(sessionId)
9187
+ },
9188
+ session.sessionId
9189
+ )),
9190
+ historySessionList?.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StateRow, { "data-testid": "chat-session-history-loading", children: labels.sessionHistoryLoading }) : null,
9191
+ historySessionList?.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StateRow, { "data-testid": "chat-session-history-error", children: historySessionList.error || labels.sessionHistoryLoadFailed }) : null,
9192
+ historySessionList && !historySessionList.isLoading && !historySessionList.error && sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StateRow, { "data-testid": "chat-session-history-empty", children: labels.sessionHistoryEmpty }) : null
9193
+ ] })
8745
9194
  ] });
8746
9195
  };
8747
9196
  var Container3 = import_styled16.default.aside`
@@ -8779,6 +9228,11 @@ var List2 = import_styled16.default.div`
8779
9228
  gap: 8px;
8780
9229
  overflow: auto;
8781
9230
  `;
9231
+ var StateRow = import_styled16.default.div`
9232
+ padding: 12px;
9233
+ font-size: 13px;
9234
+ color: var(--text-secondary);
9235
+ `;
8782
9236
 
8783
9237
  // src/components/ai-chat/index.tsx
8784
9238
  var import_jsx_runtime19 = require("@emotion/react/jsx-runtime");
@@ -8840,7 +9294,7 @@ var AiChatWorkspaceContent = ({
8840
9294
  })
8841
9295
  );
8842
9296
  const shouldShowComposerOnly = showComposerOnlyBeforeFirstMessage && !showConversationList && !isConversationStarted;
8843
- (0, import_react20.useEffect)(() => {
9297
+ (0, import_react21.useEffect)(() => {
8844
9298
  onConversationStartedChange?.(isConversationStarted);
8845
9299
  }, [isConversationStarted, onConversationStartedChange]);
8846
9300
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(Root, { "data-testid": "ai-chat", children: [
@@ -8979,12 +9433,25 @@ var Root = import_styled17.default.div`
8979
9433
  overflow: hidden;
8980
9434
  `;
8981
9435
  var Workspace = import_styled17.default.section`
9436
+ --chat-layout-rem: 16px;
9437
+ --chat-content-margin: calc(var(--chat-layout-rem) * 1);
9438
+ --chat-content-max-width: calc(var(--chat-layout-rem) * 40);
9439
+
8982
9440
  flex: 1;
8983
9441
  display: flex;
8984
9442
  flex-direction: column;
8985
9443
  gap: 12px;
8986
9444
  min-height: 0;
8987
9445
  overflow: hidden;
9446
+
9447
+ @media (min-width: 640px) {
9448
+ --chat-content-margin: calc(var(--chat-layout-rem) * 1.5);
9449
+ }
9450
+
9451
+ @media (min-width: 1024px) {
9452
+ --chat-content-margin: calc(var(--chat-layout-rem) * 4);
9453
+ --chat-content-max-width: calc(var(--chat-layout-rem) * 48);
9454
+ }
8988
9455
  `;
8989
9456
  var QuickActionsRow = import_styled17.default.div`
8990
9457
  display: flex;