@xinghunm/ai-chat 1.3.3 → 1.4.1

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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/ai-chat/index.tsx
2
- import { useEffect as useEffect10 } from "react";
2
+ import { useEffect as useEffect11 } from "react";
3
3
  import styled17 from "@emotion/styled";
4
4
  import { ConfigProvider } from "@xinghunm/compass-ui";
5
5
 
@@ -23,6 +23,7 @@ var DEFAULT_AI_CHAT_LABELS = {
23
23
  stopButton: "Stop",
24
24
  retryButton: "Retry",
25
25
  scrollToLatest: "Jump to latest",
26
+ sessionsTitle: "Sessions",
26
27
  placeholder: "Ask something...",
27
28
  modeLabelAsk: "Ask",
28
29
  modeLabelPlan: "Plan",
@@ -53,7 +54,10 @@ var DEFAULT_AI_CHAT_LABELS = {
53
54
  modelUnavailable: "No model available",
54
55
  skillLoading: "Loading skills...",
55
56
  skillEmpty: "No matching skills",
56
- removeSkillAriaLabel: "Remove skill"
57
+ removeSkillAriaLabel: "Remove skill",
58
+ sessionHistoryLoading: "Loading conversations...",
59
+ sessionHistoryLoadFailed: "Failed to load conversations",
60
+ sessionHistoryEmpty: "No conversations yet"
57
61
  };
58
62
 
59
63
  // src/lib/chat-session.ts
@@ -63,7 +67,6 @@ var createDraftChatSessionId = () => `${DRAFT_CHAT_SESSION_ID_PREFIX}${Date.now(
63
67
  var isDraftChatSessionId = (sessionId) => Boolean(sessionId?.startsWith(DRAFT_CHAT_SESSION_ID_PREFIX));
64
68
  var createDraftChatSession = ({
65
69
  model,
66
- mode = DEFAULT_CHAT_AGENT_MODE,
67
70
  nowIso: nowIso2,
68
71
  createSessionId = createDraftChatSessionId
69
72
  }) => {
@@ -73,14 +76,27 @@ var createDraftChatSession = ({
73
76
  title: "New Chat",
74
77
  createdAt: iso,
75
78
  updatedAt: iso,
76
- model,
77
- mode
79
+ model
78
80
  };
79
81
  };
80
82
 
81
83
  // src/store/chat-store.ts
82
84
  var DEFAULT_CHAT_SESSION_TITLE = "New Chat";
83
85
  var IMAGE_MESSAGE_SESSION_TITLE = "Image message";
86
+ var createHistoryMessagePaginationState = (page) => ({
87
+ previousCursor: page?.previousCursor ?? null,
88
+ hasMorePrevious: page?.hasMorePrevious ?? Boolean(page && page.previousCursor !== null),
89
+ isLoadingPrevious: false,
90
+ error: null
91
+ });
92
+ var normalizeHistoryMessages = (sessionId, messages) => messages.map((message) => ({ ...message, sessionId }));
93
+ var mergeOlderHistoryMessages = (sessionId, olderMessages, currentMessages) => {
94
+ const currentMessageIds = new Set(currentMessages.map((message) => message.id));
95
+ const uniqueOlderMessages = normalizeHistoryMessages(sessionId, olderMessages).filter(
96
+ (message) => !currentMessageIds.has(message.id)
97
+ );
98
+ return [...uniqueOlderMessages, ...currentMessages];
99
+ };
84
100
  var resolveSessionTitleFromMessage = (message) => {
85
101
  const trimmedContent = message.content.trim();
86
102
  if (trimmedContent) {
@@ -183,19 +199,27 @@ var createChatStore = (initialState) => createStore((set, get) => ({
183
199
  isStreamingBySession: {},
184
200
  isStoppingBySession: {},
185
201
  errorBySession: {},
202
+ sessionMessageLoadStatusBySession: {},
203
+ sessionMessageLoadErrorBySession: {},
204
+ historyMessagePaginationBySession: {},
186
205
  // ---- Session management ------------------------------------------------
187
206
  createSession: (session) => {
188
207
  const state = get();
189
208
  const exists = state.sessions.some((s) => s.sessionId === session.sessionId);
190
- const nextSession = {
191
- ...session,
192
- mode: session.mode ?? DEFAULT_CHAT_AGENT_MODE
193
- };
194
- const nextSessions = exists ? state.sessions : [nextSession, ...state.sessions];
209
+ const nextSessions = exists ? state.sessions : [session, ...state.sessions];
195
210
  const nextMessagesBySession = { ...state.messagesBySession };
196
211
  const nextErrorBySession = { ...state.errorBySession };
197
212
  const nextIsStreamingBySession = { ...state.isStreamingBySession };
198
213
  const nextIsStoppingBySession = { ...state.isStoppingBySession };
214
+ const nextSessionMessageLoadStatusBySession = {
215
+ ...state.sessionMessageLoadStatusBySession
216
+ };
217
+ const nextSessionMessageLoadErrorBySession = {
218
+ ...state.sessionMessageLoadErrorBySession
219
+ };
220
+ const nextHistoryMessagePaginationBySession = {
221
+ ...state.historyMessagePaginationBySession
222
+ };
199
223
  const sid = session.sessionId;
200
224
  if (nextMessagesBySession[sid] === void 0)
201
225
  nextMessagesBySession[sid] = [];
@@ -205,20 +229,30 @@ var createChatStore = (initialState) => createStore((set, get) => ({
205
229
  nextIsStreamingBySession[sid] = false;
206
230
  if (nextIsStoppingBySession[sid] === void 0)
207
231
  nextIsStoppingBySession[sid] = false;
232
+ if (nextSessionMessageLoadStatusBySession[sid] === void 0) {
233
+ nextSessionMessageLoadStatusBySession[sid] = "loaded";
234
+ }
235
+ if (nextSessionMessageLoadErrorBySession[sid] === void 0) {
236
+ nextSessionMessageLoadErrorBySession[sid] = null;
237
+ }
238
+ if (nextHistoryMessagePaginationBySession[sid] === void 0) {
239
+ nextHistoryMessagePaginationBySession[sid] = createHistoryMessagePaginationState();
240
+ }
208
241
  set({
209
242
  sessions: nextSessions,
210
243
  activeSessionId: sid,
211
244
  messagesBySession: nextMessagesBySession,
212
245
  errorBySession: nextErrorBySession,
213
246
  isStreamingBySession: nextIsStreamingBySession,
214
- isStoppingBySession: nextIsStoppingBySession
247
+ isStoppingBySession: nextIsStoppingBySession,
248
+ sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
249
+ sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
250
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
215
251
  });
216
252
  },
217
253
  startNewChat: () => {
218
- const state = get();
219
254
  const session = createDraftChatSession({
220
255
  model: "",
221
- mode: state.preferredMode,
222
256
  nowIso: () => (/* @__PURE__ */ new Date()).toISOString()
223
257
  });
224
258
  get().createSession(session);
@@ -266,6 +300,27 @@ var createChatStore = (initialState) => createStore((set, get) => ({
266
300
  nextErrorBySession[nextSessionId] = nextErrorBySession[previousSessionId] ?? null;
267
301
  delete nextErrorBySession[previousSessionId];
268
302
  }
303
+ const nextSessionMessageLoadStatusBySession = {
304
+ ...state.sessionMessageLoadStatusBySession
305
+ };
306
+ if (previousSessionId in nextSessionMessageLoadStatusBySession) {
307
+ nextSessionMessageLoadStatusBySession[nextSessionId] = nextSessionMessageLoadStatusBySession[previousSessionId] ?? "idle";
308
+ delete nextSessionMessageLoadStatusBySession[previousSessionId];
309
+ }
310
+ const nextSessionMessageLoadErrorBySession = {
311
+ ...state.sessionMessageLoadErrorBySession
312
+ };
313
+ if (previousSessionId in nextSessionMessageLoadErrorBySession) {
314
+ nextSessionMessageLoadErrorBySession[nextSessionId] = nextSessionMessageLoadErrorBySession[previousSessionId] ?? null;
315
+ delete nextSessionMessageLoadErrorBySession[previousSessionId];
316
+ }
317
+ const nextHistoryMessagePaginationBySession = {
318
+ ...state.historyMessagePaginationBySession
319
+ };
320
+ if (previousSessionId in nextHistoryMessagePaginationBySession) {
321
+ nextHistoryMessagePaginationBySession[nextSessionId] = nextHistoryMessagePaginationBySession[previousSessionId] ?? createHistoryMessagePaginationState();
322
+ delete nextHistoryMessagePaginationBySession[previousSessionId];
323
+ }
269
324
  const nextActiveSessionId = state.activeSessionId === previousSessionId ? nextSessionId : state.activeSessionId;
270
325
  set({
271
326
  sessions: nextSessions,
@@ -274,18 +329,66 @@ var createChatStore = (initialState) => createStore((set, get) => ({
274
329
  isStreamingBySession: nextIsStreamingBySession,
275
330
  isStoppingBySession: nextIsStoppingBySession,
276
331
  errorBySession: nextErrorBySession,
332
+ sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
333
+ sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
334
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
277
335
  activeSessionId: nextActiveSessionId
278
336
  });
279
337
  },
280
338
  setPreferredMode: (mode) => {
281
339
  set({ preferredMode: mode });
282
340
  },
283
- setSessionMode: (sessionId, mode) => {
341
+ hydrateHistorySessions: (sessions) => {
284
342
  const state = get();
285
- const nextSessions = state.sessions.map(
286
- (s) => s.sessionId === sessionId ? { ...s, mode } : s
343
+ const localSessions = state.sessions.filter(
344
+ (session) => isDraftChatSessionId(session.sessionId)
287
345
  );
288
- set({ sessions: nextSessions });
346
+ const localSessionIds = new Set(localSessions.map((session) => session.sessionId));
347
+ const nextSessions = [
348
+ ...localSessions,
349
+ ...sessions.filter((session) => !localSessionIds.has(session.sessionId))
350
+ ];
351
+ const nextMessagesBySession = { ...state.messagesBySession };
352
+ const nextErrorBySession = { ...state.errorBySession };
353
+ const nextIsStreamingBySession = { ...state.isStreamingBySession };
354
+ const nextIsStoppingBySession = { ...state.isStoppingBySession };
355
+ const nextSessionMessageLoadStatusBySession = {
356
+ ...state.sessionMessageLoadStatusBySession
357
+ };
358
+ const nextSessionMessageLoadErrorBySession = {
359
+ ...state.sessionMessageLoadErrorBySession
360
+ };
361
+ const nextHistoryMessagePaginationBySession = {
362
+ ...state.historyMessagePaginationBySession
363
+ };
364
+ nextSessions.forEach((session) => {
365
+ const sid = session.sessionId;
366
+ if (nextErrorBySession[sid] === void 0)
367
+ nextErrorBySession[sid] = null;
368
+ if (nextIsStreamingBySession[sid] === void 0)
369
+ nextIsStreamingBySession[sid] = false;
370
+ if (nextIsStoppingBySession[sid] === void 0)
371
+ nextIsStoppingBySession[sid] = false;
372
+ if (nextSessionMessageLoadStatusBySession[sid] === void 0) {
373
+ nextSessionMessageLoadStatusBySession[sid] = "idle";
374
+ }
375
+ if (nextSessionMessageLoadErrorBySession[sid] === void 0) {
376
+ nextSessionMessageLoadErrorBySession[sid] = null;
377
+ }
378
+ if (nextHistoryMessagePaginationBySession[sid] === void 0) {
379
+ nextHistoryMessagePaginationBySession[sid] = createHistoryMessagePaginationState();
380
+ }
381
+ });
382
+ set({
383
+ sessions: nextSessions,
384
+ messagesBySession: nextMessagesBySession,
385
+ errorBySession: nextErrorBySession,
386
+ isStreamingBySession: nextIsStreamingBySession,
387
+ isStoppingBySession: nextIsStoppingBySession,
388
+ sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
389
+ sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
390
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
391
+ });
289
392
  },
290
393
  // ---- Message operations ------------------------------------------------
291
394
  appendMessage: (sessionId, message) => {
@@ -313,6 +416,92 @@ var createChatStore = (initialState) => createStore((set, get) => ({
313
416
  isStreamingBySession: nextIsStreamingBySession
314
417
  });
315
418
  },
419
+ hydrateHistorySessionMessages: (sessionId, messages) => {
420
+ const state = get();
421
+ set({
422
+ messagesBySession: {
423
+ ...state.messagesBySession,
424
+ [sessionId]: normalizeHistoryMessages(sessionId, messages)
425
+ },
426
+ sessionMessageLoadStatusBySession: {
427
+ ...state.sessionMessageLoadStatusBySession,
428
+ [sessionId]: "loaded"
429
+ },
430
+ sessionMessageLoadErrorBySession: {
431
+ ...state.sessionMessageLoadErrorBySession,
432
+ [sessionId]: null
433
+ },
434
+ historyMessagePaginationBySession: {
435
+ ...state.historyMessagePaginationBySession,
436
+ [sessionId]: createHistoryMessagePaginationState()
437
+ }
438
+ });
439
+ },
440
+ hydrateHistorySessionMessagesPage: (sessionId, page) => {
441
+ const state = get();
442
+ set({
443
+ messagesBySession: {
444
+ ...state.messagesBySession,
445
+ [sessionId]: normalizeHistoryMessages(sessionId, page.messages)
446
+ },
447
+ sessionMessageLoadStatusBySession: {
448
+ ...state.sessionMessageLoadStatusBySession,
449
+ [sessionId]: "loaded"
450
+ },
451
+ sessionMessageLoadErrorBySession: {
452
+ ...state.sessionMessageLoadErrorBySession,
453
+ [sessionId]: null
454
+ },
455
+ historyMessagePaginationBySession: {
456
+ ...state.historyMessagePaginationBySession,
457
+ [sessionId]: createHistoryMessagePaginationState(page)
458
+ }
459
+ });
460
+ },
461
+ prependHistorySessionMessagesPage: (sessionId, page) => {
462
+ const state = get();
463
+ set({
464
+ messagesBySession: {
465
+ ...state.messagesBySession,
466
+ [sessionId]: mergeOlderHistoryMessages(
467
+ sessionId,
468
+ page.messages,
469
+ state.messagesBySession[sessionId] ?? []
470
+ )
471
+ },
472
+ historyMessagePaginationBySession: {
473
+ ...state.historyMessagePaginationBySession,
474
+ [sessionId]: createHistoryMessagePaginationState(page)
475
+ }
476
+ });
477
+ },
478
+ setHistorySessionPreviousMessagesLoadStatus: (sessionId, isLoading, error2 = null) => {
479
+ const state = get();
480
+ const current = state.historyMessagePaginationBySession[sessionId] ?? createHistoryMessagePaginationState();
481
+ set({
482
+ historyMessagePaginationBySession: {
483
+ ...state.historyMessagePaginationBySession,
484
+ [sessionId]: {
485
+ ...current,
486
+ isLoadingPrevious: isLoading,
487
+ error: error2
488
+ }
489
+ }
490
+ });
491
+ },
492
+ setHistorySessionMessageLoadStatus: (sessionId, status, error2 = null) => {
493
+ const state = get();
494
+ set({
495
+ sessionMessageLoadStatusBySession: {
496
+ ...state.sessionMessageLoadStatusBySession,
497
+ [sessionId]: status
498
+ },
499
+ sessionMessageLoadErrorBySession: {
500
+ ...state.sessionMessageLoadErrorBySession,
501
+ [sessionId]: error2
502
+ }
503
+ });
504
+ },
316
505
  startStreamingMessage: (sessionId, message) => {
317
506
  const state = get();
318
507
  set({
@@ -760,6 +949,10 @@ var AiChatProvider = (props) => {
760
949
  handleQuestionnaireSubmit,
761
950
  handleConfirmationSubmit,
762
951
  messageRenderOrder,
952
+ historySessionList,
953
+ onLoadMoreSessions,
954
+ onSelectHistorySession,
955
+ onLoadMoreHistoryMessages,
763
956
  enableImageAttachments = true,
764
957
  children
765
958
  } = props;
@@ -836,7 +1029,11 @@ var AiChatProvider = (props) => {
836
1029
  handleConfirmationSubmit,
837
1030
  messageRenderOrder,
838
1031
  transformStreamPacket: defaultTransformStreamPacket,
839
- enableImageAttachments
1032
+ enableImageAttachments,
1033
+ historySessionList,
1034
+ onLoadMoreSessions,
1035
+ onSelectHistorySession,
1036
+ onLoadMoreHistoryMessages
840
1037
  }),
841
1038
  [
842
1039
  axiosInstance,
@@ -846,8 +1043,12 @@ var AiChatProvider = (props) => {
846
1043
  enableImageAttachments,
847
1044
  handleConfirmationSubmit,
848
1045
  handleQuestionnaireSubmit,
1046
+ historySessionList,
849
1047
  labels,
850
1048
  messageRenderOrder,
1049
+ onLoadMoreSessions,
1050
+ onLoadMoreHistoryMessages,
1051
+ onSelectHistorySession,
851
1052
  renderMessageBlock,
852
1053
  sendRef,
853
1054
  retryRef,
@@ -2879,9 +3080,8 @@ var ChatMessageItemView = ({
2879
3080
  const hasMarkdownOnlyBlocks = hasStructuredBlocks && blocks.every((block) => block.type === "markdown");
2880
3081
  const hasTextContent = Boolean(settledContent || freshContent || displayedContent);
2881
3082
  const shouldRenderStructuredBlocks = hasStructuredBlocks && !(isAssistantStreaming && hasMarkdownOnlyBlocks && hasTextContent);
2882
- const isPlanMode = mode === "plan";
2883
- const canSubmitConfirmation = isPlanMode && typeof onConfirmationSubmit === "function";
2884
- const canSubmitQuestionnaire = isPlanMode && typeof onQuestionnaireSubmit === "function";
3083
+ const canSubmitConfirmation = typeof onConfirmationSubmit === "function";
3084
+ const canSubmitQuestionnaire = typeof onQuestionnaireSubmit === "function";
2885
3085
  const shouldShowStreamingCaret = isAssistantStreaming && (!shouldRenderStructuredBlocks || hasTextContent);
2886
3086
  const isUserMessage = message.role === "user";
2887
3087
  const messageRenderMode = isUserMessage ? "plain-text" : "markdown";
@@ -2939,7 +3139,7 @@ var ChatMessageItemView = ({
2939
3139
  ExecutionConfirmationCard,
2940
3140
  {
2941
3141
  proposal: block.proposal,
2942
- interactive: isPlanMode,
3142
+ interactive: canSubmitConfirmation,
2943
3143
  onConfirm: canSubmitConfirmation ? () => onConfirmationSubmit({
2944
3144
  proposalId: block.proposal.proposalId,
2945
3145
  content: createExecutionConfirmationContent(block.proposal),
@@ -3163,15 +3363,11 @@ var Bubble = styled7.article`
3163
3363
 
3164
3364
  &[data-role='user'] {
3165
3365
  width: auto;
3166
- max-width: min(760px, 100%);
3366
+ max-width: 100%;
3167
3367
  margin-left: auto;
3168
- padding: 14px 16px;
3169
- border-radius: 22px;
3170
- background: linear-gradient(180deg, rgba(59, 59, 63, 0.9) 0%, rgba(42, 43, 46, 0.92) 100%);
3171
- border: 1px solid rgba(255, 255, 255, 0.07);
3172
- box-shadow:
3173
- inset 0 1px 0 rgba(255, 255, 255, 0.03),
3174
- 0 12px 30px rgba(0, 0, 0, 0.18);
3368
+ padding: 8px 12px;
3369
+ background: #282825;
3370
+ border-radius: 16px;
3175
3371
  }
3176
3372
  `;
3177
3373
  var Header2 = styled7.div`
@@ -3503,6 +3699,7 @@ var HeroSubtitle = styled8.p`
3503
3699
  // src/components/chat-thread/index.tsx
3504
3700
  import { jsx as jsx10, jsxs as jsxs7 } from "@emotion/react/jsx-runtime";
3505
3701
  var CHAT_THREAD_PINNED_THRESHOLD_PX = 32;
3702
+ var CHAT_THREAD_LOAD_PREVIOUS_THRESHOLD_PX = 80;
3506
3703
  var isThreadPinnedToBottom = (container) => container.scrollHeight - container.clientHeight - container.scrollTop <= CHAT_THREAD_PINNED_THRESHOLD_PX;
3507
3704
  var renderChatMessage = ({
3508
3705
  message,
@@ -3572,13 +3769,17 @@ var groupConversationTurns = (historyMessages, streamingMessage) => {
3572
3769
  ];
3573
3770
  };
3574
3771
  var ChatThreadView = ({
3575
- activeSessionMode = DEFAULT_CHAT_AGENT_MODE,
3772
+ activeMode = DEFAULT_CHAT_AGENT_MODE,
3576
3773
  historyMessages,
3577
3774
  streamingMessage,
3578
3775
  error: error2,
3776
+ isLoadingPreviousMessages = false,
3777
+ previousMessagesError,
3579
3778
  retryButtonLabel,
3580
3779
  scrollToLatestLabel,
3780
+ sessionHistoryLoadingLabel,
3581
3781
  onRetry,
3782
+ onLoadPreviousMessages,
3582
3783
  onConfirmationSubmit,
3583
3784
  onQuestionnaireSubmit,
3584
3785
  renderMessageBlock
@@ -3593,6 +3794,7 @@ var ChatThreadView = ({
3593
3794
  const latestHistoryMessage = historyMessages[historyMessages.length - 1];
3594
3795
  const latestTurnRef = useRef5(null);
3595
3796
  const reservedSpaceFrameRef = useRef5(null);
3797
+ const isLoadingPreviousRef = useRef5(false);
3596
3798
  const isPinnedRef = useRef5(true);
3597
3799
  const lastHistoryMessageIdRef = useRef5(latestHistoryMessage?.id);
3598
3800
  const lastStreamingMessageIdRef = useRef5(streamingMessage?.id);
@@ -3639,17 +3841,45 @@ var ChatThreadView = ({
3639
3841
  },
3640
3842
  [markThreadPinned, scrollToBottom]
3641
3843
  );
3844
+ const handleLoadPreviousMessages = useCallback3(async () => {
3845
+ const container = containerRef.current;
3846
+ if (!container || !onLoadPreviousMessages || isLoadingPreviousMessages) {
3847
+ return;
3848
+ }
3849
+ if (isLoadingPreviousRef.current) {
3850
+ return;
3851
+ }
3852
+ isLoadingPreviousRef.current = true;
3853
+ const previousScrollHeight = container.scrollHeight;
3854
+ const previousScrollTop = container.scrollTop;
3855
+ try {
3856
+ await onLoadPreviousMessages();
3857
+ } catch {
3858
+ return;
3859
+ } finally {
3860
+ isLoadingPreviousRef.current = false;
3861
+ }
3862
+ window.requestAnimationFrame(() => {
3863
+ const nextContainer = containerRef.current;
3864
+ if (!nextContainer)
3865
+ return;
3866
+ nextContainer.scrollTop = nextContainer.scrollHeight - previousScrollHeight + previousScrollTop;
3867
+ });
3868
+ }, [isLoadingPreviousMessages, onLoadPreviousMessages]);
3642
3869
  const handleContainerScroll = useCallback3(() => {
3643
3870
  const container = containerRef.current;
3644
3871
  if (!container)
3645
3872
  return;
3873
+ if (onLoadPreviousMessages && container.scrollTop <= CHAT_THREAD_LOAD_PREVIOUS_THRESHOLD_PX) {
3874
+ void handleLoadPreviousMessages();
3875
+ }
3646
3876
  const nextPinned = isThreadPinnedToBottom(container);
3647
3877
  isPinnedRef.current = nextPinned;
3648
3878
  setIsDetached(!nextPinned);
3649
3879
  if (nextPinned) {
3650
3880
  setPendingNewMessageCount(0);
3651
3881
  }
3652
- }, []);
3882
+ }, [handleLoadPreviousMessages, onLoadPreviousMessages]);
3653
3883
  useLayoutEffect3(() => {
3654
3884
  const nextHistoryMessageId = latestHistoryMessage?.id;
3655
3885
  if (lastHistoryMessageIdRef.current === nextHistoryMessageId) {
@@ -3777,6 +4007,8 @@ var ChatThreadView = ({
3777
4007
  }, [latestTurn, scrollToBottom]);
3778
4008
  return /* @__PURE__ */ jsxs7(ThreadViewport, { children: [
3779
4009
  /* @__PURE__ */ jsxs7(Container, { ref: containerRef, "data-testid": "chat-thread", onScroll: handleContainerScroll, children: [
4010
+ isLoadingPreviousMessages ? /* @__PURE__ */ jsx10(PreviousMessagesStateRow, { "data-testid": "chat-thread-loading-previous", children: sessionHistoryLoadingLabel }) : null,
4011
+ previousMessagesError ? /* @__PURE__ */ jsx10(PreviousMessagesStateRow, { "data-testid": "chat-thread-load-previous-error", children: previousMessagesError }) : null,
3780
4012
  conversationTurns.map((turn, turnIndex) => {
3781
4013
  const isLatestTurn = turnIndex === conversationTurns.length - 1;
3782
4014
  return /* @__PURE__ */ jsxs7(
@@ -3793,7 +4025,7 @@ var ChatThreadView = ({
3793
4025
  style: isLatestTurn ? { scrollMarginTop: `${CHAT_THREAD_SCROLL_TOP_GAP}px` } : void 0,
3794
4026
  children: renderChatMessage({
3795
4027
  message: turn.userMessage,
3796
- mode: activeSessionMode,
4028
+ mode: activeMode,
3797
4029
  onConfirmationSubmit,
3798
4030
  onQuestionnaireSubmit,
3799
4031
  renderMessageBlock
@@ -3802,7 +4034,7 @@ var ChatThreadView = ({
3802
4034
  ) : null,
3803
4035
  turn.responseMessages.map((message) => /* @__PURE__ */ jsx10(MessageSlot, { children: renderChatMessage({
3804
4036
  message,
3805
- mode: activeSessionMode,
4037
+ mode: activeMode,
3806
4038
  onConfirmationSubmit,
3807
4039
  onQuestionnaireSubmit,
3808
4040
  renderMessageBlock
@@ -3833,14 +4065,25 @@ var EMPTY_MESSAGES = [];
3833
4065
  var ChatThread = () => {
3834
4066
  const activeSessionId = useChatStore((s) => s.activeSessionId);
3835
4067
  const hasSessions = useChatStore((s) => s.sessions.length > 0);
3836
- const activeSessionMode = useChatStore(
3837
- (s) => s.sessions.find((x) => x.sessionId === s.activeSessionId)?.mode ?? DEFAULT_CHAT_AGENT_MODE
4068
+ const activeSession = useChatStore(
4069
+ (s) => s.sessions.find((session) => session.sessionId === s.activeSessionId)
3838
4070
  );
4071
+ const preferredMode = useChatStore((s) => s.preferredMode);
3839
4072
  const messages = useChatStore(
3840
4073
  (s) => s.messagesBySession[s.activeSessionId ?? ""] ?? EMPTY_MESSAGES
3841
4074
  );
4075
+ const sessionMessageLoadStatus = useChatStore(
4076
+ (s) => s.sessionMessageLoadStatusBySession[s.activeSessionId ?? ""]
4077
+ );
3842
4078
  const streamingMessage = useChatStore((s) => s.streamingMessageBySession[s.activeSessionId ?? ""]);
3843
4079
  const error2 = useChatStore((s) => s.errorBySession[s.activeSessionId ?? ""]);
4080
+ const historyMessagePagination = useChatStore(
4081
+ (s) => s.historyMessagePaginationBySession[s.activeSessionId ?? ""]
4082
+ );
4083
+ const prependHistorySessionMessagesPage = useChatStore((s) => s.prependHistorySessionMessagesPage);
4084
+ const setHistorySessionPreviousMessagesLoadStatus = useChatStore(
4085
+ (s) => s.setHistorySessionPreviousMessagesLoadStatus
4086
+ );
3844
4087
  const updateQA = useChatStore((s) => s.updateQuestionnaireAnswers);
3845
4088
  const clearSessionError = useChatStore((s) => s.clearSessionError);
3846
4089
  const {
@@ -3849,6 +4092,7 @@ var ChatThread = () => {
3849
4092
  renderMessageBlock,
3850
4093
  handleQuestionnaireSubmit: customQuestionnaireSubmit,
3851
4094
  handleConfirmationSubmit: customConfirmationSubmit,
4095
+ onLoadMoreHistoryMessages,
3852
4096
  labels
3853
4097
  } = useChatContext();
3854
4098
  const handleRetry = useCallback3(() => {
@@ -3863,7 +4107,7 @@ var ChatThread = () => {
3863
4107
  if (customQuestionnaireSubmit) {
3864
4108
  const handled = await customQuestionnaireSubmit(submission, {
3865
4109
  sessionId: sourceSessionId ?? void 0,
3866
- mode: activeSessionMode
4110
+ mode: preferredMode
3867
4111
  });
3868
4112
  if (handled !== false) {
3869
4113
  if (sourceSessionId && submission.sourceMessageId) {
@@ -3890,7 +4134,7 @@ var ChatThread = () => {
3890
4134
  );
3891
4135
  }
3892
4136
  },
3893
- [activeSessionId, activeSessionMode, updateQA, sendRef, customQuestionnaireSubmit]
4137
+ [activeSessionId, customQuestionnaireSubmit, preferredMode, sendRef, updateQA]
3894
4138
  );
3895
4139
  const handleConfirmation = useCallback3(
3896
4140
  async (submission) => {
@@ -3898,7 +4142,7 @@ var ChatThread = () => {
3898
4142
  if (customConfirmationSubmit) {
3899
4143
  const handled = await customConfirmationSubmit(submission, {
3900
4144
  sessionId: sourceSessionId ?? void 0,
3901
- mode: activeSessionMode
4145
+ mode: preferredMode
3902
4146
  });
3903
4147
  if (handled !== false) {
3904
4148
  return;
@@ -3909,21 +4153,61 @@ var ChatThread = () => {
3909
4153
  includeComposerAttachments: false
3910
4154
  });
3911
4155
  },
3912
- [activeSessionId, activeSessionMode, sendRef, customConfirmationSubmit]
4156
+ [activeSessionId, customConfirmationSubmit, preferredMode, sendRef]
4157
+ );
4158
+ const handleLoadPreviousMessages = useCallback3(async () => {
4159
+ if (!activeSession || !onLoadMoreHistoryMessages || !historyMessagePagination?.hasMorePrevious || !historyMessagePagination.previousCursor || historyMessagePagination.isLoadingPrevious) {
4160
+ return;
4161
+ }
4162
+ setHistorySessionPreviousMessagesLoadStatus(activeSession.sessionId, true);
4163
+ try {
4164
+ const page = await onLoadMoreHistoryMessages({
4165
+ session: activeSession,
4166
+ cursor: historyMessagePagination.previousCursor
4167
+ });
4168
+ if (page) {
4169
+ prependHistorySessionMessagesPage(activeSession.sessionId, page);
4170
+ return;
4171
+ }
4172
+ setHistorySessionPreviousMessagesLoadStatus(activeSession.sessionId, false);
4173
+ } catch (error3) {
4174
+ setHistorySessionPreviousMessagesLoadStatus(
4175
+ activeSession.sessionId,
4176
+ false,
4177
+ error3 instanceof Error ? error3.message : String(error3)
4178
+ );
4179
+ throw error3;
4180
+ }
4181
+ }, [
4182
+ activeSession,
4183
+ historyMessagePagination,
4184
+ onLoadMoreHistoryMessages,
4185
+ prependHistorySessionMessagesPage,
4186
+ setHistorySessionPreviousMessagesLoadStatus
4187
+ ]);
4188
+ const canLoadPreviousMessages = Boolean(
4189
+ activeSession && onLoadMoreHistoryMessages && historyMessagePagination?.hasMorePrevious && historyMessagePagination.previousCursor && !historyMessagePagination.isLoadingPrevious
3913
4190
  );
4191
+ if (hasSessions && sessionMessageLoadStatus === "loading" && messages.length === 0 && !streamingMessage) {
4192
+ return /* @__PURE__ */ jsx10(ThreadStateViewport, { "data-testid": "chat-thread-loading-state", children: /* @__PURE__ */ jsx10(ThreadStateText, { children: labels.sessionHistoryLoading }) });
4193
+ }
3914
4194
  if (!hasSessions || messages.length === 0 && !streamingMessage) {
3915
4195
  return /* @__PURE__ */ jsx10(ChatThreadEmptyState, {});
3916
4196
  }
3917
4197
  return /* @__PURE__ */ jsx10(
3918
4198
  ChatThreadView,
3919
4199
  {
3920
- activeSessionMode,
4200
+ activeMode: preferredMode,
3921
4201
  historyMessages: messages,
3922
4202
  streamingMessage,
3923
4203
  error: error2,
4204
+ isLoadingPreviousMessages: historyMessagePagination?.isLoadingPrevious,
4205
+ previousMessagesError: historyMessagePagination?.error,
3924
4206
  retryButtonLabel: labels.retryButton,
3925
4207
  scrollToLatestLabel: labels.scrollToLatest,
4208
+ sessionHistoryLoadingLabel: labels.sessionHistoryLoading,
3926
4209
  onRetry: handleRetry,
4210
+ onLoadPreviousMessages: canLoadPreviousMessages ? handleLoadPreviousMessages : void 0,
3927
4211
  onConfirmationSubmit: handleConfirmation,
3928
4212
  onQuestionnaireSubmit: handleQuestionnaireSubmit,
3929
4213
  renderMessageBlock
@@ -3937,6 +4221,18 @@ var ThreadViewport = styled9.div`
3937
4221
  flex: 1;
3938
4222
  min-height: 0;
3939
4223
  `;
4224
+ var ThreadStateViewport = styled9.div`
4225
+ display: flex;
4226
+ flex: 1;
4227
+ min-height: 0;
4228
+ align-items: center;
4229
+ justify-content: center;
4230
+ padding: 24px;
4231
+ `;
4232
+ var ThreadStateText = styled9.div`
4233
+ color: var(--text-secondary, rgba(255, 255, 255, 0.64));
4234
+ font-size: 14px;
4235
+ `;
3940
4236
  var Container = styled9.div`
3941
4237
  display: flex;
3942
4238
  flex: 1;
@@ -3944,7 +4240,7 @@ var Container = styled9.div`
3944
4240
  gap: 18px;
3945
4241
  min-height: 0;
3946
4242
  overflow: auto;
3947
- padding: 24px 24px 88px;
4243
+ padding: 24px 16px 88px;
3948
4244
  overscroll-behavior: contain;
3949
4245
 
3950
4246
  &::-webkit-scrollbar {
@@ -3963,10 +4259,23 @@ var Container = styled9.div`
3963
4259
  var MessageSlot = styled9.div`
3964
4260
  display: flex;
3965
4261
  `;
4262
+ var PreviousMessagesStateRow = styled9.div`
4263
+ width: 100%;
4264
+ max-width: var(--chat-content-max-width, 48rem);
4265
+ margin-right: auto;
4266
+ margin-left: auto;
4267
+ color: var(--text-secondary, rgba(255, 255, 255, 0.64));
4268
+ font-size: 13px;
4269
+ text-align: center;
4270
+ `;
3966
4271
  var ConversationTurn = styled9.div`
3967
4272
  display: flex;
3968
4273
  flex-direction: column;
3969
4274
  gap: 18px;
4275
+ width: 100%;
4276
+ max-width: var(--chat-content-max-width, 48rem);
4277
+ margin-right: auto;
4278
+ margin-left: auto;
3970
4279
  `;
3971
4280
  var ErrorText = styled9.div`
3972
4281
  color: #ff7b72;
@@ -6620,7 +6929,6 @@ var resolveSelectedChatModel = ({
6620
6929
  var resolveSendSession = ({
6621
6930
  activeSessionId,
6622
6931
  selectedModel,
6623
- selectedMode,
6624
6932
  nowIso: nowIso2,
6625
6933
  createSessionId
6626
6934
  }) => {
@@ -6632,7 +6940,6 @@ var resolveSendSession = ({
6632
6940
  }
6633
6941
  const session = createDraftChatSession({
6634
6942
  model: selectedModel,
6635
- mode: selectedMode ?? DEFAULT_CHAT_AGENT_MODE,
6636
6943
  nowIso: nowIso2,
6637
6944
  createSessionId
6638
6945
  });
@@ -6790,9 +7097,6 @@ var useChatComposer = () => {
6790
7097
  const skillsLoader = transport.getSkills;
6791
7098
  const activeSkillsLoaderRef = useRef10(skillsLoader);
6792
7099
  const activeSessionId = useChatStore((s) => s.activeSessionId);
6793
- const activeSession = useChatStore(
6794
- (s) => s.sessions.find((x) => x.sessionId === s.activeSessionId) ?? null
6795
- );
6796
7100
  const preferredMode = useChatStore((s) => s.preferredMode);
6797
7101
  const streamingSessionId = useChatStore(
6798
7102
  (s) => s.activeSessionId && s.isStreamingBySession[s.activeSessionId] ? s.activeSessionId : null
@@ -6813,7 +7117,6 @@ var useChatComposer = () => {
6813
7117
  const setSessionError = useChatStore((s) => s.setSessionError);
6814
7118
  const clearSessionError = useChatStore((s) => s.clearSessionError);
6815
7119
  const setPreferredMode = useChatStore((s) => s.setPreferredMode);
6816
- const setSessionMode = useChatStore((s) => s.setSessionMode);
6817
7120
  const [availableModels, setAvailableModels] = useState8([]);
6818
7121
  const [isModelsLoading, setIsModelsLoading] = useState8(true);
6819
7122
  const [isModelsError, setIsModelsError] = useState8(false);
@@ -6894,12 +7197,8 @@ var useChatComposer = () => {
6894
7197
  );
6895
7198
  }, [availableModels, isModelsLoading]);
6896
7199
  useEffect8(() => {
6897
- if (activeSession) {
6898
- setSelectedModeLocal(activeSession.mode ?? DEFAULT_CHAT_AGENT_MODE);
6899
- return;
6900
- }
6901
7200
  setSelectedModeLocal(preferredMode ?? DEFAULT_CHAT_AGENT_MODE);
6902
- }, [activeSession, preferredMode]);
7201
+ }, [preferredMode]);
6903
7202
  useEffect8(() => {
6904
7203
  if (previousActiveSessionIdRef.current !== activeSessionId) {
6905
7204
  setSelectedSkills([]);
@@ -7107,7 +7406,7 @@ var useChatComposer = () => {
7107
7406
  const storeState = store.getState();
7108
7407
  const currentActiveSessionId = options?.sessionId ?? storeState.activeSessionId;
7109
7408
  const currentActiveSession = storeState.sessions.find((session2) => session2.sessionId === currentActiveSessionId) ?? null;
7110
- const currentMode = currentActiveSession?.mode ?? selectedMode;
7409
+ const currentMode = selectedMode;
7111
7410
  if (!(selectedModel || currentActiveSession?.model || availableModels[0]?.id)) {
7112
7411
  return;
7113
7412
  }
@@ -7115,7 +7414,6 @@ var useChatComposer = () => {
7115
7414
  const { localSessionId, sessionId, session } = resolveSendSession({
7116
7415
  activeSessionId: currentActiveSessionId,
7117
7416
  selectedModel: resolvedModel,
7118
- selectedMode: currentMode,
7119
7417
  nowIso,
7120
7418
  createSessionId: createDraftChatSessionId
7121
7419
  });
@@ -7263,8 +7561,6 @@ var useChatComposer = () => {
7263
7561
  setSelectedMode: (mode) => {
7264
7562
  setSelectedModeLocal(mode);
7265
7563
  setPreferredMode(mode);
7266
- if (activeSessionId)
7267
- setSessionMode(activeSessionId, mode);
7268
7564
  if (activeSessionId) {
7269
7565
  const previousRequest = lastRequestBySessionRef.current.get(activeSessionId);
7270
7566
  if (previousRequest) {
@@ -7754,7 +8050,7 @@ var PrimaryButton = styled13(Button)`
7754
8050
  min-width: 24px;
7755
8051
  width: 24px;
7756
8052
  height: 24px;
7757
- background: ${({ $canSend }) => $canSend ? "#fcfbf8" : "rgba(255, 255, 255, 0.3)"};
8053
+ background: ${({ $canSend }) => $canSend ? "#fcfbf8" : "rgba(252,251,248,0.3);"};
7758
8054
  color: ${({ $canSend }) => $canSend ? "#5b5448" : "rgba(255, 255, 255, 0.72)"};
7759
8055
  border-radius: 12px;
7760
8056
  border: 1px solid ${({ $canSend }) => $canSend ? "rgba(198, 188, 170, 0.38)" : "transparent"};
@@ -7765,7 +8061,7 @@ var PrimaryButton = styled13(Button)`
7765
8061
  }
7766
8062
 
7767
8063
  &:hover:not(:disabled) {
7768
- background: ${({ $canSend }) => $canSend ? "#f7f4ec" : "rgba(255, 255, 255, 0.3)"};
8064
+ background: ${({ $canSend }) => $canSend ? "#f7f4ec" : "rgba(252,251,248,0.3);"};
7769
8065
  color: ${({ $canSend }) => $canSend ? "#4f493f" : "rgba(255, 255, 255, 0.72)"};
7770
8066
  border-color: ${({ $canSend }) => $canSend ? "rgba(198, 188, 170, 0.46)" : "transparent"};
7771
8067
  }
@@ -7833,7 +8129,7 @@ var StopSpinner = styled13.span`
7833
8129
  import { jsx as jsx16, jsxs as jsxs11 } from "@emotion/react/jsx-runtime";
7834
8130
  var CHAT_COMPOSER_LINE_HEIGHT_PX = 20;
7835
8131
  var CHAT_COMPOSER_MAX_ROWS = 7;
7836
- var CHAT_COMPOSER_PADDING_TOP_PX = 8;
8132
+ var CHAT_COMPOSER_PADDING_TOP_PX = 12;
7837
8133
  var CHAT_COMPOSER_PADDING_BOTTOM_PX = 12;
7838
8134
  var CHAT_COMPOSER_PADDING_BLOCK_PX = CHAT_COMPOSER_PADDING_TOP_PX + CHAT_COMPOSER_PADDING_BOTTOM_PX;
7839
8135
  var CHAT_COMPOSER_MIN_ROWS = 4;
@@ -8068,6 +8364,10 @@ var ChatComposerView = ({
8068
8364
  setActiveSkillNavigation({ queryKey: "", index: 0 });
8069
8365
  };
8070
8366
  const handleKeyDown = (event) => {
8367
+ const isImeComposing = event.nativeEvent.isComposing || event.keyCode === 229;
8368
+ if (event.key === "Enter" && isImeComposing) {
8369
+ return;
8370
+ }
8071
8371
  if (skillQueryMatch) {
8072
8372
  if (event.key === "ArrowDown" && filteredSkills.length > 0) {
8073
8373
  event.preventDefault();
@@ -8345,7 +8645,7 @@ var Surface = styled14.div`
8345
8645
  'input'
8346
8646
  'footer';
8347
8647
  width: 100%;
8348
- max-width: 760px;
8648
+ max-width: var(--chat-content-max-width, 48rem);
8349
8649
  margin: 0 auto;
8350
8650
  background: var(--border-color);
8351
8651
  border-radius: 20px;
@@ -8606,36 +8906,28 @@ var SkillButton = styled14.button`
8606
8906
  `;
8607
8907
 
8608
8908
  // src/components/chat-conversation-list/index.tsx
8909
+ import { useEffect as useEffect10, useMemo as useMemo7, useRef as useRef12 } from "react";
8609
8910
  import styled16 from "@emotion/styled";
8610
8911
 
8611
8912
  // src/components/chat-conversation-list/components/chat-session-item.tsx
8612
8913
  import { memo as memo2 } from "react";
8613
8914
  import styled15 from "@emotion/styled";
8614
- import { jsx as jsx17, jsxs as jsxs12 } from "@emotion/react/jsx-runtime";
8615
- var ChatSessionItem = memo2(
8616
- ({ session, isActive, modeLabel, onClick }) => {
8617
- return /* @__PURE__ */ jsx17(
8618
- SessionButton,
8619
- {
8620
- type: "button",
8621
- "data-active": isActive,
8622
- onClick: () => onClick(session.sessionId),
8623
- children: /* @__PURE__ */ jsxs12(SessionMeta, { children: [
8624
- /* @__PURE__ */ jsx17(SessionTitle, { children: session.title }),
8625
- /* @__PURE__ */ jsx17(ModeBadge, { children: modeLabel })
8626
- ] })
8627
- }
8628
- );
8629
- }
8630
- );
8915
+ import { jsx as jsx17 } from "@emotion/react/jsx-runtime";
8916
+ var ChatSessionItem = memo2(({ session, isActive, onClick }) => {
8917
+ return /* @__PURE__ */ jsx17(
8918
+ SessionButton,
8919
+ {
8920
+ type: "button",
8921
+ "data-active": isActive,
8922
+ "data-testid": `chat-session-item-${session.sessionId}`,
8923
+ onClick: () => onClick(session.sessionId),
8924
+ children: /* @__PURE__ */ jsx17(SessionTitle, { children: session.title })
8925
+ }
8926
+ );
8927
+ });
8631
8928
  ChatSessionItem.displayName = "ChatSessionItem";
8632
- var SessionMeta = styled15.div`
8633
- display: flex;
8634
- align-items: center;
8635
- justify-content: space-between;
8636
- gap: 8px;
8637
- `;
8638
8929
  var SessionTitle = styled15.span`
8930
+ display: block;
8639
8931
  min-width: 0;
8640
8932
  overflow: hidden;
8641
8933
  text-overflow: ellipsis;
@@ -8649,51 +8941,178 @@ var SessionButton = styled15.button`
8649
8941
  color: var(--text-primary);
8650
8942
  background: rgba(255, 255, 255, 0.03);
8651
8943
  cursor: pointer;
8944
+ transition:
8945
+ background-color 160ms ease,
8946
+ border-color 160ms ease,
8947
+ transform 160ms ease;
8948
+
8949
+ &:hover {
8950
+ background: rgba(255, 255, 255, 0.06);
8951
+ border-color: rgba(255, 255, 255, 0.08);
8952
+ }
8953
+
8954
+ &:focus-visible {
8955
+ outline: none;
8956
+ background: rgba(255, 255, 255, 0.06);
8957
+ border-color: rgba(255, 255, 255, 0.18);
8958
+ }
8959
+
8960
+ &:active {
8961
+ transform: scale(0.995);
8962
+ }
8652
8963
 
8653
8964
  &[data-active='true'] {
8654
8965
  border-color: rgba(255, 255, 255, 0.2);
8655
8966
  background: rgba(255, 255, 255, 0.08);
8656
8967
  }
8657
8968
  `;
8658
- var ModeBadge = styled15.span`
8659
- flex-shrink: 0;
8660
- border-radius: 999px;
8661
- border: 1px solid rgba(255, 255, 255, 0.1);
8662
- padding: 4px 10px;
8663
- font-size: 11px;
8664
- line-height: 1;
8665
- color: var(--text-secondary);
8666
- background: rgba(255, 255, 255, 0.04);
8667
- `;
8969
+
8970
+ // src/components/chat-conversation-list/lib/history-session-selection.ts
8971
+ var shouldLoadHistorySessionMessages = ({
8972
+ sessionId,
8973
+ messagesBySession,
8974
+ loadStatusBySession,
8975
+ isStreamingBySession
8976
+ }) => {
8977
+ if (isStreamingBySession[sessionId])
8978
+ return false;
8979
+ if (loadStatusBySession[sessionId] === "loading")
8980
+ return false;
8981
+ if (loadStatusBySession[sessionId] === "error")
8982
+ return true;
8983
+ if (loadStatusBySession[sessionId] === "loaded")
8984
+ return false;
8985
+ return messagesBySession[sessionId] === void 0;
8986
+ };
8668
8987
 
8669
8988
  // src/components/chat-conversation-list/index.tsx
8670
- import { jsx as jsx18, jsxs as jsxs13 } from "@emotion/react/jsx-runtime";
8989
+ import { jsx as jsx18, jsxs as jsxs12 } from "@emotion/react/jsx-runtime";
8990
+ var SCROLL_LOAD_MORE_THRESHOLD_PX = 80;
8991
+ var shouldLoadMoreSessions = ({
8992
+ scrollTop,
8993
+ clientHeight,
8994
+ scrollHeight,
8995
+ threshold = SCROLL_LOAD_MORE_THRESHOLD_PX
8996
+ }) => scrollHeight - scrollTop - clientHeight <= threshold;
8997
+ var isHistorySessionMessagesPage = (value) => typeof value === "object" && value !== null && Array.isArray(value.messages);
8671
8998
  var ChatConversationList = () => {
8672
- const { labels } = useChatContext();
8673
- const sessions = useChatStore((s) => s.sessions);
8999
+ const { labels, historySessionList, onLoadMoreSessions, onSelectHistorySession, store } = useChatContext();
9000
+ const localSessions = useChatStore((s) => s.sessions);
8674
9001
  const activeSessionId = useChatStore((s) => s.activeSessionId);
8675
9002
  const startNewChat = useChatStore((s) => s.startNewChat);
8676
9003
  const setActiveSession = useChatStore((s) => s.setActiveSession);
8677
- const modeLabels = {
8678
- ask: labels.modeLabelAsk,
8679
- plan: labels.modeLabelPlan,
8680
- agent: labels.modeLabelAgent
9004
+ const hydrateHistorySessions = useChatStore((s) => s.hydrateHistorySessions);
9005
+ const hydrateHistorySessionMessages = useChatStore((s) => s.hydrateHistorySessionMessages);
9006
+ const hydrateHistorySessionMessagesPage = useChatStore((s) => s.hydrateHistorySessionMessagesPage);
9007
+ const setHistorySessionMessageLoadStatus = useChatStore(
9008
+ (s) => s.setHistorySessionMessageLoadStatus
9009
+ );
9010
+ const isLoadingMoreRef = useRef12(false);
9011
+ const hasSeenLoadingMoreRef = useRef12(false);
9012
+ useEffect10(() => {
9013
+ if (!historySessionList)
9014
+ return;
9015
+ hydrateHistorySessions(historySessionList.sessions);
9016
+ }, [historySessionList, hydrateHistorySessions]);
9017
+ useEffect10(() => {
9018
+ if (historySessionList?.isLoading) {
9019
+ hasSeenLoadingMoreRef.current = true;
9020
+ return;
9021
+ }
9022
+ if (hasSeenLoadingMoreRef.current) {
9023
+ hasSeenLoadingMoreRef.current = false;
9024
+ isLoadingMoreRef.current = false;
9025
+ }
9026
+ }, [historySessionList?.isLoading]);
9027
+ useEffect10(() => {
9028
+ isLoadingMoreRef.current = false;
9029
+ hasSeenLoadingMoreRef.current = false;
9030
+ }, [historySessionList?.sessions.length, historySessionList?.hasMore]);
9031
+ const sessions = useMemo7(() => {
9032
+ if (!historySessionList) {
9033
+ return localSessions;
9034
+ }
9035
+ const persistedLocalSessions = localSessions.filter(
9036
+ (session) => !isDraftChatSessionId(session.sessionId)
9037
+ );
9038
+ const localSessionIds = new Set(persistedLocalSessions.map((session) => session.sessionId));
9039
+ return [
9040
+ ...persistedLocalSessions,
9041
+ ...historySessionList.sessions.filter((session) => !localSessionIds.has(session.sessionId))
9042
+ ];
9043
+ }, [historySessionList, localSessions]);
9044
+ const handleSessionListScroll = (event) => {
9045
+ if (!historySessionList?.hasMore || historySessionList.isLoading || !onLoadMoreSessions || isLoadingMoreRef.current) {
9046
+ return;
9047
+ }
9048
+ const target = event.currentTarget;
9049
+ if (shouldLoadMoreSessions({
9050
+ scrollTop: target.scrollTop,
9051
+ clientHeight: target.clientHeight,
9052
+ scrollHeight: target.scrollHeight
9053
+ })) {
9054
+ isLoadingMoreRef.current = true;
9055
+ void Promise.resolve(onLoadMoreSessions()).catch(() => {
9056
+ isLoadingMoreRef.current = false;
9057
+ hasSeenLoadingMoreRef.current = false;
9058
+ });
9059
+ }
8681
9060
  };
8682
- return /* @__PURE__ */ jsxs13(Container3, { children: [
8683
- /* @__PURE__ */ jsxs13(Toolbar, { children: [
8684
- /* @__PURE__ */ jsx18(Title3, { children: "Sessions" }),
9061
+ const handleSelectSession = async (sessionId) => {
9062
+ setActiveSession(sessionId);
9063
+ const session = sessions.find((item) => item.sessionId === sessionId);
9064
+ if (!session || !onSelectHistorySession) {
9065
+ return;
9066
+ }
9067
+ const state = store.getState();
9068
+ const shouldLoad = shouldLoadHistorySessionMessages({
9069
+ sessionId,
9070
+ messagesBySession: state.messagesBySession,
9071
+ loadStatusBySession: state.sessionMessageLoadStatusBySession,
9072
+ isStreamingBySession: state.isStreamingBySession
9073
+ });
9074
+ if (!shouldLoad) {
9075
+ return;
9076
+ }
9077
+ setHistorySessionMessageLoadStatus(sessionId, "loading");
9078
+ try {
9079
+ const result = await onSelectHistorySession(session);
9080
+ if (Array.isArray(result)) {
9081
+ hydrateHistorySessionMessages(sessionId, result);
9082
+ return;
9083
+ }
9084
+ if (isHistorySessionMessagesPage(result)) {
9085
+ hydrateHistorySessionMessagesPage(sessionId, result);
9086
+ return;
9087
+ }
9088
+ setHistorySessionMessageLoadStatus(sessionId, "loaded");
9089
+ } catch (error2) {
9090
+ setHistorySessionMessageLoadStatus(
9091
+ sessionId,
9092
+ "error",
9093
+ error2 instanceof Error ? error2.message : String(error2)
9094
+ );
9095
+ }
9096
+ };
9097
+ return /* @__PURE__ */ jsxs12(Container3, { children: [
9098
+ /* @__PURE__ */ jsxs12(Toolbar, { children: [
9099
+ /* @__PURE__ */ jsx18(Title3, { children: labels.sessionsTitle }),
8685
9100
  /* @__PURE__ */ jsx18(CreateButton, { type: "button", "data-testid": "chat-create-session", onClick: startNewChat, children: labels.newChat })
8686
9101
  ] }),
8687
- /* @__PURE__ */ jsx18(List2, { "data-testid": "chat-session-list", children: sessions.map((session) => /* @__PURE__ */ jsx18(
8688
- ChatSessionItem,
8689
- {
8690
- session,
8691
- isActive: activeSessionId === session.sessionId,
8692
- modeLabel: modeLabels[session.mode ?? DEFAULT_CHAT_AGENT_MODE] ?? "",
8693
- onClick: setActiveSession
8694
- },
8695
- session.sessionId
8696
- )) })
9102
+ /* @__PURE__ */ jsxs12(List2, { "data-testid": "chat-session-list", onScroll: handleSessionListScroll, children: [
9103
+ sessions.map((session) => /* @__PURE__ */ jsx18(
9104
+ ChatSessionItem,
9105
+ {
9106
+ session,
9107
+ isActive: activeSessionId === session.sessionId,
9108
+ onClick: (sessionId) => void handleSelectSession(sessionId)
9109
+ },
9110
+ session.sessionId
9111
+ )),
9112
+ historySessionList?.isLoading ? /* @__PURE__ */ jsx18(StateRow, { "data-testid": "chat-session-history-loading", children: labels.sessionHistoryLoading }) : null,
9113
+ historySessionList?.error ? /* @__PURE__ */ jsx18(StateRow, { "data-testid": "chat-session-history-error", children: historySessionList.error || labels.sessionHistoryLoadFailed }) : null,
9114
+ historySessionList && !historySessionList.isLoading && !historySessionList.error && sessions.length === 0 ? /* @__PURE__ */ jsx18(StateRow, { "data-testid": "chat-session-history-empty", children: labels.sessionHistoryEmpty }) : null
9115
+ ] })
8697
9116
  ] });
8698
9117
  };
8699
9118
  var Container3 = styled16.aside`
@@ -8716,13 +9135,32 @@ var Title3 = styled16.h2`
8716
9135
  color: var(--text-secondary);
8717
9136
  `;
8718
9137
  var CreateButton = styled16.button`
8719
- border: none;
9138
+ border: 1px solid rgba(255, 255, 255, 0.18);
8720
9139
  border-radius: 12px;
8721
9140
  padding: 12px 14px;
8722
- background: var(--text-primary);
8723
- color: var(--bg-primary);
9141
+ background: rgba(255, 255, 255, 0.9);
9142
+ color: rgba(20, 20, 20, 0.92);
8724
9143
  text-align: left;
8725
9144
  cursor: pointer;
9145
+ transition:
9146
+ background-color 160ms ease,
9147
+ border-color 160ms ease,
9148
+ transform 160ms ease;
9149
+
9150
+ &:hover {
9151
+ background: rgba(255, 255, 255, 0.96);
9152
+ border-color: rgba(255, 255, 255, 0.28);
9153
+ }
9154
+
9155
+ &:focus-visible {
9156
+ outline: none;
9157
+ background: rgba(255, 255, 255, 0.96);
9158
+ border-color: rgba(255, 255, 255, 0.32);
9159
+ }
9160
+
9161
+ &:active {
9162
+ transform: scale(0.995);
9163
+ }
8726
9164
  `;
8727
9165
  var List2 = styled16.div`
8728
9166
  padding: 0 12px 16px;
@@ -8731,9 +9169,14 @@ var List2 = styled16.div`
8731
9169
  gap: 8px;
8732
9170
  overflow: auto;
8733
9171
  `;
9172
+ var StateRow = styled16.div`
9173
+ padding: 12px;
9174
+ font-size: 13px;
9175
+ color: var(--text-secondary);
9176
+ `;
8734
9177
 
8735
9178
  // src/components/ai-chat/index.tsx
8736
- import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs14 } from "@emotion/react/jsx-runtime";
9179
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs13 } from "@emotion/react/jsx-runtime";
8737
9180
  var NewTalkIcon = () => /* @__PURE__ */ jsx19(
8738
9181
  "svg",
8739
9182
  {
@@ -8744,7 +9187,7 @@ var NewTalkIcon = () => /* @__PURE__ */ jsx19(
8744
9187
  fill: "none",
8745
9188
  style: { display: "block" },
8746
9189
  xmlns: "http://www.w3.org/2000/svg",
8747
- children: /* @__PURE__ */ jsxs14(
9190
+ children: /* @__PURE__ */ jsxs13(
8748
9191
  "g",
8749
9192
  {
8750
9193
  transform: "translate(1.8909 2.0364)",
@@ -8753,7 +9196,7 @@ var NewTalkIcon = () => /* @__PURE__ */ jsx19(
8753
9196
  strokeLinejoin: "round",
8754
9197
  strokeWidth: "1.36533333",
8755
9198
  children: [
8756
- /* @__PURE__ */ jsxs14("g", { transform: "translate(9.8909 2.3273) rotate(-315) translate(-9.8909 -2.3273) translate(8.2909 0.7273)", children: [
9199
+ /* @__PURE__ */ jsxs13("g", { transform: "translate(9.8909 2.3273) rotate(-315) translate(-9.8909 -2.3273) translate(8.2909 0.7273)", children: [
8757
9200
  /* @__PURE__ */ jsx19("path", { d: "M0 0C0 0 1.06666667 1.06666667 3.2 3.2" }),
8758
9201
  /* @__PURE__ */ jsx19("path", { d: "M3.2 0C3.2 0 2.13333333 1.06666667 0 3.2" })
8759
9202
  ] }),
@@ -8792,12 +9235,12 @@ var AiChatWorkspaceContent = ({
8792
9235
  })
8793
9236
  );
8794
9237
  const shouldShowComposerOnly = showComposerOnlyBeforeFirstMessage && !showConversationList && !isConversationStarted;
8795
- useEffect10(() => {
9238
+ useEffect11(() => {
8796
9239
  onConversationStartedChange?.(isConversationStarted);
8797
9240
  }, [isConversationStarted, onConversationStartedChange]);
8798
- return /* @__PURE__ */ jsxs14(Root, { "data-testid": "ai-chat", children: [
9241
+ return /* @__PURE__ */ jsxs13(Root, { "data-testid": "ai-chat", children: [
8799
9242
  showConversationList ? /* @__PURE__ */ jsx19(ChatConversationList, {}) : null,
8800
- /* @__PURE__ */ jsxs14(Workspace, { children: [
9243
+ /* @__PURE__ */ jsxs13(Workspace, { children: [
8801
9244
  showNewChatButton && !showConversationList && !shouldShowComposerOnly ? /* @__PURE__ */ jsx19(QuickActions, { renderNewChatTrigger }) : null,
8802
9245
  shouldShowComposerOnly ? null : /* @__PURE__ */ jsx19(ChatThread, {}),
8803
9246
  /* @__PURE__ */ jsx19(ChatComposer, {})
@@ -8931,12 +9374,25 @@ var Root = styled17.div`
8931
9374
  overflow: hidden;
8932
9375
  `;
8933
9376
  var Workspace = styled17.section`
9377
+ --chat-layout-rem: 16px;
9378
+ --chat-content-margin: calc(var(--chat-layout-rem) * 1);
9379
+ --chat-content-max-width: calc(var(--chat-layout-rem) * 40);
9380
+
8934
9381
  flex: 1;
8935
9382
  display: flex;
8936
9383
  flex-direction: column;
8937
9384
  gap: 12px;
8938
9385
  min-height: 0;
8939
9386
  overflow: hidden;
9387
+
9388
+ @media (min-width: 640px) {
9389
+ --chat-content-margin: calc(var(--chat-layout-rem) * 1.5);
9390
+ }
9391
+
9392
+ @media (min-width: 1024px) {
9393
+ --chat-content-margin: calc(var(--chat-layout-rem) * 4);
9394
+ --chat-content-max-width: calc(var(--chat-layout-rem) * 48);
9395
+ }
8940
9396
  `;
8941
9397
  var QuickActionsRow = styled17.div`
8942
9398
  display: flex;