@yushaw/sanqian-chat 0.2.39 → 0.2.41

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.
@@ -1260,6 +1260,10 @@ interface HistoryListProps {
1260
1260
  yesterday?: string;
1261
1261
  delete?: string;
1262
1262
  };
1263
+ /** Whether a conversation should be highlighted (host-defined relation) */
1264
+ isConversationHighlighted?: (conversation: ConversationInfo) => boolean;
1265
+ /** Label for highlighted conversations */
1266
+ highlightedLabel?: string | ((conversation: ConversationInfo) => string | null | undefined);
1263
1267
  }
1264
1268
  declare const HistoryList: react.NamedExoticComponent<HistoryListProps>;
1265
1269
 
@@ -1318,6 +1322,34 @@ interface AlertConfig {
1318
1322
  action?: AlertAction;
1319
1323
  dismissible?: boolean;
1320
1324
  }
1325
+ interface CompactChatStateSnapshot {
1326
+ messages: ChatMessage[];
1327
+ isLoading: boolean;
1328
+ isStreaming: boolean;
1329
+ error: string | null;
1330
+ conversationId: string | null;
1331
+ conversationTitle: string | null;
1332
+ capabilities?: UseChatCapabilities;
1333
+ }
1334
+ interface CompactChatController {
1335
+ sendMessage: (content: string) => Promise<void>;
1336
+ trySendMessage: (content: string) => Promise<boolean>;
1337
+ newConversation: (options?: ConversationSwitchOptions) => void;
1338
+ loadConversation: (id: string, options?: ConversationSwitchOptions) => Promise<void>;
1339
+ stopStreaming: () => void;
1340
+ focusInput: () => void;
1341
+ setInputText: (text: string) => void;
1342
+ getState: () => CompactChatStateSnapshot;
1343
+ }
1344
+ interface CompactChatHistoryConfig {
1345
+ /**
1346
+ * Whether a conversation is related to host-side context and should be highlighted.
1347
+ * Example: highlight conversations linked to the current note.
1348
+ */
1349
+ isConversationHighlighted?: (conversation: ConversationInfo) => boolean;
1350
+ /** Label shown on highlighted rows. */
1351
+ highlightedLabel?: string | ((conversation: ConversationInfo) => string | null | undefined);
1352
+ }
1321
1353
  interface CompactChatProps {
1322
1354
  /** Chat adapter for backend communication */
1323
1355
  adapter: ChatAdapter;
@@ -1335,11 +1367,10 @@ interface CompactChatProps {
1335
1367
  onMessageReceived?: (message: ChatMessage) => void;
1336
1368
  /** Called when loading state changes */
1337
1369
  onLoadingChange?: (isLoading: boolean) => void;
1338
- /** Called when chat state changes (messages, conversationId) */
1339
- onStateChange?: (state: {
1340
- messages: ChatMessage[];
1341
- conversationId: string | null;
1342
- }) => void;
1370
+ /** Called when conversation changes (including detached/background completion metadata). */
1371
+ onConversationChange?: (conversationId: string, title?: string, meta?: ConversationChangeMeta) => void;
1372
+ /** Called when chat state changes */
1373
+ onStateChange?: (state: CompactChatStateSnapshot) => void;
1343
1374
  /** Header content (left side) */
1344
1375
  headerLeft?: ReactNode;
1345
1376
  /** Header content (right side) */
@@ -1353,11 +1384,21 @@ interface CompactChatProps {
1353
1384
  /** Ref to expose sendMessage function for external input */
1354
1385
  sendMessageRef?: React.MutableRefObject<((message: string) => void) | null>;
1355
1386
  /** Ref to expose newConversation function */
1356
- newConversationRef?: React.MutableRefObject<(() => void) | null>;
1387
+ newConversationRef?: React.MutableRefObject<((options?: ConversationSwitchOptions) => void) | null>;
1388
+ /** Ref to expose loadConversation function */
1389
+ loadConversationRef?: React.MutableRefObject<((id: string, options?: ConversationSwitchOptions) => Promise<void>) | null>;
1390
+ /** Ref to expose stopStreaming function */
1391
+ stopStreamingRef?: React.MutableRefObject<(() => void) | null>;
1357
1392
  /** Ref to expose focusInput function */
1358
1393
  focusInputRef?: React.MutableRefObject<(() => void) | null>;
1359
1394
  /** Ref to expose setText function (for filling input externally) */
1360
1395
  setTextRef?: React.MutableRefObject<((text: string) => void) | null>;
1396
+ /** Ref to expose a full controller for advanced integrations */
1397
+ controllerRef?: React.MutableRefObject<CompactChatController | null>;
1398
+ /** Called after a conversation is deleted from history */
1399
+ onConversationDeleted?: (id: string) => void;
1400
+ /** History list customization */
1401
+ historyConfig?: CompactChatHistoryConfig;
1361
1402
  /** Custom message renderer */
1362
1403
  renderMessage?: (message: ChatMessage) => ReactNode;
1363
1404
  /** Empty state content (when no messages) */
@@ -1696,4 +1737,4 @@ declare function ensureChatBaseStyles(): void;
1696
1737
  */
1697
1738
  declare function ensureFullChatStyles(): void;
1698
1739
 
1699
- export { AddResourceButton, type AddResourceButtonProps, type AlertAction, AlertBanner, type AlertBannerProps, type AlertConfig, type AlertType, AttachButton, type AttachButtonProps, type AttachConfig, type AttachPosition, type AttachState, type AttachedResource, AttachedResourceTags, type AttachedResourceTagsProps, type AttachmentMenuItem, type AttachmentMenuItemType, type ChatAdapter, type ChatAdapterConfig, type ChatFontSize, ChatInput, type ChatInputHandle, type ChatInputProps, type ChatPanelConfig, type ChatPanelMode, type ChatPanelPosition, type ChatThemeMode, type ChatUiConfig, type ChatUiConfigSerializable, type ChatUiStrings, CompactChat, type CompactChatProps, type ConnectionErrorCode, type ConnectionStatus, type ContextProviderInfo, type ConversationChangeMeta, type ConversationDetail, type ConversationInfo, type ConversationSwitchOptions, FloatingChat, type FloatingChatProps, type FloatingWindowConfig, HistoryList, type HistoryListProps, HistoryModal, type HistoryModalProps, HitlCard, type HitlCardProps, type HitlInterruptData, I18nProvider, type I18nProviderProps, IntermediateSteps, type IntermediateStepsProps, type LinkClickEvent, type LinkHandlerConfig, type Locale, MarkdownRenderer, type MarkdownRendererProps, type MessageBlock, MessageBubble, type MessageBubbleProps, MessageList, type MessageListProps, type MessageRole, ModeToggleButton, type ModeToggleButtonProps, PanelControls, type PanelControlsProps, PanelHeaderButtons, PanelResizer, Resizer, type ResizerProps, type ResolvedTheme, ResourceChip, ResourceChipList, type ResourceChipListProps, type ResourceChipProps, ResourcePicker, type ResourcePickerItem, type ResourcePickerProps, type ResourcePickerState, SanqianChat, SanqianChatMessage, type SanqianChatMessageProps, type SanqianChatProps, SanqianMessageList, type SanqianMessageListHandle, type SanqianMessageListProps, type SdkAdapterConfig, type SendMessage, type SendMessageOptions, type SessionResource, type SessionResourceEvent, type StoredSessionResource, type StreamEvent, StreamingTimeline, type StreamingTimelineProps, type ThemeMode, ThemeProvider, type ThemeProviderProps, ThinkingSection, type ThinkingSectionProps, ToolArgumentsDisplay, type ToolArgumentsDisplayProps, type ToolCall, type ToolCallStatus, type Translations, type UseAttachStateReturn, type UseChatCapabilities, type UseChatOptions, type UseChatPanelReturn, type UseChatReturn, type UseConnectionOptions, type UseConnectionReturn, type UseConversationsOptions, type UseConversationsReturn, type UseFocusPersistenceOptions, type UseResourcePickerOptions, type UseResourcePickerReturn, type WindowPosition, createChatAdapter, createIpcAdapter, createSdkAdapter, ensureChatBaseStyles, ensureFullChatStyles, getTranslations, resolveChatStrings, useAttachState, useChat, useChatPanel, useChatStyles, useConnection, useConversations, useFocusPersistence, useI18n, useIpcUiConfig, useResolvedUiConfig, useResourcePicker, useStandaloneI18n, useStandaloneTheme, useTheme, useWindowDragLock };
1740
+ export { AddResourceButton, type AddResourceButtonProps, type AlertAction, AlertBanner, type AlertBannerProps, type AlertConfig, type AlertType, AttachButton, type AttachButtonProps, type AttachConfig, type AttachPosition, type AttachState, type AttachedResource, AttachedResourceTags, type AttachedResourceTagsProps, type AttachmentMenuItem, type AttachmentMenuItemType, type ChatAdapter, type ChatAdapterConfig, type ChatFontSize, ChatInput, type ChatInputHandle, type ChatInputProps, type ChatPanelConfig, type ChatPanelMode, type ChatPanelPosition, type ChatThemeMode, type ChatUiConfig, type ChatUiConfigSerializable, type ChatUiStrings, CompactChat, type CompactChatController, type CompactChatHistoryConfig, type CompactChatProps, type CompactChatStateSnapshot, type ConnectionErrorCode, type ConnectionStatus, type ContextProviderInfo, type ConversationChangeMeta, type ConversationDetail, type ConversationInfo, type ConversationSwitchOptions, FloatingChat, type FloatingChatProps, type FloatingWindowConfig, HistoryList, type HistoryListProps, HistoryModal, type HistoryModalProps, HitlCard, type HitlCardProps, type HitlInterruptData, I18nProvider, type I18nProviderProps, IntermediateSteps, type IntermediateStepsProps, type LinkClickEvent, type LinkHandlerConfig, type Locale, MarkdownRenderer, type MarkdownRendererProps, type MessageBlock, MessageBubble, type MessageBubbleProps, MessageList, type MessageListProps, type MessageRole, ModeToggleButton, type ModeToggleButtonProps, PanelControls, type PanelControlsProps, PanelHeaderButtons, PanelResizer, Resizer, type ResizerProps, type ResolvedTheme, ResourceChip, ResourceChipList, type ResourceChipListProps, type ResourceChipProps, ResourcePicker, type ResourcePickerItem, type ResourcePickerProps, type ResourcePickerState, SanqianChat, SanqianChatMessage, type SanqianChatMessageProps, type SanqianChatProps, SanqianMessageList, type SanqianMessageListHandle, type SanqianMessageListProps, type SdkAdapterConfig, type SendMessage, type SendMessageOptions, type SessionResource, type SessionResourceEvent, type StoredSessionResource, type StreamEvent, StreamingTimeline, type StreamingTimelineProps, type ThemeMode, ThemeProvider, type ThemeProviderProps, ThinkingSection, type ThinkingSectionProps, ToolArgumentsDisplay, type ToolArgumentsDisplayProps, type ToolCall, type ToolCallStatus, type Translations, type UseAttachStateReturn, type UseChatCapabilities, type UseChatOptions, type UseChatPanelReturn, type UseChatReturn, type UseConnectionOptions, type UseConnectionReturn, type UseConversationsOptions, type UseConversationsReturn, type UseFocusPersistenceOptions, type UseResourcePickerOptions, type UseResourcePickerReturn, type WindowPosition, createChatAdapter, createIpcAdapter, createSdkAdapter, ensureChatBaseStyles, ensureFullChatStyles, getTranslations, resolveChatStrings, useAttachState, useChat, useChatPanel, useChatStyles, useConnection, useConversations, useFocusPersistence, useI18n, useIpcUiConfig, useResolvedUiConfig, useResourcePicker, useStandaloneI18n, useStandaloneTheme, useTheme, useWindowDragLock };
@@ -1260,6 +1260,10 @@ interface HistoryListProps {
1260
1260
  yesterday?: string;
1261
1261
  delete?: string;
1262
1262
  };
1263
+ /** Whether a conversation should be highlighted (host-defined relation) */
1264
+ isConversationHighlighted?: (conversation: ConversationInfo) => boolean;
1265
+ /** Label for highlighted conversations */
1266
+ highlightedLabel?: string | ((conversation: ConversationInfo) => string | null | undefined);
1263
1267
  }
1264
1268
  declare const HistoryList: react.NamedExoticComponent<HistoryListProps>;
1265
1269
 
@@ -1318,6 +1322,34 @@ interface AlertConfig {
1318
1322
  action?: AlertAction;
1319
1323
  dismissible?: boolean;
1320
1324
  }
1325
+ interface CompactChatStateSnapshot {
1326
+ messages: ChatMessage[];
1327
+ isLoading: boolean;
1328
+ isStreaming: boolean;
1329
+ error: string | null;
1330
+ conversationId: string | null;
1331
+ conversationTitle: string | null;
1332
+ capabilities?: UseChatCapabilities;
1333
+ }
1334
+ interface CompactChatController {
1335
+ sendMessage: (content: string) => Promise<void>;
1336
+ trySendMessage: (content: string) => Promise<boolean>;
1337
+ newConversation: (options?: ConversationSwitchOptions) => void;
1338
+ loadConversation: (id: string, options?: ConversationSwitchOptions) => Promise<void>;
1339
+ stopStreaming: () => void;
1340
+ focusInput: () => void;
1341
+ setInputText: (text: string) => void;
1342
+ getState: () => CompactChatStateSnapshot;
1343
+ }
1344
+ interface CompactChatHistoryConfig {
1345
+ /**
1346
+ * Whether a conversation is related to host-side context and should be highlighted.
1347
+ * Example: highlight conversations linked to the current note.
1348
+ */
1349
+ isConversationHighlighted?: (conversation: ConversationInfo) => boolean;
1350
+ /** Label shown on highlighted rows. */
1351
+ highlightedLabel?: string | ((conversation: ConversationInfo) => string | null | undefined);
1352
+ }
1321
1353
  interface CompactChatProps {
1322
1354
  /** Chat adapter for backend communication */
1323
1355
  adapter: ChatAdapter;
@@ -1335,11 +1367,10 @@ interface CompactChatProps {
1335
1367
  onMessageReceived?: (message: ChatMessage) => void;
1336
1368
  /** Called when loading state changes */
1337
1369
  onLoadingChange?: (isLoading: boolean) => void;
1338
- /** Called when chat state changes (messages, conversationId) */
1339
- onStateChange?: (state: {
1340
- messages: ChatMessage[];
1341
- conversationId: string | null;
1342
- }) => void;
1370
+ /** Called when conversation changes (including detached/background completion metadata). */
1371
+ onConversationChange?: (conversationId: string, title?: string, meta?: ConversationChangeMeta) => void;
1372
+ /** Called when chat state changes */
1373
+ onStateChange?: (state: CompactChatStateSnapshot) => void;
1343
1374
  /** Header content (left side) */
1344
1375
  headerLeft?: ReactNode;
1345
1376
  /** Header content (right side) */
@@ -1353,11 +1384,21 @@ interface CompactChatProps {
1353
1384
  /** Ref to expose sendMessage function for external input */
1354
1385
  sendMessageRef?: React.MutableRefObject<((message: string) => void) | null>;
1355
1386
  /** Ref to expose newConversation function */
1356
- newConversationRef?: React.MutableRefObject<(() => void) | null>;
1387
+ newConversationRef?: React.MutableRefObject<((options?: ConversationSwitchOptions) => void) | null>;
1388
+ /** Ref to expose loadConversation function */
1389
+ loadConversationRef?: React.MutableRefObject<((id: string, options?: ConversationSwitchOptions) => Promise<void>) | null>;
1390
+ /** Ref to expose stopStreaming function */
1391
+ stopStreamingRef?: React.MutableRefObject<(() => void) | null>;
1357
1392
  /** Ref to expose focusInput function */
1358
1393
  focusInputRef?: React.MutableRefObject<(() => void) | null>;
1359
1394
  /** Ref to expose setText function (for filling input externally) */
1360
1395
  setTextRef?: React.MutableRefObject<((text: string) => void) | null>;
1396
+ /** Ref to expose a full controller for advanced integrations */
1397
+ controllerRef?: React.MutableRefObject<CompactChatController | null>;
1398
+ /** Called after a conversation is deleted from history */
1399
+ onConversationDeleted?: (id: string) => void;
1400
+ /** History list customization */
1401
+ historyConfig?: CompactChatHistoryConfig;
1361
1402
  /** Custom message renderer */
1362
1403
  renderMessage?: (message: ChatMessage) => ReactNode;
1363
1404
  /** Empty state content (when no messages) */
@@ -1696,4 +1737,4 @@ declare function ensureChatBaseStyles(): void;
1696
1737
  */
1697
1738
  declare function ensureFullChatStyles(): void;
1698
1739
 
1699
- export { AddResourceButton, type AddResourceButtonProps, type AlertAction, AlertBanner, type AlertBannerProps, type AlertConfig, type AlertType, AttachButton, type AttachButtonProps, type AttachConfig, type AttachPosition, type AttachState, type AttachedResource, AttachedResourceTags, type AttachedResourceTagsProps, type AttachmentMenuItem, type AttachmentMenuItemType, type ChatAdapter, type ChatAdapterConfig, type ChatFontSize, ChatInput, type ChatInputHandle, type ChatInputProps, type ChatPanelConfig, type ChatPanelMode, type ChatPanelPosition, type ChatThemeMode, type ChatUiConfig, type ChatUiConfigSerializable, type ChatUiStrings, CompactChat, type CompactChatProps, type ConnectionErrorCode, type ConnectionStatus, type ContextProviderInfo, type ConversationChangeMeta, type ConversationDetail, type ConversationInfo, type ConversationSwitchOptions, FloatingChat, type FloatingChatProps, type FloatingWindowConfig, HistoryList, type HistoryListProps, HistoryModal, type HistoryModalProps, HitlCard, type HitlCardProps, type HitlInterruptData, I18nProvider, type I18nProviderProps, IntermediateSteps, type IntermediateStepsProps, type LinkClickEvent, type LinkHandlerConfig, type Locale, MarkdownRenderer, type MarkdownRendererProps, type MessageBlock, MessageBubble, type MessageBubbleProps, MessageList, type MessageListProps, type MessageRole, ModeToggleButton, type ModeToggleButtonProps, PanelControls, type PanelControlsProps, PanelHeaderButtons, PanelResizer, Resizer, type ResizerProps, type ResolvedTheme, ResourceChip, ResourceChipList, type ResourceChipListProps, type ResourceChipProps, ResourcePicker, type ResourcePickerItem, type ResourcePickerProps, type ResourcePickerState, SanqianChat, SanqianChatMessage, type SanqianChatMessageProps, type SanqianChatProps, SanqianMessageList, type SanqianMessageListHandle, type SanqianMessageListProps, type SdkAdapterConfig, type SendMessage, type SendMessageOptions, type SessionResource, type SessionResourceEvent, type StoredSessionResource, type StreamEvent, StreamingTimeline, type StreamingTimelineProps, type ThemeMode, ThemeProvider, type ThemeProviderProps, ThinkingSection, type ThinkingSectionProps, ToolArgumentsDisplay, type ToolArgumentsDisplayProps, type ToolCall, type ToolCallStatus, type Translations, type UseAttachStateReturn, type UseChatCapabilities, type UseChatOptions, type UseChatPanelReturn, type UseChatReturn, type UseConnectionOptions, type UseConnectionReturn, type UseConversationsOptions, type UseConversationsReturn, type UseFocusPersistenceOptions, type UseResourcePickerOptions, type UseResourcePickerReturn, type WindowPosition, createChatAdapter, createIpcAdapter, createSdkAdapter, ensureChatBaseStyles, ensureFullChatStyles, getTranslations, resolveChatStrings, useAttachState, useChat, useChatPanel, useChatStyles, useConnection, useConversations, useFocusPersistence, useI18n, useIpcUiConfig, useResolvedUiConfig, useResourcePicker, useStandaloneI18n, useStandaloneTheme, useTheme, useWindowDragLock };
1740
+ export { AddResourceButton, type AddResourceButtonProps, type AlertAction, AlertBanner, type AlertBannerProps, type AlertConfig, type AlertType, AttachButton, type AttachButtonProps, type AttachConfig, type AttachPosition, type AttachState, type AttachedResource, AttachedResourceTags, type AttachedResourceTagsProps, type AttachmentMenuItem, type AttachmentMenuItemType, type ChatAdapter, type ChatAdapterConfig, type ChatFontSize, ChatInput, type ChatInputHandle, type ChatInputProps, type ChatPanelConfig, type ChatPanelMode, type ChatPanelPosition, type ChatThemeMode, type ChatUiConfig, type ChatUiConfigSerializable, type ChatUiStrings, CompactChat, type CompactChatController, type CompactChatHistoryConfig, type CompactChatProps, type CompactChatStateSnapshot, type ConnectionErrorCode, type ConnectionStatus, type ContextProviderInfo, type ConversationChangeMeta, type ConversationDetail, type ConversationInfo, type ConversationSwitchOptions, FloatingChat, type FloatingChatProps, type FloatingWindowConfig, HistoryList, type HistoryListProps, HistoryModal, type HistoryModalProps, HitlCard, type HitlCardProps, type HitlInterruptData, I18nProvider, type I18nProviderProps, IntermediateSteps, type IntermediateStepsProps, type LinkClickEvent, type LinkHandlerConfig, type Locale, MarkdownRenderer, type MarkdownRendererProps, type MessageBlock, MessageBubble, type MessageBubbleProps, MessageList, type MessageListProps, type MessageRole, ModeToggleButton, type ModeToggleButtonProps, PanelControls, type PanelControlsProps, PanelHeaderButtons, PanelResizer, Resizer, type ResizerProps, type ResolvedTheme, ResourceChip, ResourceChipList, type ResourceChipListProps, type ResourceChipProps, ResourcePicker, type ResourcePickerItem, type ResourcePickerProps, type ResourcePickerState, SanqianChat, SanqianChatMessage, type SanqianChatMessageProps, type SanqianChatProps, SanqianMessageList, type SanqianMessageListHandle, type SanqianMessageListProps, type SdkAdapterConfig, type SendMessage, type SendMessageOptions, type SessionResource, type SessionResourceEvent, type StoredSessionResource, type StreamEvent, StreamingTimeline, type StreamingTimelineProps, type ThemeMode, ThemeProvider, type ThemeProviderProps, ThinkingSection, type ThinkingSectionProps, ToolArgumentsDisplay, type ToolArgumentsDisplayProps, type ToolCall, type ToolCallStatus, type Translations, type UseAttachStateReturn, type UseChatCapabilities, type UseChatOptions, type UseChatPanelReturn, type UseChatReturn, type UseConnectionOptions, type UseConnectionReturn, type UseConversationsOptions, type UseConversationsReturn, type UseFocusPersistenceOptions, type UseResourcePickerOptions, type UseResourcePickerReturn, type WindowPosition, createChatAdapter, createIpcAdapter, createSdkAdapter, ensureChatBaseStyles, ensureFullChatStyles, getTranslations, resolveChatStrings, useAttachState, useChat, useChatPanel, useChatStyles, useConnection, useConversations, useFocusPersistence, useI18n, useIpcUiConfig, useResolvedUiConfig, useResourcePicker, useStandaloneI18n, useStandaloneTheme, useTheme, useWindowDragLock };
@@ -1920,33 +1920,56 @@ code {
1920
1920
 
1921
1921
  [data-streamdown="code-block-header"] {
1922
1922
  position: absolute !important;
1923
- top: 14px !important;
1924
- right: 8px !important;
1923
+ top: 10px !important;
1924
+ left: 12px !important;
1925
+ right: auto !important;
1925
1926
  z-index: 10;
1926
1927
  padding: 0 !important;
1927
1928
  background: transparent !important;
1928
1929
  border: none !important;
1929
1930
  opacity: 0;
1930
1931
  transition: opacity 150ms ease;
1932
+ pointer-events: none;
1933
+ }
1934
+
1935
+ [data-streamdown="code-block-actions"] {
1936
+ position: absolute !important;
1937
+ top: 10px !important;
1938
+ right: 8px !important;
1939
+ z-index: 11;
1940
+ display: flex !important;
1941
+ align-items: center;
1942
+ gap: 4px;
1943
+ padding: 0 !important;
1944
+ background: transparent !important;
1945
+ border: none !important;
1946
+ opacity: 0;
1947
+ transition: opacity 150ms ease;
1948
+ pointer-events: auto !important;
1931
1949
  }
1932
1950
 
1933
- [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"] {
1951
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"],
1952
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-actions"] {
1934
1953
  opacity: 1;
1935
1954
  }
1936
1955
 
1937
1956
  [data-streamdown="code-block-header"] > span:first-child {
1957
+ display: inline-flex;
1938
1958
  font-size: 11px;
1939
1959
  color: var(--color-muted);
1940
1960
  text-transform: lowercase;
1941
- margin-right: 8px;
1942
1961
  }
1943
1962
 
1944
1963
  [data-streamdown="code-block-header"] > div {
1945
1964
  display: flex;
1946
1965
  gap: 4px;
1966
+ margin-left: 8px;
1967
+ pointer-events: auto;
1947
1968
  }
1948
1969
 
1949
- [data-streamdown="code-block-header"] button {
1970
+ /* Normalize action buttons for both streamdown v1 (header>div) and v2 (code-block-actions). */
1971
+ [data-streamdown="code-block-header"] button,
1972
+ [data-streamdown="code-block-actions"] button {
1950
1973
  padding: 4px !important;
1951
1974
  border: none !important;
1952
1975
  border-radius: 4px !important;
@@ -1955,18 +1978,21 @@ code {
1955
1978
  cursor: pointer;
1956
1979
  }
1957
1980
 
1958
- [data-streamdown="code-block-header"] button:hover {
1981
+ [data-streamdown="code-block-header"] button:hover,
1982
+ [data-streamdown="code-block-actions"] button:hover {
1959
1983
  opacity: 1;
1960
1984
  background: var(--color-border) !important;
1961
1985
  }
1962
1986
 
1963
- [data-streamdown="code-block-header"] svg {
1987
+ [data-streamdown="code-block-header"] svg,
1988
+ [data-streamdown="code-block-actions"] svg {
1964
1989
  color: var(--color-muted) !important;
1965
1990
  width: 12px !important;
1966
1991
  height: 12px !important;
1967
1992
  }
1968
1993
 
1969
- [data-streamdown="code-block-header"] button:hover svg {
1994
+ [data-streamdown="code-block-header"] button:hover svg,
1995
+ [data-streamdown="code-block-actions"] button:hover svg {
1970
1996
  color: var(--color-text) !important;
1971
1997
  }
1972
1998
 
@@ -6967,9 +6993,8 @@ var useChatHeader = (config) => {
6967
6993
  }
6968
6994
  if (typeof config?.alwaysOnTop === "boolean") {
6969
6995
  setIsPinned(config.alwaysOnTop);
6970
- resolvedOnPin?.(config.alwaysOnTop);
6971
6996
  }
6972
- }, [config?.alwaysOnTop, resolvedOnPin]);
6997
+ }, [config?.alwaysOnTop]);
6973
6998
  const showPin = !!resolvedOnPin || typeof config?.alwaysOnTop === "boolean";
6974
6999
  const showClose = !!resolvedOnClose;
6975
7000
  const logoNode = (0, import_react17.useMemo)(() => resolveLogoNode(config?.logo, "header"), [config?.logo]);
@@ -10252,7 +10277,9 @@ var HistoryList = (0, import_react36.memo)(function HistoryList2({
10252
10277
  onDelete,
10253
10278
  onLoadMore,
10254
10279
  isDarkMode = false,
10255
- strings = {}
10280
+ strings = {},
10281
+ isConversationHighlighted,
10282
+ highlightedLabel
10256
10283
  }) {
10257
10284
  const [hoveredId, setHoveredId] = (0, import_react36.useState)(null);
10258
10285
  const loadMoreRef = (0, import_react36.useRef)(null);
@@ -10308,6 +10335,8 @@ var HistoryList = (0, import_react36.memo)(function HistoryList2({
10308
10335
  conversations.map((conv) => {
10309
10336
  const isSelected = conv.id === selectedId;
10310
10337
  const isHovered = conv.id === hoveredId;
10338
+ const isHighlighted = isConversationHighlighted?.(conv) === true;
10339
+ const resolvedHighlightLabel = isHighlighted ? (typeof highlightedLabel === "function" ? highlightedLabel(conv) : highlightedLabel) ?? "RELATED" : null;
10311
10340
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
10312
10341
  "div",
10313
10342
  {
@@ -10319,21 +10348,42 @@ var HistoryList = (0, import_react36.memo)(function HistoryList2({
10319
10348
  padding: "0.5rem 0.75rem",
10320
10349
  textAlign: "left",
10321
10350
  cursor: "pointer",
10322
- transition: "background-color 0.15s ease",
10323
- background: isSelected || isHovered ? colors.hover : "transparent"
10351
+ transition: "background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease",
10352
+ border: isSelected ? "1px solid var(--chat-border-focus)" : "1px solid transparent",
10353
+ background: isSelected || isHovered ? colors.hover : isHighlighted ? "color-mix(in srgb, var(--chat-accent) 6%, transparent)" : "transparent",
10354
+ boxShadow: isSelected ? "inset 2px 0 0 var(--chat-accent)" : "none"
10324
10355
  },
10325
10356
  onClick: () => onSelect(conv.id),
10326
10357
  onMouseEnter: () => setHoveredId(conv.id),
10327
10358
  onMouseLeave: () => setHoveredId(null),
10328
10359
  children: [
10329
10360
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { minWidth: 0, flex: 1 }, children: [
10330
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: {
10331
- fontSize: "0.875rem",
10332
- color: colors.text,
10333
- overflow: "hidden",
10334
- textOverflow: "ellipsis",
10335
- whiteSpace: "nowrap"
10336
- }, children: conv.title || "Untitled" }),
10361
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.375rem", minWidth: 0 }, children: [
10362
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: {
10363
+ fontSize: "0.875rem",
10364
+ color: colors.text,
10365
+ overflow: "hidden",
10366
+ textOverflow: "ellipsis",
10367
+ whiteSpace: "nowrap"
10368
+ }, children: conv.title || "Untitled" }),
10369
+ resolvedHighlightLabel && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
10370
+ "span",
10371
+ {
10372
+ style: {
10373
+ fontSize: "0.625rem",
10374
+ lineHeight: 1,
10375
+ padding: "0.18rem 0.32rem",
10376
+ borderRadius: 999,
10377
+ border: "1px solid color-mix(in srgb, var(--chat-accent) 30%, transparent)",
10378
+ background: "color-mix(in srgb, var(--chat-accent) 12%, transparent)",
10379
+ color: "color-mix(in srgb, var(--chat-accent) 85%, var(--chat-text) 15%)",
10380
+ flexShrink: 0,
10381
+ fontWeight: 500
10382
+ },
10383
+ children: resolvedHighlightLabel
10384
+ }
10385
+ )
10386
+ ] }),
10337
10387
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { fontSize: "0.75rem", color: colors.muted }, children: formatRelativeTime(conv.updatedAt, strings) })
10338
10388
  ] }),
10339
10389
  isHovered && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
@@ -10369,39 +10419,50 @@ var HistoryModal = (0, import_react37.memo)(function HistoryModal2({
10369
10419
  isDarkMode = false
10370
10420
  }) {
10371
10421
  const modalRef = (0, import_react37.useRef)(null);
10422
+ const closeTimerRef = (0, import_react37.useRef)(null);
10372
10423
  const [isClosing, setIsClosing] = (0, import_react37.useState)(false);
10373
10424
  const [shouldRender, setShouldRender] = (0, import_react37.useState)(isOpen);
10425
+ const clearCloseTimer = (0, import_react37.useCallback)(() => {
10426
+ if (closeTimerRef.current === null) return;
10427
+ clearTimeout(closeTimerRef.current);
10428
+ closeTimerRef.current = null;
10429
+ }, []);
10374
10430
  (0, import_react37.useEffect)(() => {
10375
10431
  if (isOpen) {
10432
+ clearCloseTimer();
10376
10433
  setShouldRender(true);
10377
10434
  setIsClosing(false);
10378
- } else if (shouldRender && !isClosing) {
10379
- setIsClosing(true);
10380
- setTimeout(() => {
10381
- setShouldRender(false);
10382
- setIsClosing(false);
10383
- }, ANIMATION_DURATION);
10435
+ return;
10436
+ }
10437
+ if (!shouldRender) {
10438
+ setIsClosing(false);
10439
+ return;
10384
10440
  }
10385
- }, [isOpen, shouldRender, isClosing]);
10386
- const handleClose = (0, import_react37.useCallback)(() => {
10387
10441
  setIsClosing(true);
10388
- setTimeout(() => {
10442
+ clearCloseTimer();
10443
+ closeTimerRef.current = setTimeout(() => {
10389
10444
  setShouldRender(false);
10390
10445
  setIsClosing(false);
10391
- onClose();
10446
+ closeTimerRef.current = null;
10392
10447
  }, ANIMATION_DURATION);
10393
- }, [onClose]);
10448
+ return clearCloseTimer;
10449
+ }, [isOpen, shouldRender, clearCloseTimer]);
10450
+ (0, import_react37.useEffect)(() => clearCloseTimer, [clearCloseTimer]);
10451
+ const handleCloseRequest = (0, import_react37.useCallback)(() => {
10452
+ if (!isOpen || isClosing) return;
10453
+ onClose();
10454
+ }, [isOpen, isClosing, onClose]);
10394
10455
  (0, import_react37.useEffect)(() => {
10395
10456
  if (!shouldRender || isClosing) return;
10396
10457
  const handleKeyDown = (e) => {
10397
10458
  if (e.key === "Escape") {
10398
10459
  e.preventDefault();
10399
- handleClose();
10460
+ handleCloseRequest();
10400
10461
  }
10401
10462
  };
10402
10463
  document.addEventListener("keydown", handleKeyDown);
10403
10464
  return () => document.removeEventListener("keydown", handleKeyDown);
10404
- }, [shouldRender, isClosing, handleClose]);
10465
+ }, [shouldRender, isClosing, handleCloseRequest]);
10405
10466
  (0, import_react37.useEffect)(() => {
10406
10467
  if (!isOpen || !modalRef.current) return;
10407
10468
  modalRef.current.focus();
@@ -10409,11 +10470,12 @@ var HistoryModal = (0, import_react37.memo)(function HistoryModal2({
10409
10470
  const handleBackdropClick = (0, import_react37.useCallback)(
10410
10471
  (e) => {
10411
10472
  if (e.target === e.currentTarget && !isClosing) {
10412
- handleClose();
10473
+ handleCloseRequest();
10413
10474
  }
10414
10475
  },
10415
- [handleClose, isClosing]
10476
+ [handleCloseRequest, isClosing]
10416
10477
  );
10478
+ useWindowDragLock(shouldRender);
10417
10479
  if (!shouldRender) return null;
10418
10480
  const closingClass = isClosing ? " chat-modal-closing" : "";
10419
10481
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
@@ -10482,7 +10544,7 @@ var HistoryModal = (0, import_react37.memo)(function HistoryModal2({
10482
10544
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
10483
10545
  "button",
10484
10546
  {
10485
- onClick: handleClose,
10547
+ onClick: handleCloseRequest,
10486
10548
  "aria-label": closeLabel,
10487
10549
  style: {
10488
10550
  display: "flex",
@@ -10594,6 +10656,7 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10594
10656
  onError,
10595
10657
  onMessageReceived,
10596
10658
  onLoadingChange,
10659
+ onConversationChange,
10597
10660
  onStateChange,
10598
10661
  headerLeft,
10599
10662
  headerRight,
@@ -10602,8 +10665,13 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10602
10665
  inputPortalContainer,
10603
10666
  sendMessageRef,
10604
10667
  newConversationRef,
10668
+ loadConversationRef,
10669
+ stopStreamingRef,
10605
10670
  focusInputRef: parentFocusInputRef,
10606
10671
  setTextRef: parentSetTextRef,
10672
+ controllerRef,
10673
+ onConversationDeleted,
10674
+ historyConfig,
10607
10675
  renderMessage,
10608
10676
  emptyState,
10609
10677
  className = "",
@@ -10645,7 +10713,6 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10645
10713
  const chatContainerRef = (0, import_react38.useRef)(null);
10646
10714
  const chatInputRef = (0, import_react38.useRef)(null);
10647
10715
  const [showHistory, setShowHistory] = (0, import_react38.useState)(false);
10648
- useWindowDragLock(showHistory);
10649
10716
  const [connectionAlert, setConnectionAlert] = (0, import_react38.useState)(null);
10650
10717
  const portalContainerRef = (0, import_react38.useRef)(inputPortalContainer ?? null);
10651
10718
  portalContainerRef.current = inputPortalContainer ?? null;
@@ -10674,7 +10741,8 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10674
10741
  });
10675
10742
  const chat = useChat({
10676
10743
  adapter,
10677
- onError
10744
+ onError,
10745
+ onConversationChange
10678
10746
  });
10679
10747
  useFocusPersistence({
10680
10748
  containerRef: chatContainerRef,
@@ -10718,6 +10786,16 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10718
10786
  newConversationRef.current = chat.newConversation;
10719
10787
  }
10720
10788
  }, [newConversationRef, chat.newConversation]);
10789
+ (0, import_react38.useEffect)(() => {
10790
+ if (loadConversationRef) {
10791
+ loadConversationRef.current = chat.loadConversation;
10792
+ }
10793
+ }, [loadConversationRef, chat.loadConversation]);
10794
+ (0, import_react38.useEffect)(() => {
10795
+ if (stopStreamingRef) {
10796
+ stopStreamingRef.current = chat.stopStreaming;
10797
+ }
10798
+ }, [stopStreamingRef, chat.stopStreaming]);
10721
10799
  (0, import_react38.useEffect)(() => {
10722
10800
  if (parentFocusInputRef) {
10723
10801
  parentFocusInputRef.current = () => chatInputRef.current?.focus();
@@ -10728,6 +10806,63 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10728
10806
  parentSetTextRef.current = (text) => chatInputRef.current?.setValue(text);
10729
10807
  }
10730
10808
  }, [parentSetTextRef]);
10809
+ const getStateSnapshot = (0, import_react38.useCallback)(() => ({
10810
+ messages: chat.messages,
10811
+ isLoading: chat.isLoading,
10812
+ isStreaming: chat.isStreaming,
10813
+ error: chat.error,
10814
+ conversationId: chat.conversationId,
10815
+ conversationTitle: chat.conversationTitle,
10816
+ capabilities: chat.capabilities
10817
+ }), [
10818
+ chat.messages,
10819
+ chat.isLoading,
10820
+ chat.isStreaming,
10821
+ chat.error,
10822
+ chat.conversationId,
10823
+ chat.conversationTitle,
10824
+ chat.capabilities
10825
+ ]);
10826
+ (0, import_react38.useEffect)(() => {
10827
+ if (!controllerRef) return;
10828
+ controllerRef.current = {
10829
+ sendMessage: chat.sendMessage,
10830
+ trySendMessage: chat.trySendMessage,
10831
+ newConversation: chat.newConversation,
10832
+ loadConversation: chat.loadConversation,
10833
+ stopStreaming: chat.stopStreaming,
10834
+ focusInput: () => chatInputRef.current?.focus(),
10835
+ setInputText: (text) => chatInputRef.current?.setValue(text),
10836
+ getState: getStateSnapshot
10837
+ };
10838
+ }, [
10839
+ controllerRef,
10840
+ chat.sendMessage,
10841
+ chat.trySendMessage,
10842
+ chat.newConversation,
10843
+ chat.loadConversation,
10844
+ chat.stopStreaming,
10845
+ getStateSnapshot
10846
+ ]);
10847
+ (0, import_react38.useEffect)(() => {
10848
+ return () => {
10849
+ if (sendMessageRef) sendMessageRef.current = null;
10850
+ if (newConversationRef) newConversationRef.current = null;
10851
+ if (loadConversationRef) loadConversationRef.current = null;
10852
+ if (stopStreamingRef) stopStreamingRef.current = null;
10853
+ if (parentFocusInputRef) parentFocusInputRef.current = null;
10854
+ if (parentSetTextRef) parentSetTextRef.current = null;
10855
+ if (controllerRef) controllerRef.current = null;
10856
+ };
10857
+ }, [
10858
+ sendMessageRef,
10859
+ newConversationRef,
10860
+ loadConversationRef,
10861
+ stopStreamingRef,
10862
+ parentFocusInputRef,
10863
+ parentSetTextRef,
10864
+ controllerRef
10865
+ ]);
10731
10866
  (0, import_react38.useEffect)(() => {
10732
10867
  if (!adapter.onFocusInput) return;
10733
10868
  return adapter.onFocusInput(() => {
@@ -10749,12 +10884,9 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10749
10884
  }, [chat.isLoading, onLoadingChange]);
10750
10885
  (0, import_react38.useEffect)(() => {
10751
10886
  if (onStateChange) {
10752
- onStateChange({
10753
- messages: chat.messages,
10754
- conversationId: chat.conversationId
10755
- });
10887
+ onStateChange(getStateSnapshot());
10756
10888
  }
10757
- }, [chat.messages, chat.conversationId, onStateChange]);
10889
+ }, [getStateSnapshot, onStateChange]);
10758
10890
  (0, import_react38.useEffect)(() => {
10759
10891
  if (connection.isConnected) {
10760
10892
  resourcePicker.refreshProviders();
@@ -10782,11 +10914,12 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
10782
10914
  const handleDeleteConversation = (0, import_react38.useCallback)(
10783
10915
  async (id) => {
10784
10916
  await conversations.deleteConversation(id);
10917
+ onConversationDeleted?.(id);
10785
10918
  if (id === chat.conversationId) {
10786
10919
  chat.newConversation();
10787
10920
  }
10788
10921
  },
10789
- [conversations, chat]
10922
+ [conversations, chat, onConversationDeleted]
10790
10923
  );
10791
10924
  const handleNewChat = (0, import_react38.useCallback)(() => {
10792
10925
  chat.newConversation();
@@ -11047,6 +11180,8 @@ var CompactChat = (0, import_react38.memo)(function CompactChat2({
11047
11180
  onDelete: handleDeleteConversation,
11048
11181
  onLoadMore: conversations.loadMore,
11049
11182
  isDarkMode: resolvedIsDarkMode,
11183
+ isConversationHighlighted: historyConfig?.isConversationHighlighted,
11184
+ highlightedLabel: historyConfig?.highlightedLabel,
11050
11185
  strings: {
11051
11186
  noHistory: mergedStrings.noHistory,
11052
11187
  loadMore: mergedStrings.loadMore,
@@ -1832,33 +1832,56 @@ code {
1832
1832
 
1833
1833
  [data-streamdown="code-block-header"] {
1834
1834
  position: absolute !important;
1835
- top: 14px !important;
1836
- right: 8px !important;
1835
+ top: 10px !important;
1836
+ left: 12px !important;
1837
+ right: auto !important;
1837
1838
  z-index: 10;
1838
1839
  padding: 0 !important;
1839
1840
  background: transparent !important;
1840
1841
  border: none !important;
1841
1842
  opacity: 0;
1842
1843
  transition: opacity 150ms ease;
1844
+ pointer-events: none;
1845
+ }
1846
+
1847
+ [data-streamdown="code-block-actions"] {
1848
+ position: absolute !important;
1849
+ top: 10px !important;
1850
+ right: 8px !important;
1851
+ z-index: 11;
1852
+ display: flex !important;
1853
+ align-items: center;
1854
+ gap: 4px;
1855
+ padding: 0 !important;
1856
+ background: transparent !important;
1857
+ border: none !important;
1858
+ opacity: 0;
1859
+ transition: opacity 150ms ease;
1860
+ pointer-events: auto !important;
1843
1861
  }
1844
1862
 
1845
- [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"] {
1863
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"],
1864
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-actions"] {
1846
1865
  opacity: 1;
1847
1866
  }
1848
1867
 
1849
1868
  [data-streamdown="code-block-header"] > span:first-child {
1869
+ display: inline-flex;
1850
1870
  font-size: 11px;
1851
1871
  color: var(--color-muted);
1852
1872
  text-transform: lowercase;
1853
- margin-right: 8px;
1854
1873
  }
1855
1874
 
1856
1875
  [data-streamdown="code-block-header"] > div {
1857
1876
  display: flex;
1858
1877
  gap: 4px;
1878
+ margin-left: 8px;
1879
+ pointer-events: auto;
1859
1880
  }
1860
1881
 
1861
- [data-streamdown="code-block-header"] button {
1882
+ /* Normalize action buttons for both streamdown v1 (header>div) and v2 (code-block-actions). */
1883
+ [data-streamdown="code-block-header"] button,
1884
+ [data-streamdown="code-block-actions"] button {
1862
1885
  padding: 4px !important;
1863
1886
  border: none !important;
1864
1887
  border-radius: 4px !important;
@@ -1867,18 +1890,21 @@ code {
1867
1890
  cursor: pointer;
1868
1891
  }
1869
1892
 
1870
- [data-streamdown="code-block-header"] button:hover {
1893
+ [data-streamdown="code-block-header"] button:hover,
1894
+ [data-streamdown="code-block-actions"] button:hover {
1871
1895
  opacity: 1;
1872
1896
  background: var(--color-border) !important;
1873
1897
  }
1874
1898
 
1875
- [data-streamdown="code-block-header"] svg {
1899
+ [data-streamdown="code-block-header"] svg,
1900
+ [data-streamdown="code-block-actions"] svg {
1876
1901
  color: var(--color-muted) !important;
1877
1902
  width: 12px !important;
1878
1903
  height: 12px !important;
1879
1904
  }
1880
1905
 
1881
- [data-streamdown="code-block-header"] button:hover svg {
1906
+ [data-streamdown="code-block-header"] button:hover svg,
1907
+ [data-streamdown="code-block-actions"] button:hover svg {
1882
1908
  color: var(--color-text) !important;
1883
1909
  }
1884
1910
 
@@ -6879,9 +6905,8 @@ var useChatHeader = (config) => {
6879
6905
  }
6880
6906
  if (typeof config?.alwaysOnTop === "boolean") {
6881
6907
  setIsPinned(config.alwaysOnTop);
6882
- resolvedOnPin?.(config.alwaysOnTop);
6883
6908
  }
6884
- }, [config?.alwaysOnTop, resolvedOnPin]);
6909
+ }, [config?.alwaysOnTop]);
6885
6910
  const showPin = !!resolvedOnPin || typeof config?.alwaysOnTop === "boolean";
6886
6911
  const showClose = !!resolvedOnClose;
6887
6912
  const logoNode = useMemo4(() => resolveLogoNode(config?.logo, "header"), [config?.logo]);
@@ -10180,7 +10205,9 @@ var HistoryList = memo16(function HistoryList2({
10180
10205
  onDelete,
10181
10206
  onLoadMore,
10182
10207
  isDarkMode = false,
10183
- strings = {}
10208
+ strings = {},
10209
+ isConversationHighlighted,
10210
+ highlightedLabel
10184
10211
  }) {
10185
10212
  const [hoveredId, setHoveredId] = useState20(null);
10186
10213
  const loadMoreRef = useRef16(null);
@@ -10236,6 +10263,8 @@ var HistoryList = memo16(function HistoryList2({
10236
10263
  conversations.map((conv) => {
10237
10264
  const isSelected = conv.id === selectedId;
10238
10265
  const isHovered = conv.id === hoveredId;
10266
+ const isHighlighted = isConversationHighlighted?.(conv) === true;
10267
+ const resolvedHighlightLabel = isHighlighted ? (typeof highlightedLabel === "function" ? highlightedLabel(conv) : highlightedLabel) ?? "RELATED" : null;
10239
10268
  return /* @__PURE__ */ jsxs17(
10240
10269
  "div",
10241
10270
  {
@@ -10247,21 +10276,42 @@ var HistoryList = memo16(function HistoryList2({
10247
10276
  padding: "0.5rem 0.75rem",
10248
10277
  textAlign: "left",
10249
10278
  cursor: "pointer",
10250
- transition: "background-color 0.15s ease",
10251
- background: isSelected || isHovered ? colors.hover : "transparent"
10279
+ transition: "background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease",
10280
+ border: isSelected ? "1px solid var(--chat-border-focus)" : "1px solid transparent",
10281
+ background: isSelected || isHovered ? colors.hover : isHighlighted ? "color-mix(in srgb, var(--chat-accent) 6%, transparent)" : "transparent",
10282
+ boxShadow: isSelected ? "inset 2px 0 0 var(--chat-accent)" : "none"
10252
10283
  },
10253
10284
  onClick: () => onSelect(conv.id),
10254
10285
  onMouseEnter: () => setHoveredId(conv.id),
10255
10286
  onMouseLeave: () => setHoveredId(null),
10256
10287
  children: [
10257
10288
  /* @__PURE__ */ jsxs17("div", { style: { minWidth: 0, flex: 1 }, children: [
10258
- /* @__PURE__ */ jsx23("div", { style: {
10259
- fontSize: "0.875rem",
10260
- color: colors.text,
10261
- overflow: "hidden",
10262
- textOverflow: "ellipsis",
10263
- whiteSpace: "nowrap"
10264
- }, children: conv.title || "Untitled" }),
10289
+ /* @__PURE__ */ jsxs17("div", { style: { display: "flex", alignItems: "center", gap: "0.375rem", minWidth: 0 }, children: [
10290
+ /* @__PURE__ */ jsx23("div", { style: {
10291
+ fontSize: "0.875rem",
10292
+ color: colors.text,
10293
+ overflow: "hidden",
10294
+ textOverflow: "ellipsis",
10295
+ whiteSpace: "nowrap"
10296
+ }, children: conv.title || "Untitled" }),
10297
+ resolvedHighlightLabel && /* @__PURE__ */ jsx23(
10298
+ "span",
10299
+ {
10300
+ style: {
10301
+ fontSize: "0.625rem",
10302
+ lineHeight: 1,
10303
+ padding: "0.18rem 0.32rem",
10304
+ borderRadius: 999,
10305
+ border: "1px solid color-mix(in srgb, var(--chat-accent) 30%, transparent)",
10306
+ background: "color-mix(in srgb, var(--chat-accent) 12%, transparent)",
10307
+ color: "color-mix(in srgb, var(--chat-accent) 85%, var(--chat-text) 15%)",
10308
+ flexShrink: 0,
10309
+ fontWeight: 500
10310
+ },
10311
+ children: resolvedHighlightLabel
10312
+ }
10313
+ )
10314
+ ] }),
10265
10315
  /* @__PURE__ */ jsx23("div", { style: { fontSize: "0.75rem", color: colors.muted }, children: formatRelativeTime(conv.updatedAt, strings) })
10266
10316
  ] }),
10267
10317
  isHovered && /* @__PURE__ */ jsx23(
@@ -10297,39 +10347,50 @@ var HistoryModal = memo17(function HistoryModal2({
10297
10347
  isDarkMode = false
10298
10348
  }) {
10299
10349
  const modalRef = useRef17(null);
10350
+ const closeTimerRef = useRef17(null);
10300
10351
  const [isClosing, setIsClosing] = useState21(false);
10301
10352
  const [shouldRender, setShouldRender] = useState21(isOpen);
10353
+ const clearCloseTimer = useCallback21(() => {
10354
+ if (closeTimerRef.current === null) return;
10355
+ clearTimeout(closeTimerRef.current);
10356
+ closeTimerRef.current = null;
10357
+ }, []);
10302
10358
  useEffect25(() => {
10303
10359
  if (isOpen) {
10360
+ clearCloseTimer();
10304
10361
  setShouldRender(true);
10305
10362
  setIsClosing(false);
10306
- } else if (shouldRender && !isClosing) {
10307
- setIsClosing(true);
10308
- setTimeout(() => {
10309
- setShouldRender(false);
10310
- setIsClosing(false);
10311
- }, ANIMATION_DURATION);
10363
+ return;
10364
+ }
10365
+ if (!shouldRender) {
10366
+ setIsClosing(false);
10367
+ return;
10312
10368
  }
10313
- }, [isOpen, shouldRender, isClosing]);
10314
- const handleClose = useCallback21(() => {
10315
10369
  setIsClosing(true);
10316
- setTimeout(() => {
10370
+ clearCloseTimer();
10371
+ closeTimerRef.current = setTimeout(() => {
10317
10372
  setShouldRender(false);
10318
10373
  setIsClosing(false);
10319
- onClose();
10374
+ closeTimerRef.current = null;
10320
10375
  }, ANIMATION_DURATION);
10321
- }, [onClose]);
10376
+ return clearCloseTimer;
10377
+ }, [isOpen, shouldRender, clearCloseTimer]);
10378
+ useEffect25(() => clearCloseTimer, [clearCloseTimer]);
10379
+ const handleCloseRequest = useCallback21(() => {
10380
+ if (!isOpen || isClosing) return;
10381
+ onClose();
10382
+ }, [isOpen, isClosing, onClose]);
10322
10383
  useEffect25(() => {
10323
10384
  if (!shouldRender || isClosing) return;
10324
10385
  const handleKeyDown = (e) => {
10325
10386
  if (e.key === "Escape") {
10326
10387
  e.preventDefault();
10327
- handleClose();
10388
+ handleCloseRequest();
10328
10389
  }
10329
10390
  };
10330
10391
  document.addEventListener("keydown", handleKeyDown);
10331
10392
  return () => document.removeEventListener("keydown", handleKeyDown);
10332
- }, [shouldRender, isClosing, handleClose]);
10393
+ }, [shouldRender, isClosing, handleCloseRequest]);
10333
10394
  useEffect25(() => {
10334
10395
  if (!isOpen || !modalRef.current) return;
10335
10396
  modalRef.current.focus();
@@ -10337,11 +10398,12 @@ var HistoryModal = memo17(function HistoryModal2({
10337
10398
  const handleBackdropClick = useCallback21(
10338
10399
  (e) => {
10339
10400
  if (e.target === e.currentTarget && !isClosing) {
10340
- handleClose();
10401
+ handleCloseRequest();
10341
10402
  }
10342
10403
  },
10343
- [handleClose, isClosing]
10404
+ [handleCloseRequest, isClosing]
10344
10405
  );
10406
+ useWindowDragLock(shouldRender);
10345
10407
  if (!shouldRender) return null;
10346
10408
  const closingClass = isClosing ? " chat-modal-closing" : "";
10347
10409
  return /* @__PURE__ */ jsx24(
@@ -10410,7 +10472,7 @@ var HistoryModal = memo17(function HistoryModal2({
10410
10472
  /* @__PURE__ */ jsx24(
10411
10473
  "button",
10412
10474
  {
10413
- onClick: handleClose,
10475
+ onClick: handleCloseRequest,
10414
10476
  "aria-label": closeLabel,
10415
10477
  style: {
10416
10478
  display: "flex",
@@ -10522,6 +10584,7 @@ var CompactChat = memo18(function CompactChat2({
10522
10584
  onError,
10523
10585
  onMessageReceived,
10524
10586
  onLoadingChange,
10587
+ onConversationChange,
10525
10588
  onStateChange,
10526
10589
  headerLeft,
10527
10590
  headerRight,
@@ -10530,8 +10593,13 @@ var CompactChat = memo18(function CompactChat2({
10530
10593
  inputPortalContainer,
10531
10594
  sendMessageRef,
10532
10595
  newConversationRef,
10596
+ loadConversationRef,
10597
+ stopStreamingRef,
10533
10598
  focusInputRef: parentFocusInputRef,
10534
10599
  setTextRef: parentSetTextRef,
10600
+ controllerRef,
10601
+ onConversationDeleted,
10602
+ historyConfig,
10535
10603
  renderMessage,
10536
10604
  emptyState,
10537
10605
  className = "",
@@ -10573,7 +10641,6 @@ var CompactChat = memo18(function CompactChat2({
10573
10641
  const chatContainerRef = useRef18(null);
10574
10642
  const chatInputRef = useRef18(null);
10575
10643
  const [showHistory, setShowHistory] = useState22(false);
10576
- useWindowDragLock(showHistory);
10577
10644
  const [connectionAlert, setConnectionAlert] = useState22(null);
10578
10645
  const portalContainerRef = useRef18(inputPortalContainer ?? null);
10579
10646
  portalContainerRef.current = inputPortalContainer ?? null;
@@ -10602,7 +10669,8 @@ var CompactChat = memo18(function CompactChat2({
10602
10669
  });
10603
10670
  const chat = useChat({
10604
10671
  adapter,
10605
- onError
10672
+ onError,
10673
+ onConversationChange
10606
10674
  });
10607
10675
  useFocusPersistence({
10608
10676
  containerRef: chatContainerRef,
@@ -10646,6 +10714,16 @@ var CompactChat = memo18(function CompactChat2({
10646
10714
  newConversationRef.current = chat.newConversation;
10647
10715
  }
10648
10716
  }, [newConversationRef, chat.newConversation]);
10717
+ useEffect26(() => {
10718
+ if (loadConversationRef) {
10719
+ loadConversationRef.current = chat.loadConversation;
10720
+ }
10721
+ }, [loadConversationRef, chat.loadConversation]);
10722
+ useEffect26(() => {
10723
+ if (stopStreamingRef) {
10724
+ stopStreamingRef.current = chat.stopStreaming;
10725
+ }
10726
+ }, [stopStreamingRef, chat.stopStreaming]);
10649
10727
  useEffect26(() => {
10650
10728
  if (parentFocusInputRef) {
10651
10729
  parentFocusInputRef.current = () => chatInputRef.current?.focus();
@@ -10656,6 +10734,63 @@ var CompactChat = memo18(function CompactChat2({
10656
10734
  parentSetTextRef.current = (text) => chatInputRef.current?.setValue(text);
10657
10735
  }
10658
10736
  }, [parentSetTextRef]);
10737
+ const getStateSnapshot = useCallback22(() => ({
10738
+ messages: chat.messages,
10739
+ isLoading: chat.isLoading,
10740
+ isStreaming: chat.isStreaming,
10741
+ error: chat.error,
10742
+ conversationId: chat.conversationId,
10743
+ conversationTitle: chat.conversationTitle,
10744
+ capabilities: chat.capabilities
10745
+ }), [
10746
+ chat.messages,
10747
+ chat.isLoading,
10748
+ chat.isStreaming,
10749
+ chat.error,
10750
+ chat.conversationId,
10751
+ chat.conversationTitle,
10752
+ chat.capabilities
10753
+ ]);
10754
+ useEffect26(() => {
10755
+ if (!controllerRef) return;
10756
+ controllerRef.current = {
10757
+ sendMessage: chat.sendMessage,
10758
+ trySendMessage: chat.trySendMessage,
10759
+ newConversation: chat.newConversation,
10760
+ loadConversation: chat.loadConversation,
10761
+ stopStreaming: chat.stopStreaming,
10762
+ focusInput: () => chatInputRef.current?.focus(),
10763
+ setInputText: (text) => chatInputRef.current?.setValue(text),
10764
+ getState: getStateSnapshot
10765
+ };
10766
+ }, [
10767
+ controllerRef,
10768
+ chat.sendMessage,
10769
+ chat.trySendMessage,
10770
+ chat.newConversation,
10771
+ chat.loadConversation,
10772
+ chat.stopStreaming,
10773
+ getStateSnapshot
10774
+ ]);
10775
+ useEffect26(() => {
10776
+ return () => {
10777
+ if (sendMessageRef) sendMessageRef.current = null;
10778
+ if (newConversationRef) newConversationRef.current = null;
10779
+ if (loadConversationRef) loadConversationRef.current = null;
10780
+ if (stopStreamingRef) stopStreamingRef.current = null;
10781
+ if (parentFocusInputRef) parentFocusInputRef.current = null;
10782
+ if (parentSetTextRef) parentSetTextRef.current = null;
10783
+ if (controllerRef) controllerRef.current = null;
10784
+ };
10785
+ }, [
10786
+ sendMessageRef,
10787
+ newConversationRef,
10788
+ loadConversationRef,
10789
+ stopStreamingRef,
10790
+ parentFocusInputRef,
10791
+ parentSetTextRef,
10792
+ controllerRef
10793
+ ]);
10659
10794
  useEffect26(() => {
10660
10795
  if (!adapter.onFocusInput) return;
10661
10796
  return adapter.onFocusInput(() => {
@@ -10677,12 +10812,9 @@ var CompactChat = memo18(function CompactChat2({
10677
10812
  }, [chat.isLoading, onLoadingChange]);
10678
10813
  useEffect26(() => {
10679
10814
  if (onStateChange) {
10680
- onStateChange({
10681
- messages: chat.messages,
10682
- conversationId: chat.conversationId
10683
- });
10815
+ onStateChange(getStateSnapshot());
10684
10816
  }
10685
- }, [chat.messages, chat.conversationId, onStateChange]);
10817
+ }, [getStateSnapshot, onStateChange]);
10686
10818
  useEffect26(() => {
10687
10819
  if (connection.isConnected) {
10688
10820
  resourcePicker.refreshProviders();
@@ -10710,11 +10842,12 @@ var CompactChat = memo18(function CompactChat2({
10710
10842
  const handleDeleteConversation = useCallback22(
10711
10843
  async (id) => {
10712
10844
  await conversations.deleteConversation(id);
10845
+ onConversationDeleted?.(id);
10713
10846
  if (id === chat.conversationId) {
10714
10847
  chat.newConversation();
10715
10848
  }
10716
10849
  },
10717
- [conversations, chat]
10850
+ [conversations, chat, onConversationDeleted]
10718
10851
  );
10719
10852
  const handleNewChat = useCallback22(() => {
10720
10853
  chat.newConversation();
@@ -10975,6 +11108,8 @@ var CompactChat = memo18(function CompactChat2({
10975
11108
  onDelete: handleDeleteConversation,
10976
11109
  onLoadMore: conversations.loadMore,
10977
11110
  isDarkMode: resolvedIsDarkMode,
11111
+ isConversationHighlighted: historyConfig?.isConversationHighlighted,
11112
+ highlightedLabel: historyConfig?.highlightedLabel,
10978
11113
  strings: {
10979
11114
  noHistory: mergedStrings.noHistory,
10980
11115
  loadMore: mergedStrings.loadMore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yushaw/sanqian-chat",
3
- "version": "0.2.39",
3
+ "version": "0.2.41",
4
4
  "description": "Floating chat window SDK for Sanqian AI Assistant",
5
5
  "main": "./dist/main/index.js",
6
6
  "types": "./dist/main/index.d.ts",
@@ -413,33 +413,56 @@ code {
413
413
 
414
414
  [data-streamdown="code-block-header"] {
415
415
  position: absolute !important;
416
- top: 14px !important;
417
- right: 8px !important;
416
+ top: 10px !important;
417
+ left: 12px !important;
418
+ right: auto !important;
418
419
  z-index: 10;
419
420
  padding: 0 !important;
420
421
  background: transparent !important;
421
422
  border: none !important;
422
423
  opacity: 0;
423
424
  transition: opacity 150ms ease;
425
+ pointer-events: none;
426
+ }
427
+
428
+ [data-streamdown="code-block-actions"] {
429
+ position: absolute !important;
430
+ top: 10px !important;
431
+ right: 8px !important;
432
+ z-index: 11;
433
+ display: flex !important;
434
+ align-items: center;
435
+ gap: 4px;
436
+ padding: 0 !important;
437
+ background: transparent !important;
438
+ border: none !important;
439
+ opacity: 0;
440
+ transition: opacity 150ms ease;
441
+ pointer-events: auto !important;
424
442
  }
425
443
 
426
- [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"] {
444
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"],
445
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-actions"] {
427
446
  opacity: 1;
428
447
  }
429
448
 
430
449
  [data-streamdown="code-block-header"] > span:first-child {
450
+ display: inline-flex;
431
451
  font-size: 11px;
432
452
  color: var(--color-muted);
433
453
  text-transform: lowercase;
434
- margin-right: 8px;
435
454
  }
436
455
 
437
456
  [data-streamdown="code-block-header"] > div {
438
457
  display: flex;
439
458
  gap: 4px;
459
+ margin-left: 8px;
460
+ pointer-events: auto;
440
461
  }
441
462
 
442
- [data-streamdown="code-block-header"] button {
463
+ /* Normalize action buttons for both streamdown v1 (header>div) and v2 (code-block-actions). */
464
+ [data-streamdown="code-block-header"] button,
465
+ [data-streamdown="code-block-actions"] button {
443
466
  padding: 4px !important;
444
467
  border: none !important;
445
468
  border-radius: 4px !important;
@@ -448,18 +471,21 @@ code {
448
471
  cursor: pointer;
449
472
  }
450
473
 
451
- [data-streamdown="code-block-header"] button:hover {
474
+ [data-streamdown="code-block-header"] button:hover,
475
+ [data-streamdown="code-block-actions"] button:hover {
452
476
  opacity: 1;
453
477
  background: var(--color-border) !important;
454
478
  }
455
479
 
456
- [data-streamdown="code-block-header"] svg {
480
+ [data-streamdown="code-block-header"] svg,
481
+ [data-streamdown="code-block-actions"] svg {
457
482
  color: var(--color-muted) !important;
458
483
  width: 12px !important;
459
484
  height: 12px !important;
460
485
  }
461
486
 
462
- [data-streamdown="code-block-header"] button:hover svg {
487
+ [data-streamdown="code-block-header"] button:hover svg,
488
+ [data-streamdown="code-block-actions"] button:hover svg {
463
489
  color: var(--color-text) !important;
464
490
  }
465
491
 
@@ -420,33 +420,56 @@ code {
420
420
 
421
421
  [data-streamdown="code-block-header"] {
422
422
  position: absolute !important;
423
- top: 14px !important;
424
- right: 8px !important;
423
+ top: 10px !important;
424
+ left: 12px !important;
425
+ right: auto !important;
425
426
  z-index: 10;
426
427
  padding: 0 !important;
427
428
  background: transparent !important;
428
429
  border: none !important;
429
430
  opacity: 0;
430
431
  transition: opacity 150ms ease;
432
+ pointer-events: none;
433
+ }
434
+
435
+ [data-streamdown="code-block-actions"] {
436
+ position: absolute !important;
437
+ top: 10px !important;
438
+ right: 8px !important;
439
+ z-index: 11;
440
+ display: flex !important;
441
+ align-items: center;
442
+ gap: 4px;
443
+ padding: 0 !important;
444
+ background: transparent !important;
445
+ border: none !important;
446
+ opacity: 0;
447
+ transition: opacity 150ms ease;
448
+ pointer-events: auto !important;
431
449
  }
432
450
 
433
- [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"] {
451
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"],
452
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-actions"] {
434
453
  opacity: 1;
435
454
  }
436
455
 
437
456
  [data-streamdown="code-block-header"] > span:first-child {
457
+ display: inline-flex;
438
458
  font-size: 11px;
439
459
  color: var(--color-muted);
440
460
  text-transform: lowercase;
441
- margin-right: 8px;
442
461
  }
443
462
 
444
463
  [data-streamdown="code-block-header"] > div {
445
464
  display: flex;
446
465
  gap: 4px;
466
+ margin-left: 8px;
467
+ pointer-events: auto;
447
468
  }
448
469
 
449
- [data-streamdown="code-block-header"] button {
470
+ /* Normalize action buttons for both streamdown v1 (header>div) and v2 (code-block-actions). */
471
+ [data-streamdown="code-block-header"] button,
472
+ [data-streamdown="code-block-actions"] button {
450
473
  padding: 4px !important;
451
474
  border: none !important;
452
475
  border-radius: 4px !important;
@@ -455,18 +478,21 @@ code {
455
478
  cursor: pointer;
456
479
  }
457
480
 
458
- [data-streamdown="code-block-header"] button:hover {
481
+ [data-streamdown="code-block-header"] button:hover,
482
+ [data-streamdown="code-block-actions"] button:hover {
459
483
  opacity: 1;
460
484
  background: var(--color-border) !important;
461
485
  }
462
486
 
463
- [data-streamdown="code-block-header"] svg {
487
+ [data-streamdown="code-block-header"] svg,
488
+ [data-streamdown="code-block-actions"] svg {
464
489
  color: var(--color-muted) !important;
465
490
  width: 12px !important;
466
491
  height: 12px !important;
467
492
  }
468
493
 
469
- [data-streamdown="code-block-header"] button:hover svg {
494
+ [data-streamdown="code-block-header"] button:hover svg,
495
+ [data-streamdown="code-block-actions"] button:hover svg {
470
496
  color: var(--color-text) !important;
471
497
  }
472
498
 
@@ -413,33 +413,56 @@ code {
413
413
 
414
414
  [data-streamdown="code-block-header"] {
415
415
  position: absolute !important;
416
- top: 14px !important;
417
- right: 8px !important;
416
+ top: 10px !important;
417
+ left: 12px !important;
418
+ right: auto !important;
418
419
  z-index: 10;
419
420
  padding: 0 !important;
420
421
  background: transparent !important;
421
422
  border: none !important;
422
423
  opacity: 0;
423
424
  transition: opacity 150ms ease;
425
+ pointer-events: none;
426
+ }
427
+
428
+ [data-streamdown="code-block-actions"] {
429
+ position: absolute !important;
430
+ top: 10px !important;
431
+ right: 8px !important;
432
+ z-index: 11;
433
+ display: flex !important;
434
+ align-items: center;
435
+ gap: 4px;
436
+ padding: 0 !important;
437
+ background: transparent !important;
438
+ border: none !important;
439
+ opacity: 0;
440
+ transition: opacity 150ms ease;
441
+ pointer-events: auto !important;
424
442
  }
425
443
 
426
- [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"] {
444
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-header"],
445
+ [data-streamdown="code-block"]:hover [data-streamdown="code-block-actions"] {
427
446
  opacity: 1;
428
447
  }
429
448
 
430
449
  [data-streamdown="code-block-header"] > span:first-child {
450
+ display: inline-flex;
431
451
  font-size: 11px;
432
452
  color: var(--color-muted);
433
453
  text-transform: lowercase;
434
- margin-right: 8px;
435
454
  }
436
455
 
437
456
  [data-streamdown="code-block-header"] > div {
438
457
  display: flex;
439
458
  gap: 4px;
459
+ margin-left: 8px;
460
+ pointer-events: auto;
440
461
  }
441
462
 
442
- [data-streamdown="code-block-header"] button {
463
+ /* Normalize action buttons for both streamdown v1 (header>div) and v2 (code-block-actions). */
464
+ [data-streamdown="code-block-header"] button,
465
+ [data-streamdown="code-block-actions"] button {
443
466
  padding: 4px !important;
444
467
  border: none !important;
445
468
  border-radius: 4px !important;
@@ -448,18 +471,21 @@ code {
448
471
  cursor: pointer;
449
472
  }
450
473
 
451
- [data-streamdown="code-block-header"] button:hover {
474
+ [data-streamdown="code-block-header"] button:hover,
475
+ [data-streamdown="code-block-actions"] button:hover {
452
476
  opacity: 1;
453
477
  background: var(--color-border) !important;
454
478
  }
455
479
 
456
- [data-streamdown="code-block-header"] svg {
480
+ [data-streamdown="code-block-header"] svg,
481
+ [data-streamdown="code-block-actions"] svg {
457
482
  color: var(--color-muted) !important;
458
483
  width: 12px !important;
459
484
  height: 12px !important;
460
485
  }
461
486
 
462
- [data-streamdown="code-block-header"] button:hover svg {
487
+ [data-streamdown="code-block-header"] button:hover svg,
488
+ [data-streamdown="code-block-actions"] button:hover svg {
463
489
  color: var(--color-text) !important;
464
490
  }
465
491