@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.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
 
@@ -70,6 +70,7 @@ var DEFAULT_AI_CHAT_LABELS = {
70
70
  stopButton: "Stop",
71
71
  retryButton: "Retry",
72
72
  scrollToLatest: "Jump to latest",
73
+ sessionsTitle: "Sessions",
73
74
  placeholder: "Ask something...",
74
75
  modeLabelAsk: "Ask",
75
76
  modeLabelPlan: "Plan",
@@ -100,7 +101,10 @@ var DEFAULT_AI_CHAT_LABELS = {
100
101
  modelUnavailable: "No model available",
101
102
  skillLoading: "Loading skills...",
102
103
  skillEmpty: "No matching skills",
103
- removeSkillAriaLabel: "Remove skill"
104
+ removeSkillAriaLabel: "Remove skill",
105
+ sessionHistoryLoading: "Loading conversations...",
106
+ sessionHistoryLoadFailed: "Failed to load conversations",
107
+ sessionHistoryEmpty: "No conversations yet"
104
108
  };
105
109
 
106
110
  // src/lib/chat-session.ts
@@ -110,7 +114,6 @@ var createDraftChatSessionId = () => `${DRAFT_CHAT_SESSION_ID_PREFIX}${Date.now(
110
114
  var isDraftChatSessionId = (sessionId) => Boolean(sessionId?.startsWith(DRAFT_CHAT_SESSION_ID_PREFIX));
111
115
  var createDraftChatSession = ({
112
116
  model,
113
- mode = DEFAULT_CHAT_AGENT_MODE,
114
117
  nowIso: nowIso2,
115
118
  createSessionId = createDraftChatSessionId
116
119
  }) => {
@@ -120,14 +123,27 @@ var createDraftChatSession = ({
120
123
  title: "New Chat",
121
124
  createdAt: iso,
122
125
  updatedAt: iso,
123
- model,
124
- mode
126
+ model
125
127
  };
126
128
  };
127
129
 
128
130
  // src/store/chat-store.ts
129
131
  var DEFAULT_CHAT_SESSION_TITLE = "New Chat";
130
132
  var IMAGE_MESSAGE_SESSION_TITLE = "Image message";
133
+ var createHistoryMessagePaginationState = (page) => ({
134
+ previousCursor: page?.previousCursor ?? null,
135
+ hasMorePrevious: page?.hasMorePrevious ?? Boolean(page && page.previousCursor !== null),
136
+ isLoadingPrevious: false,
137
+ error: null
138
+ });
139
+ var normalizeHistoryMessages = (sessionId, messages) => messages.map((message) => ({ ...message, sessionId }));
140
+ var mergeOlderHistoryMessages = (sessionId, olderMessages, currentMessages) => {
141
+ const currentMessageIds = new Set(currentMessages.map((message) => message.id));
142
+ const uniqueOlderMessages = normalizeHistoryMessages(sessionId, olderMessages).filter(
143
+ (message) => !currentMessageIds.has(message.id)
144
+ );
145
+ return [...uniqueOlderMessages, ...currentMessages];
146
+ };
131
147
  var resolveSessionTitleFromMessage = (message) => {
132
148
  const trimmedContent = message.content.trim();
133
149
  if (trimmedContent) {
@@ -230,19 +246,27 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
230
246
  isStreamingBySession: {},
231
247
  isStoppingBySession: {},
232
248
  errorBySession: {},
249
+ sessionMessageLoadStatusBySession: {},
250
+ sessionMessageLoadErrorBySession: {},
251
+ historyMessagePaginationBySession: {},
233
252
  // ---- Session management ------------------------------------------------
234
253
  createSession: (session) => {
235
254
  const state = get();
236
255
  const exists = state.sessions.some((s) => s.sessionId === session.sessionId);
237
- const nextSession = {
238
- ...session,
239
- mode: session.mode ?? DEFAULT_CHAT_AGENT_MODE
240
- };
241
- const nextSessions = exists ? state.sessions : [nextSession, ...state.sessions];
256
+ const nextSessions = exists ? state.sessions : [session, ...state.sessions];
242
257
  const nextMessagesBySession = { ...state.messagesBySession };
243
258
  const nextErrorBySession = { ...state.errorBySession };
244
259
  const nextIsStreamingBySession = { ...state.isStreamingBySession };
245
260
  const nextIsStoppingBySession = { ...state.isStoppingBySession };
261
+ const nextSessionMessageLoadStatusBySession = {
262
+ ...state.sessionMessageLoadStatusBySession
263
+ };
264
+ const nextSessionMessageLoadErrorBySession = {
265
+ ...state.sessionMessageLoadErrorBySession
266
+ };
267
+ const nextHistoryMessagePaginationBySession = {
268
+ ...state.historyMessagePaginationBySession
269
+ };
246
270
  const sid = session.sessionId;
247
271
  if (nextMessagesBySession[sid] === void 0)
248
272
  nextMessagesBySession[sid] = [];
@@ -252,20 +276,30 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
252
276
  nextIsStreamingBySession[sid] = false;
253
277
  if (nextIsStoppingBySession[sid] === void 0)
254
278
  nextIsStoppingBySession[sid] = false;
279
+ if (nextSessionMessageLoadStatusBySession[sid] === void 0) {
280
+ nextSessionMessageLoadStatusBySession[sid] = "loaded";
281
+ }
282
+ if (nextSessionMessageLoadErrorBySession[sid] === void 0) {
283
+ nextSessionMessageLoadErrorBySession[sid] = null;
284
+ }
285
+ if (nextHistoryMessagePaginationBySession[sid] === void 0) {
286
+ nextHistoryMessagePaginationBySession[sid] = createHistoryMessagePaginationState();
287
+ }
255
288
  set({
256
289
  sessions: nextSessions,
257
290
  activeSessionId: sid,
258
291
  messagesBySession: nextMessagesBySession,
259
292
  errorBySession: nextErrorBySession,
260
293
  isStreamingBySession: nextIsStreamingBySession,
261
- isStoppingBySession: nextIsStoppingBySession
294
+ isStoppingBySession: nextIsStoppingBySession,
295
+ sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
296
+ sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
297
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
262
298
  });
263
299
  },
264
300
  startNewChat: () => {
265
- const state = get();
266
301
  const session = createDraftChatSession({
267
302
  model: "",
268
- mode: state.preferredMode,
269
303
  nowIso: () => (/* @__PURE__ */ new Date()).toISOString()
270
304
  });
271
305
  get().createSession(session);
@@ -313,6 +347,27 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
313
347
  nextErrorBySession[nextSessionId] = nextErrorBySession[previousSessionId] ?? null;
314
348
  delete nextErrorBySession[previousSessionId];
315
349
  }
350
+ const nextSessionMessageLoadStatusBySession = {
351
+ ...state.sessionMessageLoadStatusBySession
352
+ };
353
+ if (previousSessionId in nextSessionMessageLoadStatusBySession) {
354
+ nextSessionMessageLoadStatusBySession[nextSessionId] = nextSessionMessageLoadStatusBySession[previousSessionId] ?? "idle";
355
+ delete nextSessionMessageLoadStatusBySession[previousSessionId];
356
+ }
357
+ const nextSessionMessageLoadErrorBySession = {
358
+ ...state.sessionMessageLoadErrorBySession
359
+ };
360
+ if (previousSessionId in nextSessionMessageLoadErrorBySession) {
361
+ nextSessionMessageLoadErrorBySession[nextSessionId] = nextSessionMessageLoadErrorBySession[previousSessionId] ?? null;
362
+ delete nextSessionMessageLoadErrorBySession[previousSessionId];
363
+ }
364
+ const nextHistoryMessagePaginationBySession = {
365
+ ...state.historyMessagePaginationBySession
366
+ };
367
+ if (previousSessionId in nextHistoryMessagePaginationBySession) {
368
+ nextHistoryMessagePaginationBySession[nextSessionId] = nextHistoryMessagePaginationBySession[previousSessionId] ?? createHistoryMessagePaginationState();
369
+ delete nextHistoryMessagePaginationBySession[previousSessionId];
370
+ }
316
371
  const nextActiveSessionId = state.activeSessionId === previousSessionId ? nextSessionId : state.activeSessionId;
317
372
  set({
318
373
  sessions: nextSessions,
@@ -321,18 +376,66 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
321
376
  isStreamingBySession: nextIsStreamingBySession,
322
377
  isStoppingBySession: nextIsStoppingBySession,
323
378
  errorBySession: nextErrorBySession,
379
+ sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
380
+ sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
381
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession,
324
382
  activeSessionId: nextActiveSessionId
325
383
  });
326
384
  },
327
385
  setPreferredMode: (mode) => {
328
386
  set({ preferredMode: mode });
329
387
  },
330
- setSessionMode: (sessionId, mode) => {
388
+ hydrateHistorySessions: (sessions) => {
331
389
  const state = get();
332
- const nextSessions = state.sessions.map(
333
- (s) => s.sessionId === sessionId ? { ...s, mode } : s
390
+ const localSessions = state.sessions.filter(
391
+ (session) => isDraftChatSessionId(session.sessionId)
334
392
  );
335
- set({ sessions: nextSessions });
393
+ const localSessionIds = new Set(localSessions.map((session) => session.sessionId));
394
+ const nextSessions = [
395
+ ...localSessions,
396
+ ...sessions.filter((session) => !localSessionIds.has(session.sessionId))
397
+ ];
398
+ const nextMessagesBySession = { ...state.messagesBySession };
399
+ const nextErrorBySession = { ...state.errorBySession };
400
+ const nextIsStreamingBySession = { ...state.isStreamingBySession };
401
+ const nextIsStoppingBySession = { ...state.isStoppingBySession };
402
+ const nextSessionMessageLoadStatusBySession = {
403
+ ...state.sessionMessageLoadStatusBySession
404
+ };
405
+ const nextSessionMessageLoadErrorBySession = {
406
+ ...state.sessionMessageLoadErrorBySession
407
+ };
408
+ const nextHistoryMessagePaginationBySession = {
409
+ ...state.historyMessagePaginationBySession
410
+ };
411
+ nextSessions.forEach((session) => {
412
+ const sid = session.sessionId;
413
+ if (nextErrorBySession[sid] === void 0)
414
+ nextErrorBySession[sid] = null;
415
+ if (nextIsStreamingBySession[sid] === void 0)
416
+ nextIsStreamingBySession[sid] = false;
417
+ if (nextIsStoppingBySession[sid] === void 0)
418
+ nextIsStoppingBySession[sid] = false;
419
+ if (nextSessionMessageLoadStatusBySession[sid] === void 0) {
420
+ nextSessionMessageLoadStatusBySession[sid] = "idle";
421
+ }
422
+ if (nextSessionMessageLoadErrorBySession[sid] === void 0) {
423
+ nextSessionMessageLoadErrorBySession[sid] = null;
424
+ }
425
+ if (nextHistoryMessagePaginationBySession[sid] === void 0) {
426
+ nextHistoryMessagePaginationBySession[sid] = createHistoryMessagePaginationState();
427
+ }
428
+ });
429
+ set({
430
+ sessions: nextSessions,
431
+ messagesBySession: nextMessagesBySession,
432
+ errorBySession: nextErrorBySession,
433
+ isStreamingBySession: nextIsStreamingBySession,
434
+ isStoppingBySession: nextIsStoppingBySession,
435
+ sessionMessageLoadStatusBySession: nextSessionMessageLoadStatusBySession,
436
+ sessionMessageLoadErrorBySession: nextSessionMessageLoadErrorBySession,
437
+ historyMessagePaginationBySession: nextHistoryMessagePaginationBySession
438
+ });
336
439
  },
337
440
  // ---- Message operations ------------------------------------------------
338
441
  appendMessage: (sessionId, message) => {
@@ -360,6 +463,92 @@ var createChatStore = (initialState) => (0, import_vanilla.createStore)((set, ge
360
463
  isStreamingBySession: nextIsStreamingBySession
361
464
  });
362
465
  },
466
+ hydrateHistorySessionMessages: (sessionId, messages) => {
467
+ const state = get();
468
+ set({
469
+ messagesBySession: {
470
+ ...state.messagesBySession,
471
+ [sessionId]: normalizeHistoryMessages(sessionId, messages)
472
+ },
473
+ sessionMessageLoadStatusBySession: {
474
+ ...state.sessionMessageLoadStatusBySession,
475
+ [sessionId]: "loaded"
476
+ },
477
+ sessionMessageLoadErrorBySession: {
478
+ ...state.sessionMessageLoadErrorBySession,
479
+ [sessionId]: null
480
+ },
481
+ historyMessagePaginationBySession: {
482
+ ...state.historyMessagePaginationBySession,
483
+ [sessionId]: createHistoryMessagePaginationState()
484
+ }
485
+ });
486
+ },
487
+ hydrateHistorySessionMessagesPage: (sessionId, page) => {
488
+ const state = get();
489
+ set({
490
+ messagesBySession: {
491
+ ...state.messagesBySession,
492
+ [sessionId]: normalizeHistoryMessages(sessionId, page.messages)
493
+ },
494
+ sessionMessageLoadStatusBySession: {
495
+ ...state.sessionMessageLoadStatusBySession,
496
+ [sessionId]: "loaded"
497
+ },
498
+ sessionMessageLoadErrorBySession: {
499
+ ...state.sessionMessageLoadErrorBySession,
500
+ [sessionId]: null
501
+ },
502
+ historyMessagePaginationBySession: {
503
+ ...state.historyMessagePaginationBySession,
504
+ [sessionId]: createHistoryMessagePaginationState(page)
505
+ }
506
+ });
507
+ },
508
+ prependHistorySessionMessagesPage: (sessionId, page) => {
509
+ const state = get();
510
+ set({
511
+ messagesBySession: {
512
+ ...state.messagesBySession,
513
+ [sessionId]: mergeOlderHistoryMessages(
514
+ sessionId,
515
+ page.messages,
516
+ state.messagesBySession[sessionId] ?? []
517
+ )
518
+ },
519
+ historyMessagePaginationBySession: {
520
+ ...state.historyMessagePaginationBySession,
521
+ [sessionId]: createHistoryMessagePaginationState(page)
522
+ }
523
+ });
524
+ },
525
+ setHistorySessionPreviousMessagesLoadStatus: (sessionId, isLoading, error2 = null) => {
526
+ const state = get();
527
+ const current = state.historyMessagePaginationBySession[sessionId] ?? createHistoryMessagePaginationState();
528
+ set({
529
+ historyMessagePaginationBySession: {
530
+ ...state.historyMessagePaginationBySession,
531
+ [sessionId]: {
532
+ ...current,
533
+ isLoadingPrevious: isLoading,
534
+ error: error2
535
+ }
536
+ }
537
+ });
538
+ },
539
+ setHistorySessionMessageLoadStatus: (sessionId, status, error2 = null) => {
540
+ const state = get();
541
+ set({
542
+ sessionMessageLoadStatusBySession: {
543
+ ...state.sessionMessageLoadStatusBySession,
544
+ [sessionId]: status
545
+ },
546
+ sessionMessageLoadErrorBySession: {
547
+ ...state.sessionMessageLoadErrorBySession,
548
+ [sessionId]: error2
549
+ }
550
+ });
551
+ },
363
552
  startStreamingMessage: (sessionId, message) => {
364
553
  const state = get();
365
554
  set({
@@ -807,6 +996,10 @@ var AiChatProvider = (props) => {
807
996
  handleQuestionnaireSubmit,
808
997
  handleConfirmationSubmit,
809
998
  messageRenderOrder,
999
+ historySessionList,
1000
+ onLoadMoreSessions,
1001
+ onSelectHistorySession,
1002
+ onLoadMoreHistoryMessages,
810
1003
  enableImageAttachments = true,
811
1004
  children
812
1005
  } = props;
@@ -883,7 +1076,11 @@ var AiChatProvider = (props) => {
883
1076
  handleConfirmationSubmit,
884
1077
  messageRenderOrder,
885
1078
  transformStreamPacket: defaultTransformStreamPacket,
886
- enableImageAttachments
1079
+ enableImageAttachments,
1080
+ historySessionList,
1081
+ onLoadMoreSessions,
1082
+ onSelectHistorySession,
1083
+ onLoadMoreHistoryMessages
887
1084
  }),
888
1085
  [
889
1086
  axiosInstance,
@@ -893,8 +1090,12 @@ var AiChatProvider = (props) => {
893
1090
  enableImageAttachments,
894
1091
  handleConfirmationSubmit,
895
1092
  handleQuestionnaireSubmit,
1093
+ historySessionList,
896
1094
  labels,
897
1095
  messageRenderOrder,
1096
+ onLoadMoreSessions,
1097
+ onLoadMoreHistoryMessages,
1098
+ onSelectHistorySession,
898
1099
  renderMessageBlock,
899
1100
  sendRef,
900
1101
  retryRef,
@@ -2922,9 +3123,8 @@ var ChatMessageItemView = ({
2922
3123
  const hasMarkdownOnlyBlocks = hasStructuredBlocks && blocks.every((block) => block.type === "markdown");
2923
3124
  const hasTextContent = Boolean(settledContent || freshContent || displayedContent);
2924
3125
  const shouldRenderStructuredBlocks = hasStructuredBlocks && !(isAssistantStreaming && hasMarkdownOnlyBlocks && hasTextContent);
2925
- const isPlanMode = mode === "plan";
2926
- const canSubmitConfirmation = isPlanMode && typeof onConfirmationSubmit === "function";
2927
- const canSubmitQuestionnaire = isPlanMode && typeof onQuestionnaireSubmit === "function";
3126
+ const canSubmitConfirmation = typeof onConfirmationSubmit === "function";
3127
+ const canSubmitQuestionnaire = typeof onQuestionnaireSubmit === "function";
2928
3128
  const shouldShowStreamingCaret = isAssistantStreaming && (!shouldRenderStructuredBlocks || hasTextContent);
2929
3129
  const isUserMessage = message.role === "user";
2930
3130
  const messageRenderMode = isUserMessage ? "plain-text" : "markdown";
@@ -2982,7 +3182,7 @@ var ChatMessageItemView = ({
2982
3182
  ExecutionConfirmationCard,
2983
3183
  {
2984
3184
  proposal: block.proposal,
2985
- interactive: isPlanMode,
3185
+ interactive: canSubmitConfirmation,
2986
3186
  onConfirm: canSubmitConfirmation ? () => onConfirmationSubmit({
2987
3187
  proposalId: block.proposal.proposalId,
2988
3188
  content: createExecutionConfirmationContent(block.proposal),
@@ -3206,15 +3406,11 @@ var Bubble = import_styled7.default.article`
3206
3406
 
3207
3407
  &[data-role='user'] {
3208
3408
  width: auto;
3209
- max-width: min(760px, 100%);
3409
+ max-width: 100%;
3210
3410
  margin-left: auto;
3211
- padding: 14px 16px;
3212
- border-radius: 22px;
3213
- background: linear-gradient(180deg, rgba(59, 59, 63, 0.9) 0%, rgba(42, 43, 46, 0.92) 100%);
3214
- border: 1px solid rgba(255, 255, 255, 0.07);
3215
- box-shadow:
3216
- inset 0 1px 0 rgba(255, 255, 255, 0.03),
3217
- 0 12px 30px rgba(0, 0, 0, 0.18);
3411
+ padding: 8px 12px;
3412
+ background: #282825;
3413
+ border-radius: 16px;
3218
3414
  }
3219
3415
  `;
3220
3416
  var Header2 = import_styled7.default.div`
@@ -3546,6 +3742,7 @@ var HeroSubtitle = import_styled8.default.p`
3546
3742
  // src/components/chat-thread/index.tsx
3547
3743
  var import_jsx_runtime10 = require("@emotion/react/jsx-runtime");
3548
3744
  var CHAT_THREAD_PINNED_THRESHOLD_PX = 32;
3745
+ var CHAT_THREAD_LOAD_PREVIOUS_THRESHOLD_PX = 80;
3549
3746
  var isThreadPinnedToBottom = (container) => container.scrollHeight - container.clientHeight - container.scrollTop <= CHAT_THREAD_PINNED_THRESHOLD_PX;
3550
3747
  var renderChatMessage = ({
3551
3748
  message,
@@ -3615,13 +3812,17 @@ var groupConversationTurns = (historyMessages, streamingMessage) => {
3615
3812
  ];
3616
3813
  };
3617
3814
  var ChatThreadView = ({
3618
- activeSessionMode = DEFAULT_CHAT_AGENT_MODE,
3815
+ activeMode = DEFAULT_CHAT_AGENT_MODE,
3619
3816
  historyMessages,
3620
3817
  streamingMessage,
3621
3818
  error: error2,
3819
+ isLoadingPreviousMessages = false,
3820
+ previousMessagesError,
3622
3821
  retryButtonLabel,
3623
3822
  scrollToLatestLabel,
3823
+ sessionHistoryLoadingLabel,
3624
3824
  onRetry,
3825
+ onLoadPreviousMessages,
3625
3826
  onConfirmationSubmit,
3626
3827
  onQuestionnaireSubmit,
3627
3828
  renderMessageBlock
@@ -3636,6 +3837,7 @@ var ChatThreadView = ({
3636
3837
  const latestHistoryMessage = historyMessages[historyMessages.length - 1];
3637
3838
  const latestTurnRef = (0, import_react11.useRef)(null);
3638
3839
  const reservedSpaceFrameRef = (0, import_react11.useRef)(null);
3840
+ const isLoadingPreviousRef = (0, import_react11.useRef)(false);
3639
3841
  const isPinnedRef = (0, import_react11.useRef)(true);
3640
3842
  const lastHistoryMessageIdRef = (0, import_react11.useRef)(latestHistoryMessage?.id);
3641
3843
  const lastStreamingMessageIdRef = (0, import_react11.useRef)(streamingMessage?.id);
@@ -3682,17 +3884,45 @@ var ChatThreadView = ({
3682
3884
  },
3683
3885
  [markThreadPinned, scrollToBottom]
3684
3886
  );
3887
+ const handleLoadPreviousMessages = (0, import_react11.useCallback)(async () => {
3888
+ const container = containerRef.current;
3889
+ if (!container || !onLoadPreviousMessages || isLoadingPreviousMessages) {
3890
+ return;
3891
+ }
3892
+ if (isLoadingPreviousRef.current) {
3893
+ return;
3894
+ }
3895
+ isLoadingPreviousRef.current = true;
3896
+ const previousScrollHeight = container.scrollHeight;
3897
+ const previousScrollTop = container.scrollTop;
3898
+ try {
3899
+ await onLoadPreviousMessages();
3900
+ } catch {
3901
+ return;
3902
+ } finally {
3903
+ isLoadingPreviousRef.current = false;
3904
+ }
3905
+ window.requestAnimationFrame(() => {
3906
+ const nextContainer = containerRef.current;
3907
+ if (!nextContainer)
3908
+ return;
3909
+ nextContainer.scrollTop = nextContainer.scrollHeight - previousScrollHeight + previousScrollTop;
3910
+ });
3911
+ }, [isLoadingPreviousMessages, onLoadPreviousMessages]);
3685
3912
  const handleContainerScroll = (0, import_react11.useCallback)(() => {
3686
3913
  const container = containerRef.current;
3687
3914
  if (!container)
3688
3915
  return;
3916
+ if (onLoadPreviousMessages && container.scrollTop <= CHAT_THREAD_LOAD_PREVIOUS_THRESHOLD_PX) {
3917
+ void handleLoadPreviousMessages();
3918
+ }
3689
3919
  const nextPinned = isThreadPinnedToBottom(container);
3690
3920
  isPinnedRef.current = nextPinned;
3691
3921
  setIsDetached(!nextPinned);
3692
3922
  if (nextPinned) {
3693
3923
  setPendingNewMessageCount(0);
3694
3924
  }
3695
- }, []);
3925
+ }, [handleLoadPreviousMessages, onLoadPreviousMessages]);
3696
3926
  (0, import_react11.useLayoutEffect)(() => {
3697
3927
  const nextHistoryMessageId = latestHistoryMessage?.id;
3698
3928
  if (lastHistoryMessageIdRef.current === nextHistoryMessageId) {
@@ -3820,6 +4050,8 @@ var ChatThreadView = ({
3820
4050
  }, [latestTurn, scrollToBottom]);
3821
4051
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ThreadViewport, { children: [
3822
4052
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Container, { ref: containerRef, "data-testid": "chat-thread", onScroll: handleContainerScroll, children: [
4053
+ isLoadingPreviousMessages ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PreviousMessagesStateRow, { "data-testid": "chat-thread-loading-previous", children: sessionHistoryLoadingLabel }) : null,
4054
+ previousMessagesError ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PreviousMessagesStateRow, { "data-testid": "chat-thread-load-previous-error", children: previousMessagesError }) : null,
3823
4055
  conversationTurns.map((turn, turnIndex) => {
3824
4056
  const isLatestTurn = turnIndex === conversationTurns.length - 1;
3825
4057
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
@@ -3836,7 +4068,7 @@ var ChatThreadView = ({
3836
4068
  style: isLatestTurn ? { scrollMarginTop: `${CHAT_THREAD_SCROLL_TOP_GAP}px` } : void 0,
3837
4069
  children: renderChatMessage({
3838
4070
  message: turn.userMessage,
3839
- mode: activeSessionMode,
4071
+ mode: activeMode,
3840
4072
  onConfirmationSubmit,
3841
4073
  onQuestionnaireSubmit,
3842
4074
  renderMessageBlock
@@ -3845,7 +4077,7 @@ var ChatThreadView = ({
3845
4077
  ) : null,
3846
4078
  turn.responseMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageSlot, { children: renderChatMessage({
3847
4079
  message,
3848
- mode: activeSessionMode,
4080
+ mode: activeMode,
3849
4081
  onConfirmationSubmit,
3850
4082
  onQuestionnaireSubmit,
3851
4083
  renderMessageBlock
@@ -3876,14 +4108,25 @@ var EMPTY_MESSAGES = [];
3876
4108
  var ChatThread = () => {
3877
4109
  const activeSessionId = useChatStore((s) => s.activeSessionId);
3878
4110
  const hasSessions = useChatStore((s) => s.sessions.length > 0);
3879
- const activeSessionMode = useChatStore(
3880
- (s) => s.sessions.find((x) => x.sessionId === s.activeSessionId)?.mode ?? DEFAULT_CHAT_AGENT_MODE
4111
+ const activeSession = useChatStore(
4112
+ (s) => s.sessions.find((session) => session.sessionId === s.activeSessionId)
3881
4113
  );
4114
+ const preferredMode = useChatStore((s) => s.preferredMode);
3882
4115
  const messages = useChatStore(
3883
4116
  (s) => s.messagesBySession[s.activeSessionId ?? ""] ?? EMPTY_MESSAGES
3884
4117
  );
4118
+ const sessionMessageLoadStatus = useChatStore(
4119
+ (s) => s.sessionMessageLoadStatusBySession[s.activeSessionId ?? ""]
4120
+ );
3885
4121
  const streamingMessage = useChatStore((s) => s.streamingMessageBySession[s.activeSessionId ?? ""]);
3886
4122
  const error2 = useChatStore((s) => s.errorBySession[s.activeSessionId ?? ""]);
4123
+ const historyMessagePagination = useChatStore(
4124
+ (s) => s.historyMessagePaginationBySession[s.activeSessionId ?? ""]
4125
+ );
4126
+ const prependHistorySessionMessagesPage = useChatStore((s) => s.prependHistorySessionMessagesPage);
4127
+ const setHistorySessionPreviousMessagesLoadStatus = useChatStore(
4128
+ (s) => s.setHistorySessionPreviousMessagesLoadStatus
4129
+ );
3887
4130
  const updateQA = useChatStore((s) => s.updateQuestionnaireAnswers);
3888
4131
  const clearSessionError = useChatStore((s) => s.clearSessionError);
3889
4132
  const {
@@ -3892,6 +4135,7 @@ var ChatThread = () => {
3892
4135
  renderMessageBlock,
3893
4136
  handleQuestionnaireSubmit: customQuestionnaireSubmit,
3894
4137
  handleConfirmationSubmit: customConfirmationSubmit,
4138
+ onLoadMoreHistoryMessages,
3895
4139
  labels
3896
4140
  } = useChatContext();
3897
4141
  const handleRetry = (0, import_react11.useCallback)(() => {
@@ -3906,7 +4150,7 @@ var ChatThread = () => {
3906
4150
  if (customQuestionnaireSubmit) {
3907
4151
  const handled = await customQuestionnaireSubmit(submission, {
3908
4152
  sessionId: sourceSessionId ?? void 0,
3909
- mode: activeSessionMode
4153
+ mode: preferredMode
3910
4154
  });
3911
4155
  if (handled !== false) {
3912
4156
  if (sourceSessionId && submission.sourceMessageId) {
@@ -3933,7 +4177,7 @@ var ChatThread = () => {
3933
4177
  );
3934
4178
  }
3935
4179
  },
3936
- [activeSessionId, activeSessionMode, updateQA, sendRef, customQuestionnaireSubmit]
4180
+ [activeSessionId, customQuestionnaireSubmit, preferredMode, sendRef, updateQA]
3937
4181
  );
3938
4182
  const handleConfirmation = (0, import_react11.useCallback)(
3939
4183
  async (submission) => {
@@ -3941,7 +4185,7 @@ var ChatThread = () => {
3941
4185
  if (customConfirmationSubmit) {
3942
4186
  const handled = await customConfirmationSubmit(submission, {
3943
4187
  sessionId: sourceSessionId ?? void 0,
3944
- mode: activeSessionMode
4188
+ mode: preferredMode
3945
4189
  });
3946
4190
  if (handled !== false) {
3947
4191
  return;
@@ -3952,21 +4196,61 @@ var ChatThread = () => {
3952
4196
  includeComposerAttachments: false
3953
4197
  });
3954
4198
  },
3955
- [activeSessionId, activeSessionMode, sendRef, customConfirmationSubmit]
4199
+ [activeSessionId, customConfirmationSubmit, preferredMode, sendRef]
4200
+ );
4201
+ const handleLoadPreviousMessages = (0, import_react11.useCallback)(async () => {
4202
+ if (!activeSession || !onLoadMoreHistoryMessages || !historyMessagePagination?.hasMorePrevious || !historyMessagePagination.previousCursor || historyMessagePagination.isLoadingPrevious) {
4203
+ return;
4204
+ }
4205
+ setHistorySessionPreviousMessagesLoadStatus(activeSession.sessionId, true);
4206
+ try {
4207
+ const page = await onLoadMoreHistoryMessages({
4208
+ session: activeSession,
4209
+ cursor: historyMessagePagination.previousCursor
4210
+ });
4211
+ if (page) {
4212
+ prependHistorySessionMessagesPage(activeSession.sessionId, page);
4213
+ return;
4214
+ }
4215
+ setHistorySessionPreviousMessagesLoadStatus(activeSession.sessionId, false);
4216
+ } catch (error3) {
4217
+ setHistorySessionPreviousMessagesLoadStatus(
4218
+ activeSession.sessionId,
4219
+ false,
4220
+ error3 instanceof Error ? error3.message : String(error3)
4221
+ );
4222
+ throw error3;
4223
+ }
4224
+ }, [
4225
+ activeSession,
4226
+ historyMessagePagination,
4227
+ onLoadMoreHistoryMessages,
4228
+ prependHistorySessionMessagesPage,
4229
+ setHistorySessionPreviousMessagesLoadStatus
4230
+ ]);
4231
+ const canLoadPreviousMessages = Boolean(
4232
+ activeSession && onLoadMoreHistoryMessages && historyMessagePagination?.hasMorePrevious && historyMessagePagination.previousCursor && !historyMessagePagination.isLoadingPrevious
3956
4233
  );
4234
+ if (hasSessions && sessionMessageLoadStatus === "loading" && messages.length === 0 && !streamingMessage) {
4235
+ 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 }) });
4236
+ }
3957
4237
  if (!hasSessions || messages.length === 0 && !streamingMessage) {
3958
4238
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatThreadEmptyState, {});
3959
4239
  }
3960
4240
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3961
4241
  ChatThreadView,
3962
4242
  {
3963
- activeSessionMode,
4243
+ activeMode: preferredMode,
3964
4244
  historyMessages: messages,
3965
4245
  streamingMessage,
3966
4246
  error: error2,
4247
+ isLoadingPreviousMessages: historyMessagePagination?.isLoadingPrevious,
4248
+ previousMessagesError: historyMessagePagination?.error,
3967
4249
  retryButtonLabel: labels.retryButton,
3968
4250
  scrollToLatestLabel: labels.scrollToLatest,
4251
+ sessionHistoryLoadingLabel: labels.sessionHistoryLoading,
3969
4252
  onRetry: handleRetry,
4253
+ onLoadPreviousMessages: canLoadPreviousMessages ? handleLoadPreviousMessages : void 0,
3970
4254
  onConfirmationSubmit: handleConfirmation,
3971
4255
  onQuestionnaireSubmit: handleQuestionnaireSubmit,
3972
4256
  renderMessageBlock
@@ -3980,6 +4264,18 @@ var ThreadViewport = import_styled9.default.div`
3980
4264
  flex: 1;
3981
4265
  min-height: 0;
3982
4266
  `;
4267
+ var ThreadStateViewport = import_styled9.default.div`
4268
+ display: flex;
4269
+ flex: 1;
4270
+ min-height: 0;
4271
+ align-items: center;
4272
+ justify-content: center;
4273
+ padding: 24px;
4274
+ `;
4275
+ var ThreadStateText = import_styled9.default.div`
4276
+ color: var(--text-secondary, rgba(255, 255, 255, 0.64));
4277
+ font-size: 14px;
4278
+ `;
3983
4279
  var Container = import_styled9.default.div`
3984
4280
  display: flex;
3985
4281
  flex: 1;
@@ -3987,7 +4283,7 @@ var Container = import_styled9.default.div`
3987
4283
  gap: 18px;
3988
4284
  min-height: 0;
3989
4285
  overflow: auto;
3990
- padding: 24px 24px 88px;
4286
+ padding: 24px 16px 88px;
3991
4287
  overscroll-behavior: contain;
3992
4288
 
3993
4289
  &::-webkit-scrollbar {
@@ -4006,10 +4302,23 @@ var Container = import_styled9.default.div`
4006
4302
  var MessageSlot = import_styled9.default.div`
4007
4303
  display: flex;
4008
4304
  `;
4305
+ var PreviousMessagesStateRow = import_styled9.default.div`
4306
+ width: 100%;
4307
+ max-width: var(--chat-content-max-width, 48rem);
4308
+ margin-right: auto;
4309
+ margin-left: auto;
4310
+ color: var(--text-secondary, rgba(255, 255, 255, 0.64));
4311
+ font-size: 13px;
4312
+ text-align: center;
4313
+ `;
4009
4314
  var ConversationTurn = import_styled9.default.div`
4010
4315
  display: flex;
4011
4316
  flex-direction: column;
4012
4317
  gap: 18px;
4318
+ width: 100%;
4319
+ max-width: var(--chat-content-max-width, 48rem);
4320
+ margin-right: auto;
4321
+ margin-left: auto;
4013
4322
  `;
4014
4323
  var ErrorText = import_styled9.default.div`
4015
4324
  color: #ff7b72;
@@ -6663,7 +6972,6 @@ var resolveSelectedChatModel = ({
6663
6972
  var resolveSendSession = ({
6664
6973
  activeSessionId,
6665
6974
  selectedModel,
6666
- selectedMode,
6667
6975
  nowIso: nowIso2,
6668
6976
  createSessionId
6669
6977
  }) => {
@@ -6675,7 +6983,6 @@ var resolveSendSession = ({
6675
6983
  }
6676
6984
  const session = createDraftChatSession({
6677
6985
  model: selectedModel,
6678
- mode: selectedMode ?? DEFAULT_CHAT_AGENT_MODE,
6679
6986
  nowIso: nowIso2,
6680
6987
  createSessionId
6681
6988
  });
@@ -6833,9 +7140,6 @@ var useChatComposer = () => {
6833
7140
  const skillsLoader = transport.getSkills;
6834
7141
  const activeSkillsLoaderRef = (0, import_react15.useRef)(skillsLoader);
6835
7142
  const activeSessionId = useChatStore((s) => s.activeSessionId);
6836
- const activeSession = useChatStore(
6837
- (s) => s.sessions.find((x) => x.sessionId === s.activeSessionId) ?? null
6838
- );
6839
7143
  const preferredMode = useChatStore((s) => s.preferredMode);
6840
7144
  const streamingSessionId = useChatStore(
6841
7145
  (s) => s.activeSessionId && s.isStreamingBySession[s.activeSessionId] ? s.activeSessionId : null
@@ -6856,7 +7160,6 @@ var useChatComposer = () => {
6856
7160
  const setSessionError = useChatStore((s) => s.setSessionError);
6857
7161
  const clearSessionError = useChatStore((s) => s.clearSessionError);
6858
7162
  const setPreferredMode = useChatStore((s) => s.setPreferredMode);
6859
- const setSessionMode = useChatStore((s) => s.setSessionMode);
6860
7163
  const [availableModels, setAvailableModels] = (0, import_react15.useState)([]);
6861
7164
  const [isModelsLoading, setIsModelsLoading] = (0, import_react15.useState)(true);
6862
7165
  const [isModelsError, setIsModelsError] = (0, import_react15.useState)(false);
@@ -6937,12 +7240,8 @@ var useChatComposer = () => {
6937
7240
  );
6938
7241
  }, [availableModels, isModelsLoading]);
6939
7242
  (0, import_react15.useEffect)(() => {
6940
- if (activeSession) {
6941
- setSelectedModeLocal(activeSession.mode ?? DEFAULT_CHAT_AGENT_MODE);
6942
- return;
6943
- }
6944
7243
  setSelectedModeLocal(preferredMode ?? DEFAULT_CHAT_AGENT_MODE);
6945
- }, [activeSession, preferredMode]);
7244
+ }, [preferredMode]);
6946
7245
  (0, import_react15.useEffect)(() => {
6947
7246
  if (previousActiveSessionIdRef.current !== activeSessionId) {
6948
7247
  setSelectedSkills([]);
@@ -7150,7 +7449,7 @@ var useChatComposer = () => {
7150
7449
  const storeState = store.getState();
7151
7450
  const currentActiveSessionId = options?.sessionId ?? storeState.activeSessionId;
7152
7451
  const currentActiveSession = storeState.sessions.find((session2) => session2.sessionId === currentActiveSessionId) ?? null;
7153
- const currentMode = currentActiveSession?.mode ?? selectedMode;
7452
+ const currentMode = selectedMode;
7154
7453
  if (!(selectedModel || currentActiveSession?.model || availableModels[0]?.id)) {
7155
7454
  return;
7156
7455
  }
@@ -7158,7 +7457,6 @@ var useChatComposer = () => {
7158
7457
  const { localSessionId, sessionId, session } = resolveSendSession({
7159
7458
  activeSessionId: currentActiveSessionId,
7160
7459
  selectedModel: resolvedModel,
7161
- selectedMode: currentMode,
7162
7460
  nowIso,
7163
7461
  createSessionId: createDraftChatSessionId
7164
7462
  });
@@ -7306,8 +7604,6 @@ var useChatComposer = () => {
7306
7604
  setSelectedMode: (mode) => {
7307
7605
  setSelectedModeLocal(mode);
7308
7606
  setPreferredMode(mode);
7309
- if (activeSessionId)
7310
- setSessionMode(activeSessionId, mode);
7311
7607
  if (activeSessionId) {
7312
7608
  const previousRequest = lastRequestBySessionRef.current.get(activeSessionId);
7313
7609
  if (previousRequest) {
@@ -7797,7 +8093,7 @@ var PrimaryButton = (0, import_styled13.default)(import_compass_ui3.Button)`
7797
8093
  min-width: 24px;
7798
8094
  width: 24px;
7799
8095
  height: 24px;
7800
- background: ${({ $canSend }) => $canSend ? "#fcfbf8" : "rgba(255, 255, 255, 0.3)"};
8096
+ background: ${({ $canSend }) => $canSend ? "#fcfbf8" : "rgba(252,251,248,0.3);"};
7801
8097
  color: ${({ $canSend }) => $canSend ? "#5b5448" : "rgba(255, 255, 255, 0.72)"};
7802
8098
  border-radius: 12px;
7803
8099
  border: 1px solid ${({ $canSend }) => $canSend ? "rgba(198, 188, 170, 0.38)" : "transparent"};
@@ -7808,7 +8104,7 @@ var PrimaryButton = (0, import_styled13.default)(import_compass_ui3.Button)`
7808
8104
  }
7809
8105
 
7810
8106
  &:hover:not(:disabled) {
7811
- background: ${({ $canSend }) => $canSend ? "#f7f4ec" : "rgba(255, 255, 255, 0.3)"};
8107
+ background: ${({ $canSend }) => $canSend ? "#f7f4ec" : "rgba(252,251,248,0.3);"};
7812
8108
  color: ${({ $canSend }) => $canSend ? "#4f493f" : "rgba(255, 255, 255, 0.72)"};
7813
8109
  border-color: ${({ $canSend }) => $canSend ? "rgba(198, 188, 170, 0.46)" : "transparent"};
7814
8110
  }
@@ -7876,7 +8172,7 @@ var StopSpinner = import_styled13.default.span`
7876
8172
  var import_jsx_runtime16 = require("@emotion/react/jsx-runtime");
7877
8173
  var CHAT_COMPOSER_LINE_HEIGHT_PX = 20;
7878
8174
  var CHAT_COMPOSER_MAX_ROWS = 7;
7879
- var CHAT_COMPOSER_PADDING_TOP_PX = 8;
8175
+ var CHAT_COMPOSER_PADDING_TOP_PX = 12;
7880
8176
  var CHAT_COMPOSER_PADDING_BOTTOM_PX = 12;
7881
8177
  var CHAT_COMPOSER_PADDING_BLOCK_PX = CHAT_COMPOSER_PADDING_TOP_PX + CHAT_COMPOSER_PADDING_BOTTOM_PX;
7882
8178
  var CHAT_COMPOSER_MIN_ROWS = 4;
@@ -8111,6 +8407,10 @@ var ChatComposerView = ({
8111
8407
  setActiveSkillNavigation({ queryKey: "", index: 0 });
8112
8408
  };
8113
8409
  const handleKeyDown = (event) => {
8410
+ const isImeComposing = event.nativeEvent.isComposing || event.keyCode === 229;
8411
+ if (event.key === "Enter" && isImeComposing) {
8412
+ return;
8413
+ }
8114
8414
  if (skillQueryMatch) {
8115
8415
  if (event.key === "ArrowDown" && filteredSkills.length > 0) {
8116
8416
  event.preventDefault();
@@ -8388,7 +8688,7 @@ var Surface = import_styled14.default.div`
8388
8688
  'input'
8389
8689
  'footer';
8390
8690
  width: 100%;
8391
- max-width: 760px;
8691
+ max-width: var(--chat-content-max-width, 48rem);
8392
8692
  margin: 0 auto;
8393
8693
  background: var(--border-color);
8394
8694
  border-radius: 20px;
@@ -8649,36 +8949,28 @@ var SkillButton = import_styled14.default.button`
8649
8949
  `;
8650
8950
 
8651
8951
  // src/components/chat-conversation-list/index.tsx
8952
+ var import_react20 = require("react");
8652
8953
  var import_styled16 = __toESM(require("@emotion/styled"));
8653
8954
 
8654
8955
  // src/components/chat-conversation-list/components/chat-session-item.tsx
8655
8956
  var import_react19 = require("react");
8656
8957
  var import_styled15 = __toESM(require("@emotion/styled"));
8657
8958
  var import_jsx_runtime17 = require("@emotion/react/jsx-runtime");
8658
- var ChatSessionItem = (0, import_react19.memo)(
8659
- ({ session, isActive, modeLabel, onClick }) => {
8660
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
8661
- SessionButton,
8662
- {
8663
- type: "button",
8664
- "data-active": isActive,
8665
- onClick: () => onClick(session.sessionId),
8666
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(SessionMeta, { children: [
8667
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SessionTitle, { children: session.title }),
8668
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ModeBadge, { children: modeLabel })
8669
- ] })
8670
- }
8671
- );
8672
- }
8673
- );
8959
+ var ChatSessionItem = (0, import_react19.memo)(({ session, isActive, onClick }) => {
8960
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
8961
+ SessionButton,
8962
+ {
8963
+ type: "button",
8964
+ "data-active": isActive,
8965
+ "data-testid": `chat-session-item-${session.sessionId}`,
8966
+ onClick: () => onClick(session.sessionId),
8967
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SessionTitle, { children: session.title })
8968
+ }
8969
+ );
8970
+ });
8674
8971
  ChatSessionItem.displayName = "ChatSessionItem";
8675
- var SessionMeta = import_styled15.default.div`
8676
- display: flex;
8677
- align-items: center;
8678
- justify-content: space-between;
8679
- gap: 8px;
8680
- `;
8681
8972
  var SessionTitle = import_styled15.default.span`
8973
+ display: block;
8682
8974
  min-width: 0;
8683
8975
  overflow: hidden;
8684
8976
  text-overflow: ellipsis;
@@ -8692,51 +8984,178 @@ var SessionButton = import_styled15.default.button`
8692
8984
  color: var(--text-primary);
8693
8985
  background: rgba(255, 255, 255, 0.03);
8694
8986
  cursor: pointer;
8987
+ transition:
8988
+ background-color 160ms ease,
8989
+ border-color 160ms ease,
8990
+ transform 160ms ease;
8991
+
8992
+ &:hover {
8993
+ background: rgba(255, 255, 255, 0.06);
8994
+ border-color: rgba(255, 255, 255, 0.08);
8995
+ }
8996
+
8997
+ &:focus-visible {
8998
+ outline: none;
8999
+ background: rgba(255, 255, 255, 0.06);
9000
+ border-color: rgba(255, 255, 255, 0.18);
9001
+ }
9002
+
9003
+ &:active {
9004
+ transform: scale(0.995);
9005
+ }
8695
9006
 
8696
9007
  &[data-active='true'] {
8697
9008
  border-color: rgba(255, 255, 255, 0.2);
8698
9009
  background: rgba(255, 255, 255, 0.08);
8699
9010
  }
8700
9011
  `;
8701
- var ModeBadge = import_styled15.default.span`
8702
- flex-shrink: 0;
8703
- border-radius: 999px;
8704
- border: 1px solid rgba(255, 255, 255, 0.1);
8705
- padding: 4px 10px;
8706
- font-size: 11px;
8707
- line-height: 1;
8708
- color: var(--text-secondary);
8709
- background: rgba(255, 255, 255, 0.04);
8710
- `;
9012
+
9013
+ // src/components/chat-conversation-list/lib/history-session-selection.ts
9014
+ var shouldLoadHistorySessionMessages = ({
9015
+ sessionId,
9016
+ messagesBySession,
9017
+ loadStatusBySession,
9018
+ isStreamingBySession
9019
+ }) => {
9020
+ if (isStreamingBySession[sessionId])
9021
+ return false;
9022
+ if (loadStatusBySession[sessionId] === "loading")
9023
+ return false;
9024
+ if (loadStatusBySession[sessionId] === "error")
9025
+ return true;
9026
+ if (loadStatusBySession[sessionId] === "loaded")
9027
+ return false;
9028
+ return messagesBySession[sessionId] === void 0;
9029
+ };
8711
9030
 
8712
9031
  // src/components/chat-conversation-list/index.tsx
8713
9032
  var import_jsx_runtime18 = require("@emotion/react/jsx-runtime");
9033
+ var SCROLL_LOAD_MORE_THRESHOLD_PX = 80;
9034
+ var shouldLoadMoreSessions = ({
9035
+ scrollTop,
9036
+ clientHeight,
9037
+ scrollHeight,
9038
+ threshold = SCROLL_LOAD_MORE_THRESHOLD_PX
9039
+ }) => scrollHeight - scrollTop - clientHeight <= threshold;
9040
+ var isHistorySessionMessagesPage = (value) => typeof value === "object" && value !== null && Array.isArray(value.messages);
8714
9041
  var ChatConversationList = () => {
8715
- const { labels } = useChatContext();
8716
- const sessions = useChatStore((s) => s.sessions);
9042
+ const { labels, historySessionList, onLoadMoreSessions, onSelectHistorySession, store } = useChatContext();
9043
+ const localSessions = useChatStore((s) => s.sessions);
8717
9044
  const activeSessionId = useChatStore((s) => s.activeSessionId);
8718
9045
  const startNewChat = useChatStore((s) => s.startNewChat);
8719
9046
  const setActiveSession = useChatStore((s) => s.setActiveSession);
8720
- const modeLabels = {
8721
- ask: labels.modeLabelAsk,
8722
- plan: labels.modeLabelPlan,
8723
- agent: labels.modeLabelAgent
9047
+ const hydrateHistorySessions = useChatStore((s) => s.hydrateHistorySessions);
9048
+ const hydrateHistorySessionMessages = useChatStore((s) => s.hydrateHistorySessionMessages);
9049
+ const hydrateHistorySessionMessagesPage = useChatStore((s) => s.hydrateHistorySessionMessagesPage);
9050
+ const setHistorySessionMessageLoadStatus = useChatStore(
9051
+ (s) => s.setHistorySessionMessageLoadStatus
9052
+ );
9053
+ const isLoadingMoreRef = (0, import_react20.useRef)(false);
9054
+ const hasSeenLoadingMoreRef = (0, import_react20.useRef)(false);
9055
+ (0, import_react20.useEffect)(() => {
9056
+ if (!historySessionList)
9057
+ return;
9058
+ hydrateHistorySessions(historySessionList.sessions);
9059
+ }, [historySessionList, hydrateHistorySessions]);
9060
+ (0, import_react20.useEffect)(() => {
9061
+ if (historySessionList?.isLoading) {
9062
+ hasSeenLoadingMoreRef.current = true;
9063
+ return;
9064
+ }
9065
+ if (hasSeenLoadingMoreRef.current) {
9066
+ hasSeenLoadingMoreRef.current = false;
9067
+ isLoadingMoreRef.current = false;
9068
+ }
9069
+ }, [historySessionList?.isLoading]);
9070
+ (0, import_react20.useEffect)(() => {
9071
+ isLoadingMoreRef.current = false;
9072
+ hasSeenLoadingMoreRef.current = false;
9073
+ }, [historySessionList?.sessions.length, historySessionList?.hasMore]);
9074
+ const sessions = (0, import_react20.useMemo)(() => {
9075
+ if (!historySessionList) {
9076
+ return localSessions;
9077
+ }
9078
+ const persistedLocalSessions = localSessions.filter(
9079
+ (session) => !isDraftChatSessionId(session.sessionId)
9080
+ );
9081
+ const localSessionIds = new Set(persistedLocalSessions.map((session) => session.sessionId));
9082
+ return [
9083
+ ...persistedLocalSessions,
9084
+ ...historySessionList.sessions.filter((session) => !localSessionIds.has(session.sessionId))
9085
+ ];
9086
+ }, [historySessionList, localSessions]);
9087
+ const handleSessionListScroll = (event) => {
9088
+ if (!historySessionList?.hasMore || historySessionList.isLoading || !onLoadMoreSessions || isLoadingMoreRef.current) {
9089
+ return;
9090
+ }
9091
+ const target = event.currentTarget;
9092
+ if (shouldLoadMoreSessions({
9093
+ scrollTop: target.scrollTop,
9094
+ clientHeight: target.clientHeight,
9095
+ scrollHeight: target.scrollHeight
9096
+ })) {
9097
+ isLoadingMoreRef.current = true;
9098
+ void Promise.resolve(onLoadMoreSessions()).catch(() => {
9099
+ isLoadingMoreRef.current = false;
9100
+ hasSeenLoadingMoreRef.current = false;
9101
+ });
9102
+ }
9103
+ };
9104
+ const handleSelectSession = async (sessionId) => {
9105
+ setActiveSession(sessionId);
9106
+ const session = sessions.find((item) => item.sessionId === sessionId);
9107
+ if (!session || !onSelectHistorySession) {
9108
+ return;
9109
+ }
9110
+ const state = store.getState();
9111
+ const shouldLoad = shouldLoadHistorySessionMessages({
9112
+ sessionId,
9113
+ messagesBySession: state.messagesBySession,
9114
+ loadStatusBySession: state.sessionMessageLoadStatusBySession,
9115
+ isStreamingBySession: state.isStreamingBySession
9116
+ });
9117
+ if (!shouldLoad) {
9118
+ return;
9119
+ }
9120
+ setHistorySessionMessageLoadStatus(sessionId, "loading");
9121
+ try {
9122
+ const result = await onSelectHistorySession(session);
9123
+ if (Array.isArray(result)) {
9124
+ hydrateHistorySessionMessages(sessionId, result);
9125
+ return;
9126
+ }
9127
+ if (isHistorySessionMessagesPage(result)) {
9128
+ hydrateHistorySessionMessagesPage(sessionId, result);
9129
+ return;
9130
+ }
9131
+ setHistorySessionMessageLoadStatus(sessionId, "loaded");
9132
+ } catch (error2) {
9133
+ setHistorySessionMessageLoadStatus(
9134
+ sessionId,
9135
+ "error",
9136
+ error2 instanceof Error ? error2.message : String(error2)
9137
+ );
9138
+ }
8724
9139
  };
8725
9140
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Container3, { children: [
8726
9141
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(Toolbar, { children: [
8727
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Title3, { children: "Sessions" }),
9142
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Title3, { children: labels.sessionsTitle }),
8728
9143
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(CreateButton, { type: "button", "data-testid": "chat-create-session", onClick: startNewChat, children: labels.newChat })
8729
9144
  ] }),
8730
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(List2, { "data-testid": "chat-session-list", children: sessions.map((session) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
8731
- ChatSessionItem,
8732
- {
8733
- session,
8734
- isActive: activeSessionId === session.sessionId,
8735
- modeLabel: modeLabels[session.mode ?? DEFAULT_CHAT_AGENT_MODE] ?? "",
8736
- onClick: setActiveSession
8737
- },
8738
- session.sessionId
8739
- )) })
9145
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(List2, { "data-testid": "chat-session-list", onScroll: handleSessionListScroll, children: [
9146
+ sessions.map((session) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
9147
+ ChatSessionItem,
9148
+ {
9149
+ session,
9150
+ isActive: activeSessionId === session.sessionId,
9151
+ onClick: (sessionId) => void handleSelectSession(sessionId)
9152
+ },
9153
+ session.sessionId
9154
+ )),
9155
+ historySessionList?.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StateRow, { "data-testid": "chat-session-history-loading", children: labels.sessionHistoryLoading }) : null,
9156
+ historySessionList?.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StateRow, { "data-testid": "chat-session-history-error", children: historySessionList.error || labels.sessionHistoryLoadFailed }) : null,
9157
+ 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
9158
+ ] })
8740
9159
  ] });
