@gravity-ui/aikit 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +4 -0
  2. package/dist/components/atoms/ContextItem/index.d.ts +1 -1
  3. package/dist/components/atoms/ContextItem/index.js +1 -1
  4. package/dist/components/atoms/IntersectionContainer/IntersectionContainer.d.ts +10 -0
  5. package/dist/components/atoms/IntersectionContainer/IntersectionContainer.js +14 -0
  6. package/dist/components/atoms/IntersectionContainer/IntersectionContainer.scss +7 -0
  7. package/dist/components/atoms/IntersectionContainer/index.d.ts +1 -0
  8. package/dist/components/atoms/IntersectionContainer/index.js +1 -0
  9. package/dist/components/atoms/Shimmer/Shimmer.scss +6 -6
  10. package/dist/components/atoms/index.d.ts +1 -0
  11. package/dist/components/atoms/index.js +1 -0
  12. package/dist/components/molecules/PromptInputHeader/PromptInputHeader.d.ts +1 -1
  13. package/dist/components/organisms/AssistantMessage/defaultMessageTypeRegistry.js +1 -1
  14. package/dist/components/organisms/Header/Header.js +3 -2
  15. package/dist/components/organisms/Header/types.d.ts +1 -0
  16. package/dist/components/organisms/Header/useHeader.d.ts +1 -0
  17. package/dist/components/organisms/Header/useHeader.js +2 -1
  18. package/dist/components/organisms/MessageList/MessageList.d.ts +5 -1
  19. package/dist/components/organisms/MessageList/MessageList.js +14 -5
  20. package/dist/components/organisms/MessageList/MessageList.scss +6 -1
  21. package/dist/components/organisms/PromptInput/PromptInput.d.ts +2 -0
  22. package/dist/components/organisms/PromptInput/PromptInput.js +2 -1
  23. package/dist/components/organisms/PromptInput/usePromptInput.d.ts +0 -2
  24. package/dist/components/organisms/PromptInput/usePromptInput.js +10 -16
  25. package/dist/components/organisms/ThinkingMessage/index.d.ts +2 -9
  26. package/dist/components/organisms/ThinkingMessage/useThinkingMessage.d.ts +2 -12
  27. package/dist/components/organisms/ThinkingMessage/useThinkingMessage.js +1 -2
  28. package/dist/components/pages/ChatContainer/ChatContainer.js +6 -3
  29. package/dist/components/pages/ChatContainer/ChatContainer.scss +4 -4
  30. package/dist/components/pages/ChatContainer/index.d.ts +1 -1
  31. package/dist/components/pages/ChatContainer/types.d.ts +17 -1
  32. package/dist/components/templates/ChatContent/ChatContent.js +1 -1
  33. package/dist/components/templates/ChatContent/ChatContent.scss +4 -1
  34. package/dist/components/templates/EmptyContainer/EmptyContainer.d.ts +2 -0
  35. package/dist/components/templates/EmptyContainer/EmptyContainer.js +2 -2
  36. package/dist/components/templates/History/History.d.ts +2 -0
  37. package/dist/components/templates/History/History.js +2 -2
  38. package/dist/components/templates/History/History.scss +8 -0
  39. package/dist/components/templates/History/HistoryList.d.ts +2 -0
  40. package/dist/components/templates/History/HistoryList.js +3 -2
  41. package/dist/hooks/index.d.ts +1 -0
  42. package/dist/hooks/index.js +1 -0
  43. package/dist/hooks/useScrollPreservation.d.ts +9 -0
  44. package/dist/hooks/useScrollPreservation.js +28 -0
  45. package/dist/themes/dark.css +3 -0
  46. package/dist/types/messages.d.ts +5 -2
  47. package/package.json +1 -1
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @gravity-ui/aikit
2
2
 
3
+ [![npm package](https://img.shields.io/npm/v/@gravity-ui/aikit?logo=npm)](https://www.npmjs.com/package/@gravity-ui/aikit) [![CI](https://img.shields.io/github/actions/workflow/status/gravity-ui/aikit/.github/workflows/ci.yml?branch=main&label=CI&logo=github)](https://github.com/gravity-ui/aikit/actions/workflows/ci.yml?query=branch:main) [![storybook](https://img.shields.io/badge/Storybook-deployed-ff4685?logo=storybook)](https://preview.gravity-ui.com/aikit/?path=/docs/pages-chatcontainer--docs)
4
+
5
+ ---
6
+
3
7
  UI component library for AI chats built with Atomic Design principles.
4
8
 
5
9
  ## Description
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { QAProps } from '@gravity-ui/uikit';
3
3
  type ContextItemProps = QAProps & {
4
4
  content: React.ReactNode;
5
- onClick: () => void;
5
+ onClick?: () => void;
6
6
  className?: string;
7
7
  };
8
8
  export declare const ContextItem: (props: ContextItemProps) => import("react/jsx-runtime").JSX.Element;
@@ -4,5 +4,5 @@ import { block } from '../../../utils/cn';
4
4
  const b = block('context-item');
5
5
  export const ContextItem = (props) => {
6
6
  const { content, onClick, className, qa } = props;
7
- return (_jsx(Label, { size: "s", theme: "clear", onCloseClick: onClick, type: "close", className: b(null, className), "data-qa": qa, children: content }));
7
+ return (_jsx(Label, Object.assign({ size: "s", theme: "clear" }, (onClick && { onCloseClick: onClick, type: 'close' }), { className: b(null, className), "data-qa": qa, children: content })));
8
8
  };
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import './IntersectionContainer.scss';
3
+ interface IIntersectionContainerProps {
4
+ children?: React.ReactNode;
5
+ onIntersect?: () => void;
6
+ options?: IntersectionObserverInit;
7
+ className?: string;
8
+ }
9
+ export declare const IntersectionContainer: ({ children, onIntersect, options, className, }: IIntersectionContainerProps) => string | number | boolean | Iterable<React.ReactNode> | import("react/jsx-runtime").JSX.Element | null | undefined;
10
+ export {};
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { useIntersection } from '@gravity-ui/uikit';
4
+ import { block } from '../../../utils/cn';
5
+ import './IntersectionContainer.scss';
6
+ const b = block('intersection-container');
7
+ export const IntersectionContainer = ({ children, onIntersect, options, className, }) => {
8
+ const [ref, setRef] = React.useState(null);
9
+ useIntersection({ element: ref, onIntersect, options });
10
+ if (onIntersect) {
11
+ return (_jsx("div", { className: b('container', className), ref: setRef, children: children }));
12
+ }
13
+ return children;
14
+ };
@@ -0,0 +1,7 @@
1
+ @use '../../../styles/variables';
2
+
3
+ $block: '.#{variables.$ns}intersection-container';
4
+
5
+ #{$block} {
6
+ width: 100%;
7
+ }
@@ -0,0 +1 @@
1
+ export * from './IntersectionContainer';
@@ -0,0 +1 @@
1
+ export * from './IntersectionContainer';
@@ -7,7 +7,8 @@ $block: '.#{variables.$ns}shimmer';
7
7
  position: relative;
8
8
  display: inline-block;
9
9
 
10
- background: linear-gradient(
10
+ color: var(--g-aikit-text-primary, inherit);
11
+ mask-image: linear-gradient(
11
12
  90deg,
12
13
  var(--g-aikit-shimmer-color-from) 0%,
13
14
  var(--g-aikit-shimmer-color-from) 40%,
@@ -15,18 +16,17 @@ $block: '.#{variables.$ns}shimmer';
15
16
  var(--g-aikit-shimmer-color-from) 60%,
16
17
  var(--g-aikit-shimmer-color-from) 100%
17
18
  );
18
- background-size: var(--g-aikit-shimmer-gradient-size) 100%;
19
- background-clip: text;
20
- -webkit-text-fill-color: transparent;
19
+ mask-size: var(--g-aikit-shimmer-gradient-size) 100%;
20
+ mask-clip: text;
21
21
  animation: shimmer var(--g-aikit-shimmer-duration) infinite linear;
22
22
  }
23
23
  }
24
24
 
25
25
  @keyframes shimmer {
26
26
  0% {
27
- background-position: 200% 50%;
27
+ mask-position: 200% 50%;
28
28
  }
29
29
  100% {
30
- background-position: 0% 50%;
30
+ mask-position: 0% 50%;
31
31
  }
32
32
  }
@@ -6,6 +6,7 @@ export * from './ContextItem';
6
6
  export * from './DiffStat';
7
7
  export * from './Disclaimer';
8
8
  export * from './InlineCitation';
9
+ export * from './IntersectionContainer';
9
10
  export * from './Loader';
10
11
  export * from './MarkdownRenderer';
11
12
  export * from './MessageBalloon';
@@ -7,6 +7,7 @@ export * from './ContextItem';
7
7
  export * from './DiffStat';
8
8
  export * from './Disclaimer';
9
9
  export * from './InlineCitation';
10
+ export * from './IntersectionContainer';
10
11
  export * from './Loader';
11
12
  export * from './MarkdownRenderer';
12
13
  export * from './MessageBalloon';
@@ -10,7 +10,7 @@ export type ContextItemConfig = {
10
10
  /** Content to display in the context item */
11
11
  content: ReactNode;
12
12
  /** Callback when context item is removed */
13
- onRemove: () => void;
13
+ onRemove?: () => void;
14
14
  };
15
15
  /**
16
16
  * Props for the PromptInputHeader component
@@ -12,7 +12,7 @@ export function createDefaultMessageRegistry(transformOptions) {
12
12
  component: ({ part }) => _jsx(ToolMessage, Object.assign({}, part.data)),
13
13
  });
14
14
  registerMessageRenderer(registry, 'thinking', {
15
- component: ({ part }) => _jsx(ThinkingMessage, { data: part.data }),
15
+ component: ({ part }) => _jsx(ThinkingMessage, Object.assign({}, part.data)),
16
16
  });
17
17
  return registry;
18
18
  }
@@ -29,7 +29,7 @@ const FOLDING_ICONS = {
29
29
  * @returns Header component
30
30
  */