8741
9160
  };
8742
9161
  var Container3 = import_styled16.default.aside`
@@ -8759,13 +9178,32 @@ var Title3 = import_styled16.default.h2`
8759
9178
  color: var(--text-secondary);
8760
9179
  `;
8761
9180
  var CreateButton = import_styled16.default.button`
8762
- border: none;
9181
+ border: 1px solid rgba(255, 255, 255, 0.18);
8763
9182
  border-radius: 12px;
8764
9183
  padding: 12px 14px;
8765
- background: var(--text-primary);
8766
- color: var(--bg-primary);
9184
+ background: rgba(255, 255, 255, 0.9);
9185
+ color: rgba(20, 20, 20, 0.92);
8767
9186
  text-align: left;
8768
9187
  cursor: pointer;
9188
+ transition:
9189
+ background-color 160ms ease,
9190
+ border-color 160ms ease,
9191
+ transform 160ms ease;
9192
+
9193
+ &:hover {
9194
+ background: rgba(255, 255, 255, 0.96);
9195
+ border-color: rgba(255, 255, 255, 0.28);
9196
+ }
9197
+
9198
+ &:focus-visible {
9199
+ outline: none;
9200
+ background: rgba(255, 255, 255, 0.96);
9201
+ border-color: rgba(255, 255, 255, 0.32);
9202
+ }
9203
+
9204
+ &:active {
9205
+ transform: scale(0.995);
9206
+ }
8769
9207
  `;
8770
9208
  var List2 = import_styled16.default.div`
8771
9209
  padding: 0 12px 16px;
@@ -8774,6 +9212,11 @@ var List2 = import_styled16.default.div`
8774
9212
  gap: 8px;
8775
9213
  overflow: auto;
8776
9214
  `;
9215
+ var StateRow = import_styled16.default.div`
9216
+ padding: 12px;
9217
+ font-size: 13px;
9218
+ color: var(--text-secondary);
9219
+ `;
8777
9220
 
8778
9221
  // src/components/ai-chat/index.tsx
8779
9222
  var import_jsx_runtime19 = require("@emotion/react/jsx-runtime");
@@ -8835,7 +9278,7 @@ var AiChatWorkspaceContent = ({
8835
9278
  })
8836
9279
  );
8837
9280
  const shouldShowComposerOnly = showComposerOnlyBeforeFirstMessage && !showConversationList && !isConversationStarted;
8838
- (0, import_react20.useEffect)(() => {
9281
+ (0, import_react21.useEffect)(() => {
8839
9282
  onConversationStartedChange?.(isConversationStarted);
8840
9283
  }, [isConversationStarted, onConversationStartedChange]);
8841
9284
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(Root, { "data-testid": "ai-chat", children: [
@@ -8974,12 +9417,25 @@ var Root = import_styled17.default.div`
8974
9417
  overflow: hidden;
8975
9418
  `;
8976
9419
  var Workspace = import_styled17.default.section`
9420
+ --chat-layout-rem: 16px;
9421
+ --chat-content-margin: calc(var(--chat-layout-rem) * 1);
9422
+ --chat-content-max-width: calc(var(--chat-layout-rem) * 40);
9423
+
8977
9424
  flex: 1;
8978
9425
  display: flex;
8979
9426
  flex-direction: column;
8980
9427
  gap: 12px;
8981
9428
  min-height: 0;
8982
9429
  overflow: hidden;
9430
+
9431
+ @media (min-width: 640px) {
9432
+ --chat-content-margin: calc(var(--chat-layout-rem) * 1.5);
9433
+ }
9434
+
9435
+ @media (min-width: 1024px) {
9436
+ --chat-content-margin: calc(var(--chat-layout-rem) * 4);
9437
+ --chat-content-max-width: calc(var(--chat-layout-rem) * 48);
9438
+ }
8983
9439
  `;
8984
9440
  var QuickActionsRow = import_styled17.default.div`
8985
9441
  display: flex;