31
31
  export function Header(props) {
32
- const { title, preview, icon, baseActions, additionalActions, titlePosition, className, historyButtonRef, } = useHeader(props);
32
+ const { title, preview, icon, baseActions, additionalActions, titlePosition, withIcon, className, historyButtonRef, } = useHeader(props);
33
33
  // Render base action
34
34
  const renderBaseAction = (action) => {
35
35
  let IconComponent = ACTION_ICONS[action.id];
@@ -61,5 +61,6 @@ export function Header(props) {
61
61
  };
62
62
  // Determine class for title positioning
63
63
  const titlePositionClass = b('title-container', { position: titlePosition });
64
- return (_jsxs("div", { className: b('', className), children: [icon ? _jsx("div", { className: b('icon'), children: icon }) : _jsx(Icon, { data: Sparkles, size: 16 }), _jsxs("div", { className: titlePositionClass, children: [title && (_jsx(Text, { as: "div", variant: "subheader-2", className: b('title'), children: title })), preview && _jsx("div", { className: b('preview'), children: preview })] }), _jsxs(ButtonGroup, { children: [additionalActions.map((action, index) => renderAdditionalAction(action, index)), baseActions.map((action) => renderBaseAction(action))] })] }));
64
+ const iconElement = icon ? (_jsx("div", { className: b('icon'), children: icon })) : (_jsx(Icon, { data: Sparkles, size: 16 }));
65
+ return (_jsxs("div", { className: b('', className), children: [withIcon && iconElement, _jsxs("div", { className: titlePositionClass, children: [title && (_jsx(Text, { as: "div", variant: "subheader-2", className: b('title'), children: title })), preview && _jsx("div", { className: b('preview'), children: preview })] }), _jsxs(ButtonGroup, { children: [additionalActions.map((action, index) => renderAdditionalAction(action, index)), baseActions.map((action) => renderBaseAction(action))] })] }));
65
66
  }
@@ -19,5 +19,6 @@ export type HeaderProps = {
19
19
  historyButtonRef?: React.RefObject<HTMLElement>;
20
20
  foldingState?: 'collapsed' | 'opened';
21
21
  titlePosition?: 'left' | 'center';
22
+ withIcon?: boolean;
22
23
  className?: string;
23
24
  };
@@ -16,6 +16,7 @@ export declare function useHeader(props: HeaderProps): {
16
16
  baseActions: ActionItem[];
17
17
  additionalActions: ActionItem[];
18
18
  titlePosition: 'left' | 'center';
19
+ withIcon: boolean;
19
20
  className?: string;
20
21
  historyButtonRef?: React.RefObject<HTMLElement>;
21
22
  };
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { HeaderAction } from './types';
3
3
  export function useHeader(props) {
4
- const { icon, title, preview, baseActions = [], handleNewChat, handleHistoryToggle, handleClose, handleFolding, foldingState = 'opened', additionalActions = [], titlePosition = 'left', className, historyButtonRef, } = props;
4
+ const { icon, title, preview, baseActions = [], handleNewChat, handleHistoryToggle, handleClose, handleFolding, foldingState = 'opened', additionalActions = [], titlePosition = 'left', withIcon = true, className, historyButtonRef, } = props;
5
5
  // Build base actions
6
6
  const baseActionsList = useMemo(() => {
7
7
  const actions = [];
@@ -65,6 +65,7 @@ export function useHeader(props) {
65
65
  baseActions: baseActionsList,
66
66
  additionalActions: additionalActionsList,
67
67
  titlePosition,
68
+ withIcon,
68
69
  className,
69
70
  historyButtonRef,
70
71
  };
@@ -17,7 +17,11 @@ export type MessageListProps<TContent extends TMessageContent = never> = {
17
17
  showAvatar?: boolean;
18
18
  userActions?: DefaultMessageAction<TUserMessage<TMessageMetadata>>[];
19
19
  assistantActions?: DefaultMessageAction<TAssistantMessage<TContent, TMessageMetadata>>[];
20
+ /** Array of chat statuses that should display the loader */
21
+ loaderStatuses?: ChatStatus[];
20
22
  className?: string;
21
23
  qa?: string;
24
+ hasPreviousMessages?: boolean;
25
+ onLoadPreviousMessages?: () => void;
22
26
  };
23
- export declare function MessageList<TContent extends TMessageContent = never>({ messages, messageRendererRegistry, transformOptions, showActionsOnHover, showTimestamp, showAvatar, userActions, assistantActions, className, qa, status, errorMessage, onRetry, }: MessageListProps<TContent>): import("react/jsx-runtime").JSX.Element;
27
+ export declare function MessageList<TContent extends TMessageContent = never>({ messages, messageRendererRegistry, transformOptions, showActionsOnHover, showTimestamp, showAvatar, userActions, assistantActions, loaderStatuses, className, qa, status, errorMessage, onRetry, hasPreviousMessages, onLoadPreviousMessages, }: MessageListProps<TContent>): import("react/jsx-runtime").JSX.Element;
@@ -1,24 +1,33 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useSmartScroll } from '../../../hooks';
2
+ import { useEffect } from 'react';
3
+ import { useScrollPreservation, useSmartScroll } from '../../../hooks';
3
4
  import { isAssistantMessage, isUserMessage, resolveMessageActions, } from '../../../utils';
4
5
  import { block } from '../../../utils/cn';
6
+ import { IntersectionContainer } from '../../atoms/IntersectionContainer';
5
7
  import { Loader } from '../../atoms/Loader';
6
8
  import { AssistantMessage } from '../AssistantMessage';
7
9
  import { UserMessage } from '../UserMessage';
8
10
  import { ErrorAlert } from './ErrorAlert';
9
11
  import './MessageList.scss';
10
12
  const b = block('message-list');
11
- export function MessageList({ messages, messageRendererRegistry, transformOptions, showActionsOnHover, showTimestamp, showAvatar, userActions, assistantActions, className, qa, status, errorMessage, onRetry, }) {
13
+ export function MessageList({ messages, messageRendererRegistry, transformOptions, showActionsOnHover, showTimestamp, showAvatar, userActions, assistantActions, loaderStatuses = ['submitted'], className, qa, status, errorMessage, onRetry, hasPreviousMessages = false, onLoadPreviousMessages, }) {
12
14
  const isStreaming = status === 'streaming';
13
15
  const messagesCount = messages.length;
14
- const { containerRef, endRef } = useSmartScroll(isStreaming, messagesCount);
16
+ const showLoader = status && loaderStatuses.includes(status);
17
+ const { containerRef, endRef, scrollToBottom } = useSmartScroll(isStreaming, messagesCount);
18
+ useEffect(() => {
19
+ scrollToBottom();
20
+ }, []);
21
+ // Preserve scroll position when older messages are loaded
22
+ useScrollPreservation(containerRef, messages.length);
15
23
  const renderMessage = (message, index) => {
16
24
  if (isUserMessage(message)) {
17
25
  const actions = resolveMessageActions(message, userActions);
18
26
  return (_jsx(UserMessage, { content: message.content, actions: actions, timestamp: message.timestamp, format: message.format, avatarUrl: message.avatarUrl, transformOptions: transformOptions, showActionsOnHover: showActionsOnHover, showTimestamp: showTimestamp, showAvatar: showAvatar }, message.id || `message-${index}`));
19
27
  }
20
28
  if (isAssistantMessage(message)) {
21
- const showActions = message.status === 'complete';
29
+ const isLastMessage = index === messages.length - 1;
30
+ const showActions = !(isLastMessage && isStreaming);
22
31
  const actions = showActions
23
32
  ? resolveMessageActions(message, assistantActions)
24
33
  : undefined;
@@ -26,5 +35,5 @@ export function MessageList({ messages, messageRendererRegistry, transformOption
26
35
  }
27
36
  return null;
28
37
  };
29
- return (_jsxs("div", { ref: containerRef, className: b(null, className), "data-qa": qa, children: [_jsx("div", { className: b('messages', className), "data-qa": qa, children: messages.map(renderMessage) }), status === 'submitted' && _jsx(Loader, { className: b('loader') }), status === 'error' && (_jsx(ErrorAlert, { className: b('error-alert'), onRetry: onRetry, errorMessage: errorMessage })), _jsx("div", { ref: endRef })] }));
38
+ return (_jsxs("div", { ref: containerRef, className: b(null, className), "data-qa": qa, children: [hasPreviousMessages && (_jsx(IntersectionContainer, { onIntersect: onLoadPreviousMessages, className: b('load-trigger'), children: _jsx(Loader, { view: "loading" }) })), _jsx("div", { className: b('messages'), "data-qa": qa, children: messages.map(renderMessage) }), showLoader && _jsx(Loader, { className: b('loader') }), status === 'error' && (_jsx(ErrorAlert, { className: b('error-alert'), onRetry: onRetry, errorMessage: errorMessage })), _jsx("div", { ref: endRef })] }));
30
39
  }
@@ -9,7 +9,6 @@ $block: '.#{variables.$ns}message-list';
9
9
  min-height: 0;
10
10
  flex: 1;
11
11
  align-self: stretch;
12
- width: 100%;
13
12
 
14
13
  &__messages {
15
14
  display: flex;
@@ -27,4 +26,10 @@ $block: '.#{variables.$ns}message-list';
27
26
  &__error-alert {
28
27
  margin-top: var(--g-spacing-4);
29
28
  }
29
+
30
+ &__load-trigger {
31
+ display: flex;
32
+ justify-content: center;
33
+ padding: var(--g-spacing-2);
34
+ }
30
35
  }
@@ -11,6 +11,8 @@ export type PromptInputProps = {
11
11
  onSend: (data: TSubmitData) => Promise<void>;
12
12
  /** Callback when sending is cancelled */
13
13
  onCancel?: () => Promise<void>;
14
+ /** Initial value */
15
+ initialValue?: string;
14
16
  /** Disabled state */
15
17
  disabled?: boolean;
16
18
  /** Chat status to determine input behavior */
@@ -13,10 +13,11 @@ import './PromptInput.scss';
13
13
  * @returns React component
14
14
  */
15
15
  export function PromptInput(props) {
16
- const { view = 'simple', onSend, onCancel, disabled = false, status = 'ready', maxLength, headerProps, bodyProps, footerProps, suggestionsProps, topPanel, bottomPanel, className, qa, } = props;
16
+ const { view = 'simple', onSend, onCancel, initialValue, disabled = false, status = 'ready', maxLength, headerProps, bodyProps, footerProps, suggestionsProps, topPanel, bottomPanel, className, qa, } = props;
17
17
  const hookState = usePromptInput({
18
18
  onSend,
19
19
  onCancel,
20
+ initialValue,
20
21
  disabled,
21
22
  status,
22
23
  maxLength,
@@ -24,8 +24,6 @@ export type UsePromptInputReturn = {
24
24
  value: string;
25
25
  /** Set the input value */
26
26
  setValue: (value: string) => void;
27
- /** Is currently sending */
28
- isSending: boolean;
29
27
  /** Can submit the form */
30
28
  canSubmit: boolean;
31
29
  /** Submit button state */
@@ -8,21 +8,22 @@ import { useCallback, useState } from 'react';
8
8
  export function usePromptInput(props) {
9
9
  const { onSend, onCancel, initialValue = '', maxLength, disabled = false, status = 'ready', } = props;
10
10
  const [value, setValue] = useState(initialValue);
11
- const [isSending, setIsSending] = useState(false);
12
11
  const [attachments, setAttachments] = useState([]);
12
+ const isSubmitted = status === 'submitted';
13
13
  const trimmedValue = value.trim();
14
- const canSubmit = !disabled && !isSending && trimmedValue.length > 0;
14
+ const canSubmit = !disabled && !isSubmitted && trimmedValue.length > 0;
15
15
  // Map ChatStatus to submit button state
16
16
  // ChatStatus.ready → submitButtonState.enabled
17
17
  // ChatStatus.error → submitButtonState.enabled
18
18
  // ChatStatus.streaming → submitButtonState.cancelable
19
19
  // ChatStatus.submitted → submitButtonState.loading
20
20
  let submitButtonState = 'disabled';
21
- if (disabled || !trimmedValue) {
21
+ // disabled by props or empty value and status is ready
22
+ if (disabled) {
22
23
  submitButtonState = 'disabled';
23
24
  }
24
- else if (isSending) {
25
- submitButtonState = 'loading';
25
+ else if (!trimmedValue && (status === 'ready' || status === 'error')) {
26
+ submitButtonState = 'disabled';
26
27
  }
27
28
  else {
28
29
  switch (status) {
@@ -56,16 +57,10 @@ export function usePromptInput(props) {
56
57
  if (!canSubmit) {
57
58
  return;
58
59
  }
59
- setIsSending(true);
60
- try {
61
- const submitData = Object.assign({ content: trimmedValue }, (attachments.length > 0 && { attachments }));
62
- await onSend(submitData);
63
- setValue('');
64
- setAttachments([]);
65
- }
66
- finally {
67
- setIsSending(false);
68
- }
60
+ const submitData = Object.assign({ content: trimmedValue }, (attachments.length > 0 && { attachments }));
61
+ onSend(submitData);
62
+ setValue('');
63
+ setAttachments([]);
69
64
  }, [submitButtonState, canSubmit, trimmedValue, attachments, onSend, onCancel]);
70
65
  const handleKeyDown = useCallback((event) => {
71
66
  const isEnter = event.code === 'Enter' || event.code === 'NumpadEnter';
@@ -89,7 +84,6 @@ export function usePromptInput(props) {
89
84
  return {
90
85
  value,
91
86
  setValue,
92
- isSending,
93
87
  canSubmit,
94
88
  submitButtonState,
95
89
  isInputDisabled,
@@ -1,18 +1,11 @@
1
1
  import { DOMProps, QAProps } from '@gravity-ui/uikit';
2
- import { ThinkingMessageData } from './useThinkingMessage';
2
+ import type { ThinkingMessageContentData } from '../../../types/messages';
3
3
  import './ThinkingMessage.scss';
4
4
  /**
5
5
  * Props for the ThinkingMessage component.
6
6
  * Combines DOM props, QA props, and thinking message data.
7
7
  */
8
- export type ThinkingMessageProps = DOMProps & QAProps & ThinkingMessageData & {
9
- /** Whether the thinking content should be expanded by default */
10
- defaultExpanded?: boolean;
11
- /** Whether to show the status indicator (loader) */
12
- showStatusIndicator?: boolean;
13
- /** Callback fired when the copy button is clicked */
14
- onCopyClick?: () => void;
15
- };
8
+ export type ThinkingMessageProps = DOMProps & QAProps & ThinkingMessageContentData;
16
9
  /**
17
10
  * ThinkingMessage component displays AI model's internal reasoning process.
18
11
  * Shows a collapsible block with thinking content and a copy button.
@@ -1,15 +1,5 @@
1
- import type { ThinkingMessageContent } from '../../../types/messages';
2
- import { BaseMessageAction } from '../../molecules/BaseMessage';
3
- export type ThinkingMessageData = Omit<ThinkingMessageContent, 'type'>;
4
- export type UseThinkingMessageOptions = ThinkingMessageData & {
5
- defaultExpanded?: boolean;
6
- showStatusIndicator?: boolean;
7
- };
8
- export type ThinkingMessageAction = {
9
- type: BaseMessageAction | string;
10
- onClick: () => void;
11
- };
12
- export declare function useThinkingMessage(options: UseThinkingMessageOptions): {
1
+ import type { ThinkingMessageContentData } from '../../../types/messages';
2
+ export declare function useThinkingMessage(options: ThinkingMessageContentData): {
13
3
  isExpanded: boolean;
14
4
  toggleExpanded: () => void;
15
5
  buttonTitle: string;
@@ -1,8 +1,7 @@
1
1
  import { useCallback, useMemo, useState } from 'react';
2
2
  import { i18n } from './i18n';
3
3
  export function useThinkingMessage(options) {
4
- const { defaultExpanded = true, showStatusIndicator = true, data } = options;
5
- const { status, content } = data;
4
+ const { defaultExpanded = true, showStatusIndicator = true, status, content } = options;
6
5
  const [isExpanded, setIsExpanded] = useState(defaultExpanded);
7
6
  const buttonTitle = useMemo(() => {
8
7
  return i18n(`title-${status}`);
@@ -19,7 +19,7 @@ const b = block('chat-container');
19
19
  */
20
20
  export function ChatContainer(props) {
21
21
  var _a, _b;
22
- const { chats = [], messages = [], onSendMessage, onDeleteChat, onCancel, onRetry, status = 'ready', error = null, showActionsOnHover = false, contextItems = [], transformOptions, headerProps = {}, contentProps = {}, emptyContainerProps = {}, promptInputProps = {}, historyProps = {}, welcomeConfig, i18nConfig = {}, className, headerClassName, contentClassName, footerClassName, qa, } = props;
22
+ const { chats = [], messages = [], onSendMessage, onDeleteChat, onCancel, onRetry, status = 'ready', error = null, showActionsOnHover = false, contextItems = [], transformOptions, messageListConfig, headerProps = {}, contentProps = {}, emptyContainerProps = {}, promptInputProps = {}, historyProps = {}, welcomeConfig, i18nConfig = {}, className, headerClassName, contentClassName, footerClassName, qa, } = props;
23
23
  const hookState = useChatContainer(props);
24
24
  // Collect i18n texts with overrides
25
25
  const headerTitle = useMemo(() => {
@@ -46,7 +46,7 @@ export function ChatContainer(props) {
46
46
  ((_b = i18nConfig.emptyState) === null || _b === void 0 ? void 0 : _b.description) ||
47
47
  i18n('empty-state-description'), suggestionTitle: (welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.suggestionTitle) ||
48
48
  ((_c = i18nConfig.emptyState) === null || _c === void 0 ? void 0 : _c.suggestionsTitle) ||
49
- i18n('empty-state-suggestions-title'), suggestions: welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.suggestions, alignment: welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.alignment, wrapText: welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.wrapText, showMore: welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.showMore, showMoreText: (welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.showMoreText) ||
49
+ i18n('empty-state-suggestions-title'), suggestions: welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.suggestions, alignment: welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.alignment, layout: welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.layout, wrapText: welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.wrapText, showMore: welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.showMore, showMoreText: (welcomeConfig === null || welcomeConfig === void 0 ? void 0 : welcomeConfig.showMoreText) ||
50
50
  ((_d = i18nConfig.emptyState) === null || _d === void 0 ? void 0 : _d.showMoreText) ||
51
51
  i18n('empty-state-show-more'), onSuggestionClick: async (clickedTitle) => {
52
52
  await onSendMessage({ content: clickedTitle });
@@ -60,7 +60,10 @@ export function ChatContainer(props) {
60
60
  onRetry,
61
61
  showActionsOnHover,
62
62
  transformOptions,
63
- }), [messages, status, error, onRetry, showActionsOnHover, transformOptions]);
63
+ userActions: messageListConfig === null || messageListConfig === void 0 ? void 0 : messageListConfig.userActions,
64
+ assistantActions: messageListConfig === null || messageListConfig === void 0 ? void 0 : messageListConfig.assistantActions,
65
+ loaderStatuses: messageListConfig === null || messageListConfig === void 0 ? void 0 : messageListConfig.loaderStatuses,
66
+ }), [messages, status, error, onRetry, showActionsOnHover, transformOptions, messageListConfig]);
64
67
  // Build props for PromptInput
65
68
  const finalPromptInputProps = useMemo(() => {
66
69
  var _a, _b;
@@ -32,11 +32,11 @@
32
32
  flex: 1 0 0;
33
33
  }
34
34
 
35
- &__content--view-empty {
35
+ &__content_view_empty {
36
36
  background: var(--g-aikit-chat-container-content-empty-background);
37
37
  }
38
38
 
39
- &__content--view-chat {
39
+ &__content_view_chat {
40
40
  background: var(--g-aikit-chat-container-content-chat-background);
41
41
  }
42
42
 
@@ -50,11 +50,11 @@
50
50
  background: var(--g-aikit-chat-container-footer-background);
51
51
  }
52
52
 
53
- &__footer--view-empty {
53
+ &__footer_view_empty {
54
54
  background: var(--g-aikit-chat-container-footer-empty-background);
55
55
  }
56
56
 
57
- &__footer--view-chat {
57
+ &__footer_view_chat {
58
58
  background: var(--g-aikit-chat-container-footer-chat-background);
59
59
  }
60
60
  }
@@ -1,2 +1,2 @@
1
1
  export { ChatContainer } from './ChatContainer';
2
- export type { ChatContainerProps, ChatContainerI18nConfig, WelcomeConfig } from './types';
2
+ export type { ChatContainerProps, ChatContainerI18nConfig, WelcomeConfig, MessageListConfig, } from './types';
@@ -1,5 +1,6 @@
1
1
  import type { OptionsType } from '@diplodoc/transform/lib/typings';
2
- import type { ChatStatus, ChatType, TChatMessage, TSubmitData } from '../../../types';
2
+ import type { ChatStatus, ChatType, TAssistantMessage, TChatMessage, TMessageContent, TMessageMetadata, TSubmitData, TUserMessage } from '../../../types';
3
+ import type { DefaultMessageAction } from '../../../utils';
3
4
  import type { ContextItemConfig } from '../../molecules/PromptInputHeader';
4
5
  import type { SuggestionsItem } from '../../molecules/Suggestions';
5
6
  import type { HeaderProps } from '../../organisms/Header';
@@ -71,6 +72,8 @@ export interface WelcomeConfig {
71
72
  suggestions?: SuggestionsItem[];
72
73
  /** Alignment configuration for image, title, and description */
73
74
  alignment?: AlignmentConfig;
75
+ /** Layout orientation for suggestions: 'grid' for horizontal, 'list' for vertical */
76
+ layout?: 'grid' | 'list';
74
77
  /** Enable text wrapping inside suggestion buttons instead of ellipsis */
75
78
  wrapText?: boolean;
76
79
  /** Show more suggestions callback */
@@ -78,6 +81,17 @@ export interface WelcomeConfig {
78
81
  /** Show more button text */
79
82
  showMoreText?: string;
80
83
  }
84
+ /**
85
+ * MessageList configuration
86
+ */
87
+ export interface MessageListConfig {
88
+ /** Default actions for user messages */
89
+ userActions?: DefaultMessageAction<TUserMessage<TMessageMetadata>>[];
90
+ /** Default actions for assistant messages */
91
+ assistantActions?: DefaultMessageAction<TAssistantMessage<TMessageContent, TMessageMetadata>>[];
92
+ /** Array of chat statuses that should display the loader */
93
+ loaderStatuses?: ChatStatus[];
94
+ }
81
95
  /**
82
96
  * Props for ChatContainer component
83
97
  */
@@ -114,6 +128,8 @@ export interface ChatContainerProps {
114
128
  contextItems?: ContextItemConfig[];
115
129
  /** Transform options for markdown rendering */
116
130
  transformOptions?: OptionsType;
131
+ /** MessageList configuration for actions and loader behavior */
132
+ messageListConfig?: MessageListConfig;
117
133
  /** Props override for Header component */
118
134
  headerProps?: Partial<Omit<HeaderProps, 'handleNewChat' | 'handleHistoryToggle' | 'handleClose'>>;
119
135
  /** Props override for ChatContent component */
@@ -15,5 +15,5 @@ export function ChatContent(props) {
15
15
  const isEmptyView = view === 'empty';
16
16
  return (_jsx("div", { className: b(null, className), "data-qa": qa, children: isEmptyView
17
17
  ? emptyContainerProps && _jsx(EmptyContainer, Object.assign({}, emptyContainerProps))
18
- : messageListProps && (_jsx("div", { className: b('message-list-container'), children: _jsx(MessageList, Object.assign({}, messageListProps)) })) }));
18
+ : messageListProps && (_jsx("div", { className: b('message-list-container'), children: _jsx(MessageList, Object.assign({}, messageListProps, { className: b('message-list', messageListProps.className) })) })) }));
19
19
  }
@@ -16,7 +16,6 @@ $block: '.#{variables.$ns}chat-content';
16
16
 
17
17
  &__message-list-container {
18
18
  display: flex;
19
- padding: var(--g-spacing-2) var(--g-spacing-2) 0 var(--g-spacing-2);
20
19
  flex-direction: column;
21
20
  align-items: flex-start;
22
21
  gap: var(--g-spacing-4);
@@ -24,4 +23,8 @@ $block: '.#{variables.$ns}chat-content';
24
23
  flex: 1;
25
24
  min-height: 0;
26
25
  }
26
+
27
+ &__message-list {
28
+ padding: var(--g-spacing-2) var(--g-spacing-2) 0 var(--g-spacing-2);
29
+ }
27
30
  }
@@ -38,6 +38,8 @@ export interface EmptyContainerProps {
38
38
  onSuggestionClick?: (content: string, id?: string) => void;
39
39
  /** Alignment configuration for image, title, and description */
40
40
  alignment?: AlignmentConfig;
41
+ /** Layout orientation for suggestions: 'grid' for horizontal, 'list' for vertical */
42
+ layout?: 'grid' | 'list';
41
43
  /** Enable text wrapping inside suggestion buttons instead of ellipsis */
42
44
  wrapText?: boolean;
43
45
  /** Callback for showing more suggestions */
@@ -14,7 +14,7 @@ const b = block('empty-container');
14
14
  * @returns React component
15
15
  */
16
16
  export function EmptyContainer(props) {
17
- const { image, title, description, suggestionTitle, suggestions = [], onSuggestionClick, alignment, wrapText = false, showMore, showMoreText, className, qa, } = props;
17
+ const { image, title, description, suggestionTitle, suggestions = [], onSuggestionClick, alignment, layout = 'grid', wrapText = false, showMore, showMoreText, className, qa, } = props;
18
18
  const hasContent = title || description || (suggestions && suggestions.length > 0);
19
19
  // Define alignment for each element
20
20
  const imageAlignment = (alignment === null || alignment === void 0 ? void 0 : alignment.image) || 'left';
@@ -22,5 +22,5 @@ export function EmptyContainer(props) {
22
22
  const descriptionAlignment = (alignment === null || alignment === void 0 ? void 0 : alignment.description) || 'left';
23
23
  // Define text for "Show more" button with localization support
24
24
  const showMoreButtonText = showMoreText || i18n('show-more-button');
25
- return (_jsx("div", { className: b(null, className), "data-qa": qa, children: _jsx("div", { className: b('content'), children: hasContent && (_jsxs(_Fragment, { children: [_jsxs("div", { className: b('welcome-section'), children: [image && (_jsx("div", { className: b('image-container', { align: imageAlignment }), children: image })), _jsxs("div", { className: b('text-container'), children: [title && (_jsx(Text, { variant: "header-2", className: b('title', { align: titleAlignment }), children: title })), description && (_jsx(Text, { variant: "body-2", color: "complementary", className: b('description', { align: descriptionAlignment }), children: description }))] })] }), suggestions && suggestions.length > 0 && onSuggestionClick && (_jsxs("div", { className: b('suggestions-section'), children: [suggestionTitle && (_jsx("div", { className: b('suggestions-title'), children: _jsx(Text, { variant: "subheader-3", color: "primary", children: suggestionTitle }) })), _jsx("div", { children: _jsx(Suggestions, { items: suggestions, onClick: onSuggestionClick, layout: "grid", wrapText: wrapText }) }), showMore && (_jsx("div", { className: b('show-more'), children: _jsxs(Button, { view: "flat-secondary", size: "l", onClick: showMore, className: b('show-more-button'), children: [_jsx(Button.Icon, { children: _jsx(ArrowRotateRight, {}) }), showMoreButtonText] }) }))] }))] })) }) }));
25
+ return (_jsx("div", { className: b(null, className), "data-qa": qa, children: _jsx("div", { className: b('content'), children: hasContent && (_jsxs(_Fragment, { children: [_jsxs("div", { className: b('welcome-section'), children: [image && (_jsx("div", { className: b('image-container', { align: imageAlignment }), children: image })), _jsxs("div", { className: b('text-container'), children: [title && (_jsx(Text, { variant: "header-2", className: b('title', { align: titleAlignment }), children: title })), description && (_jsx(Text, { variant: "body-2", color: "complementary", className: b('description', { align: descriptionAlignment }), children: description }))] })] }), suggestions && suggestions.length > 0 && onSuggestionClick && (_jsxs("div", { className: b('suggestions-section'), children: [suggestionTitle && (_jsx("div", { className: b('suggestions-title'), children: _jsx(Text, { variant: "subheader-3", color: "primary", children: suggestionTitle }) })), _jsx("div", { children: _jsx(Suggestions, { items: suggestions, onClick: onSuggestionClick, layout: layout, wrapText: wrapText }) }), showMore && (_jsx("div", { className: b('show-more'), children: _jsxs(Button, { view: "flat-secondary", size: "l", onClick: showMore, className: b('show-more-button'), children: [_jsx(Button.Icon, { children: _jsx(ArrowRotateRight, {}) }), showMoreButtonText] }) }))] }))] })) }) }));
26
26
  }
@@ -30,6 +30,8 @@ export interface HistoryProps extends QAProps, DOMProps {
30
30
  className?: string;
31
31
  /** Custom filter function for search */
32
32
  filterFunction?: ChatFilterFunction;
33
+ /** Loading state */
34
+ loading?: boolean;
33
35
  /** Control popup open state */
34
36
  open?: boolean;
35
37
  /** Callback when popup open state changes */
@@ -8,9 +8,9 @@ import { HistoryList } from './HistoryList';
8
8
  * @returns React component
9
9
  */
10
10
  export function History(props) {
11
- const { chats, selectedChat, onSelectChat, onDeleteChat, onLoadMore, hasMore = false, searchable = true, groupBy = 'date', showActions = true, emptyPlaceholder, className, qa, style, filterFunction, open = false, onOpenChange, anchorElement, } = props;
11
+ const { chats, selectedChat, onSelectChat, onDeleteChat, onLoadMore, hasMore = false, searchable = true, groupBy = 'date', showActions = true, emptyPlaceholder, className, qa, style, filterFunction, loading = false, open = false, onOpenChange, anchorElement, } = props;
12
12
  const handleChatClick = () => {
13
13
  onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(false);
14
14
  };
15
- return (_jsx(Popup, { anchorElement: anchorElement, placement: "bottom-end", open: open, onOpenChange: onOpenChange, children: _jsx(HistoryList, { chats: chats, selectedChat: selectedChat, onSelectChat: onSelectChat, onDeleteChat: onDeleteChat, onLoadMore: onLoadMore, hasMore: hasMore, searchable: searchable, groupBy: groupBy, showActions: showActions, emptyPlaceholder: emptyPlaceholder, className: className, qa: qa, style: style, filterFunction: filterFunction, onChatClick: handleChatClick }) }));
15
+ return (_jsx(Popup, { anchorElement: anchorElement, placement: "bottom-end", open: open, onOpenChange: onOpenChange, children: _jsx(HistoryList, { chats: chats, selectedChat: selectedChat, onSelectChat: onSelectChat, onDeleteChat: onDeleteChat, onLoadMore: onLoadMore, hasMore: hasMore, searchable: searchable, groupBy: groupBy, showActions: showActions, emptyPlaceholder: emptyPlaceholder, className: className, qa: qa, style: style, filterFunction: filterFunction, loading: loading, onChatClick: handleChatClick }) }));
16
16
  }
@@ -87,4 +87,12 @@ $block: '.#{variables.$ns}history';
87
87
  text-align: center;
88
88
  flex: 1;
89
89
  }
90
+
91
+ &__loader-wrapper {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ padding: spacing(8) spacing(4);
96
+ flex: 1;
97
+ }
90
98
  }
@@ -31,6 +31,8 @@ export interface HistoryListProps extends QAProps, DOMProps {
31
31
  className?: string;
32
32
  /** Custom filter function for search */
33
33
  filterFunction?: ChatFilterFunction;
34
+ /** Loading state */
35
+ loading?: boolean;
34
36
  /** Callback when chat is clicked (for closing popup in parent) */
35
37
  onChatClick?: (chat: ChatType) => void;
36
38
  }
@@ -3,6 +3,7 @@ import { useMemo } from 'react';
3
3
  import { Button, List } from '@gravity-ui/uikit';
4
4
  import { defaultChatFilter, groupChatsByDate } from '../../../utils/chatUtils';
5
5
  import { block } from '../../../utils/cn';
6
+ import { Loader } from '../../atoms/Loader';
6
7
  import { ChatItem } from './ChatItem';
7
8
  import { DateHeaderItem } from './DateHeaderItem';
8
9
  import { i18n } from './i18n';
@@ -15,7 +16,7 @@ const b = block('history');
15
16
  * @returns React component
16
17
  */
17
18
  export function HistoryList(props) {
18
- const { chats, selectedChat, onSelectChat, onDeleteChat, onLoadMore, hasMore = false, searchable = true, groupBy = 'date', showActions = true, emptyPlaceholder, className, qa, style, filterFunction = defaultChatFilter, onChatClick, } = props;
19
+ const { chats, selectedChat, onSelectChat, onDeleteChat, onLoadMore, hasMore = false, searchable = true, groupBy = 'date', showActions = true, emptyPlaceholder, className, qa, style, filterFunction = defaultChatFilter, loading = false, onChatClick, } = props;
19
20
  // Group chats if needed
20
21
  const groupedChats = useMemo(() => {
21
22
  if (groupBy === 'none') {
@@ -83,5 +84,5 @@ export function HistoryList(props) {
83
84
  return (_jsx(ChatItem, { chat: item, showActions: showActions, onChatClick: handleChatClick, onDeleteClick: onDeleteChat ? handleDeleteClick : undefined }, item.id));
84
85
  };
85
86
  const emptyState = emptyPlaceholder || _jsx("div", { className: b('empty'), children: i18n('empty-state') });
86
- return (_jsxs("div", { className: b('container', className), "data-qa": qa, style: style, children: [_jsx("div", { className: b('list-wrapper'), children: _jsx(List, { items: listItems, renderItem: renderItem, virtualized: false, filterable: searchable, filterItem: wrappedFilterFunction, filterPlaceholder: i18n('search-placeholder'), filterClassName: b('filter'), emptyPlaceholder: emptyState, selectedItemIndex: selectedItemIndex, itemsClassName: b('list'), itemClassName: b('list-item') }) }), hasMore && onLoadMore && (_jsx(Button, { view: "flat-action", size: "m", width: "max", onClick: onLoadMore, children: i18n('action-load-more') }))] }));
87
+ return (_jsxs("div", { className: b('container', className), "data-qa": qa, style: style, children: [_jsx("div", { className: b('list-wrapper'), children: loading ? (_jsx("div", { className: b('loader-wrapper'), children: _jsx(Loader, { view: "loading" }) })) : (_jsx(List, { items: listItems, renderItem: renderItem, virtualized: false, filterable: searchable, filterItem: wrappedFilterFunction, filterPlaceholder: i18n('search-placeholder'), filterClassName: b('filter'), emptyPlaceholder: emptyState, selectedItemIndex: selectedItemIndex, itemsClassName: b('list'), itemClassName: b('list-item') })) }), hasMore && onLoadMore && (_jsx(Button, { view: "flat-action", size: "m", width: "max", onClick: onLoadMore, children: i18n('action-load-more') }))] }));
87
88
  }
@@ -1,3 +1,4 @@
1
1
  export * from './useDateFormatter';
2
2
  export * from './useToolMessage';
3
3
  export * from './useSmartScroll';
4
+ export * from './useScrollPreservation';
@@ -1,3 +1,4 @@
1
1
  export * from './useDateFormatter';
2
2
  export * from './useToolMessage';
3
3
  export * from './useSmartScroll';
4
+ export * from './useScrollPreservation';
@@ -0,0 +1,9 @@
1
+ import { type RefObject } from 'react';
2
+ /**
3
+ * Hook to preserve scroll position when older messages are loaded at the top
4
+ * This prevents the chat from jumping when new content is prepended
5
+ * @param {RefObject<T>} containerRef - Reference to the scrollable container element
6
+ * @param {number} messagesCount - Current count of messages in the list
7
+ * @returns {void}
8
+ */
9
+ export declare function useScrollPreservation<T extends HTMLElement>(containerRef: RefObject<T>, messagesCount: number): void;
@@ -0,0 +1,28 @@
1
+ import { useLayoutEffect, useRef } from 'react';
2
+ /**
3
+ * Hook to preserve scroll position when older messages are loaded at the top
4
+ * This prevents the chat from jumping when new content is prepended
5
+ * @param {RefObject<T>} containerRef - Reference to the scrollable container element
6
+ * @param {number} messagesCount - Current count of messages in the list
7
+ * @returns {void}
8
+ */
9
+ export function useScrollPreservation(containerRef, messagesCount) {
10
+ const prevScrollHeight = useRef(0);
11
+ const prevMessagesCount = useRef(messagesCount);
12
+ useLayoutEffect(() => {
13
+ const container = containerRef.current;
14
+ if (!container)
15
+ return;
16
+ const currentScrollHeight = container.scrollHeight;
17
+ // Only adjust scroll if messages were added at the top (older messages loaded)
18
+ if (prevScrollHeight.current > 0 &&
19
+ messagesCount > prevMessagesCount.current &&
20
+ currentScrollHeight > prevScrollHeight.current) {
21
+ // Calculate the height difference and adjust scroll position
22
+ const heightDiff = currentScrollHeight - prevScrollHeight.current;
23
+ container.scrollTop += heightDiff;
24
+ }
25
+ prevScrollHeight.current = currentScrollHeight;
26
+ prevMessagesCount.current = messagesCount;
27
+ }, [containerRef, messagesCount]);
28
+ }
@@ -16,4 +16,7 @@
16
16
  --g-aikit-accent-color: #0077ff;
17
17
 
18
18
  --g-aikit-line-brand: var(--g-aikit-accent-color);
19
+
20
+ --g-aikit-shimmer-color-from: rgba(255, 255, 255, 0.35);
21
+ --g-aikit-shimmer-color-to: rgba(255, 255, 255, 0.85);
19
22
  }
@@ -21,7 +21,6 @@ export type TSubmitData = {
21
21
  attachments?: File[];
22
22
  metadata?: TMessageMetadata;
23
23
  };
24
- export type TMessageStatus = 'sending' | 'complete' | 'error' | 'streaming';
25
24
  export type TMessageRole = 'user' | 'assistant' | 'system';
26
25
  export type TMessageContent<Type extends string = string, Data = unknown> = {
27
26
  id?: string;
@@ -36,6 +35,11 @@ export type ThinkingMessageContentData = {
36
35
  title?: string;
37
36
  content: string | string[];
38
37
  status: 'thinking' | 'thought';
38
+ defaultExpanded?: boolean;
39
+ showStatusIndicator?: boolean;
40
+ onCopyClick?: () => void;
41
+ className?: string;
42
+ qa?: string;
39
43
  };
40
44
  export type ThinkingMessageContent = TMessageContent<'thinking', ThinkingMessageContentData>;
41
45
  export type ToolMessageContentData = ToolMessageProps;
@@ -45,7 +49,6 @@ export type TMessageContentUnion<TCustomMessageContent extends TMessageContent =
45
49
  export type TAssistantMessageContent<TCustomMessageContent extends TMessageContent = never> = string | TMessageContentUnion<TCustomMessageContent> | TMessageContentUnion<TCustomMessageContent>[];
46
50
  export type TBaseMessage<Metadata = TMessageMetadata> = Pick<BaseMessageProps, 'actions' | 'timestamp'> & {
47
51
  id?: string;
48
- status?: TMessageStatus;
49
52
  error?: unknown;
50
53
  metadata?: Metadata;
51
54
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/aikit",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Gravity UI base kit for building ai assistant chats",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",