@gravity-ui/aikit 1.8.0 → 1.9.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 (50) hide show
  1. package/dist/components/atoms/Alert/index.js +1 -1
  2. package/dist/components/atoms/ChatDate/ChatDate.js +1 -1
  3. package/dist/components/atoms/ContextIndicator/index.js +1 -1
  4. package/dist/components/atoms/DiffStat/index.js +1 -1
  5. package/dist/components/atoms/Disclaimer/Disclaimer.js +1 -1
  6. package/dist/components/atoms/IntersectionContainer/IntersectionContainer.js +1 -1
  7. package/dist/components/atoms/Loader/Loader.js +1 -1
  8. package/dist/components/atoms/MarkdownRenderer/MarkdownRenderer.js +1 -1
  9. package/dist/components/atoms/MessageBalloon/index.js +1 -1
  10. package/dist/components/atoms/Shimmer/index.js +1 -1
  11. package/dist/components/atoms/SubmitButton/SubmitButton.js +1 -1
  12. package/dist/components/atoms/ToolIndicator/index.js +1 -1
  13. package/dist/components/molecules/BaseMessage/BaseMessage.css +1 -0
  14. package/dist/components/molecules/BaseMessage/__stories__/BaseMessage.stories.d.ts +1 -0
  15. package/dist/components/molecules/BaseMessage/__stories__/BaseMessage.stories.js +10 -0
  16. package/dist/components/molecules/BaseMessage/index.js +22 -14
  17. package/dist/components/molecules/ButtonGroup/index.js +1 -1
  18. package/dist/components/molecules/PromptInputBody/PromptInputBody.js +1 -1
  19. package/dist/components/molecules/PromptInputFooter/PromptInputFooter.js +1 -1
  20. package/dist/components/molecules/PromptInputHeader/PromptInputHeader.js +1 -1
  21. package/dist/components/molecules/PromptInputPanel/PromptInputPanel.js +1 -1
  22. package/dist/components/molecules/Suggestions/Suggestions.js +1 -1
  23. package/dist/components/molecules/Tabs/Tabs.js +1 -1
  24. package/dist/components/molecules/ToolFooter/index.js +1 -1
  25. package/dist/components/molecules/ToolHeader/index.js +1 -1
  26. package/dist/components/molecules/ToolStatus/index.js +1 -1
  27. package/dist/components/organisms/AssistantMessage/AssistantMessage.d.ts +2 -2
  28. package/dist/components/organisms/AssistantMessage/AssistantMessage.js +3 -3
  29. package/dist/components/organisms/AssistantMessage/__stories__/AssistantMessage.stories.d.ts +1 -0
  30. package/dist/components/organisms/AssistantMessage/__stories__/AssistantMessage.stories.js +12 -1
  31. package/dist/components/organisms/Header/Header.js +1 -1
  32. package/dist/components/organisms/MessageList/MessageList.js +2 -2
  33. package/dist/components/organisms/MessageList/__stories__/MessageList.stories.d.ts +1 -0
  34. package/dist/components/organisms/MessageList/__stories__/MessageList.stories.js +54 -4
  35. package/dist/components/organisms/PromptInput/PromptInput.js +1 -1
  36. package/dist/components/organisms/ThinkingMessage/index.js +1 -1
  37. package/dist/components/organisms/ToolMessage/index.js +1 -1
  38. package/dist/components/organisms/UserMessage/index.js +1 -1
  39. package/dist/components/pages/ChatContainer/ChatContainer.js +1 -1
  40. package/dist/components/pages/ChatContainer/__stories__/ChatContainer.stories.d.ts +5 -0
  41. package/dist/components/pages/ChatContainer/__stories__/ChatContainer.stories.js +77 -0
  42. package/dist/components/templates/ChatContent/ChatContent.js +1 -1
  43. package/dist/components/templates/EmptyContainer/EmptyContainer.js +1 -1
  44. package/dist/components/templates/History/HistoryList.js +1 -1
  45. package/dist/demo/ContentWrapper/ContentWrapper.js +1 -1
  46. package/dist/demo/Showcase/Showcase.js +1 -1
  47. package/dist/demo/ShowcaseItem/ShowcaseItem.js +1 -1
  48. package/dist/demo/SwapArea/SwapArea.js +1 -1
  49. package/dist/types/messages.d.ts +3 -0
  50. package/package.json +3 -2
@@ -3,7 +3,7 @@ import { useMemo, useState } from 'react';
3
3
  import { ChevronDown, ChevronUp, CircleExclamationFill, CircleInfoFill, TriangleExclamationFill, } from '@gravity-ui/icons';
4
4
  import { Button, Icon, Text } from '@gravity-ui/uikit';
5
5
  import { block } from '../../../utils/cn';
6
- import './Alert.scss';
6
+ import './Alert.css';
7
7
  const b = block('alert');
8
8
  const statusIcons = {
9
9
  info: CircleInfoFill,
@@ -3,7 +3,7 @@ import { Fragment } from 'react';
3
3
  import { useDateFormatter } from '../../../hooks';
4
4
  import { block } from '../../../utils/cn';
5
5
  import { i18n } from './i18n';
6
- import './ChatDate.scss';
6
+ import './ChatDate.css';
7
7
  const b = block('chat-date');
8
8
  /**
9
9
  * ChatDate component displays formatted dates with optional time
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { block } from '../../../utils/cn';
3
3
  import { getProgressColor } from './utils';
4
- import './ContextIndicator.scss';
4
+ import './ContextIndicator.css';
5
5
  const b = block('context-indicator');
6
6
  export const ContextIndicator = (props) => {
7
7
  const { className, qa, orientation = 'horizontal', reversed = false } = props;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { block } from '../../../utils/cn';
3
- import './DiffStat.scss';
3
+ import './DiffStat.css';
4
4
  const b = block('diff-stat');
5
5
  export const DiffStat = (props) => {
6
6
  const { added, deleted, className, style, qa } = props;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Text } from '@gravity-ui/uikit';
3
3
  import { block } from '../../../utils/cn';
4
- import './Disclaimer.scss';
4
+ import './Disclaimer.css';
5
5
  const b = block('disclaimer');
6
6
  /**
7
7
  * Disclaimer component displays informational or warning messages
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { useIntersection } from '@gravity-ui/uikit';
4
4
  import { block } from '../../../utils/cn';
5
- import './IntersectionContainer.scss';
5
+ import './IntersectionContainer.css';
6
6
  const b = block('intersection-container');
7
7
  export const IntersectionContainer = ({ children, onIntersect, options, className, }) => {
8
8
  const [ref, setRef] = React.useState(null);
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Spin } from '@gravity-ui/uikit';
3
3
  import { block } from '../../../utils/cn';
4
- import './Loader.scss';
4
+ import './Loader.css';
5
5
  const b = block('loader');
6
6
  export function Loader({ view = 'streaming', size = 's', className, qa }) {
7
7
  if (view === 'streaming') {
@@ -4,7 +4,7 @@ import { useMarkdownTransform } from '../../../hooks/useMarkdownTransform';
4
4
  import { useRemend } from '../../../hooks/useRemend';
5
5
  import { block } from '../../../utils/cn';
6
6
  import { areOptionsEqual } from '../../../utils/markdownUtils';
7
- import './MarkdownRenderer.scss';
7
+ import './MarkdownRenderer.css';
8
8
  const b = block('markdown-renderer');
9
9
  function MarkdownRendererComponent({ content, className, qa, transformOptions, shouldParseIncompleteMarkdown = false, }) {
10
10
  const closedContent = useRemend(content, shouldParseIncompleteMarkdown);
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { block } from '../../../utils/cn';
3
- import './MessageBalloon.scss';
3
+ import './MessageBalloon.css';
4
4
  const b = block('message-balloon');
5
5
  export const MessageBalloon = (props) => {
6
6
  const { className, qa, children } = props;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { block } from '../../../utils/cn';
3
- import './Shimmer.scss';
3
+ import './Shimmer.css';
4
4
  const b = block('shimmer');
5
5
  export const Shimmer = (props) => {
6
6
  const { className, qa, children } = props;
@@ -5,7 +5,7 @@ import { Icon, Spin } from '@gravity-ui/uikit';
5
5
  import { block } from '../../../utils/cn';
6
6
  import { ActionButton } from '../ActionButton';
7
7
  import { i18n } from './i18n';
8
- import './SubmitButton.scss';
8
+ import './SubmitButton.css';
9
9
  const b = block('submit-button');
10
10
  /**
11
11
  * Submit button component with state management through props
@@ -3,7 +3,7 @@ import { CircleCheck, CircleInfo, CircleXmark } from '@gravity-ui/icons';
3
3
  import { Icon } from '@gravity-ui/uikit';
4
4
  import { block } from '../../../utils/cn';
5
5
  import { Loader } from '../Loader';
6
- import './ToolIndicator.scss';
6
+ import './ToolIndicator.css';
7
7
  const b = block('tool-indicator');
8
8
  export const ToolIndicator = (props) => {
9
9
  const { status = 'info', className, qa } = props;
@@ -2,6 +2,7 @@
2
2
  display: flex;
3
3
  flex-direction: column;
4
4
  gap: var(--g-spacing-1);
5
+ word-break: break-word;
5
6
  }
6
7
  .g-aikit-base-message_variant_user {
7
8
  align-items: flex-end;
@@ -7,3 +7,4 @@ export declare const Variant: StoryObj<BaseMessageProps>;
7
7
  export declare const ShowActionsOnHover: StoryFn<BaseMessageProps>;
8
8
  export declare const ShowTimestamp: StoryObj<BaseMessageProps>;
9
9
  export declare const WithCustomAction: StoryFn<BaseMessageProps>;
10
+ export declare const WithUserRating: StoryObj<BaseMessageProps>;
@@ -50,6 +50,12 @@ const buttons = [
50
50
  // eslint-disable-next-line no-console
51
51
  { actionType: 'unlike', onClick: () => console.log('unlike') },
52
52
  ];
53
+ const likeUnlikeButtons = [
54
+ // eslint-disable-next-line no-console
55
+ { actionType: 'like', onClick: () => console.log('like') },
56
+ // eslint-disable-next-line no-console
57
+ { actionType: 'unlike', onClick: () => console.log('unlike') },
58
+ ];
53
59
  export const Playground = (args) => (_jsx(ContentWrapper, { children: _jsx(BaseMessage, Object.assign({}, args)) }));
54
60
  Playground.args = {
55
61
  children: 'My message',
@@ -75,3 +81,7 @@ export const WithCustomAction = (args) => (_jsx(ContentWrapper, { children: _jsx
75
81
  // eslint-disable-next-line no-console
76
82
  onClick: () => console.log('custom'), children: "Custom" }, "custom"),
77
83
  ], role: "assistant", children: 'Message with custom action button' })) }));
84
+ export const WithUserRating = {
85
+ render: (args) => (_jsxs(_Fragment, { children: [_jsx(ShowcaseItem, { title: "No rating", width: "300px", children: _jsx(BaseMessage, Object.assign({}, args, { actions: likeUnlikeButtons, role: "assistant", children: 'Message' })) }), _jsx(ShowcaseItem, { title: "Rated like", width: "300px", children: _jsx(BaseMessage, Object.assign({}, args, { actions: likeUnlikeButtons, role: "assistant", userRating: "like", children: 'Message' })) }), _jsx(ShowcaseItem, { title: "Rated dislike", width: "300px", children: _jsx(BaseMessage, Object.assign({}, args, { actions: likeUnlikeButtons, role: "assistant", userRating: "dislike", children: 'Message' })) })] })),
86
+ decorators: defaultDecorators,
87
+ };
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import { ArrowRotateLeft, Copy as CopyIcon, Pencil, ThumbsDown, ThumbsUp, TrashBin, } from '@gravity-ui/icons';
3
+ import { ArrowRotateLeft, Copy as CopyIcon, Pencil, ThumbsDown, ThumbsDownFill, ThumbsUp, ThumbsUpFill, TrashBin, } from '@gravity-ui/icons';
4
4
  import { Icon } from '@gravity-ui/uikit';
5
5
  import { isActionConfig } from '../../../utils/actionUtils';
6
6
  import { block } from '../../../utils/cn';
@@ -8,7 +8,7 @@ import { ActionButton } from '../../atoms';
8
8
  import { ChatDate } from '../../atoms/ChatDate';
9
9
  import { ButtonGroup } from '../ButtonGroup';
10
10
  import { i18n } from './i18n';
11
- import './BaseMessage.scss';
11
+ import './BaseMessage.css';
12
12
  const b = block('base-message');
13
13
  export var BaseMessageActionType;
14
14
  (function (BaseMessageActionType) {
@@ -19,16 +19,8 @@ export var BaseMessageActionType;
19
19
  BaseMessageActionType["Unlike"] = "unlike";
20
20
  BaseMessageActionType["Delete"] = "delete";
21
21
  })(BaseMessageActionType || (BaseMessageActionType = {}));
22
- const BaseMessageActionIcons = {
23
- [BaseMessageActionType.Copy]: CopyIcon,
24
- [BaseMessageActionType.Edit]: Pencil,
25
- [BaseMessageActionType.Retry]: ArrowRotateLeft,
26
- [BaseMessageActionType.Like]: ThumbsUp,
27
- [BaseMessageActionType.Unlike]: ThumbsDown,
28
- [BaseMessageActionType.Delete]: TrashBin,
29
- };
30
22
  export const BaseMessage = (props) => {
31
- const { className, qa, showActionsOnHover, actions, children, role: variant, showTimestamp, timestamp = '', } = props;
23
+ const { className, qa, showActionsOnHover, actions, children, role: variant, showTimestamp, timestamp = '', userRating, } = props;
32
24
  // Get tooltip text for action
33
25
  const getTooltipText = (actionType) => {
34
26
  if (!actionType) {
@@ -49,9 +41,7 @@ export const BaseMessage = (props) => {
49
41
  return _jsx(React.Fragment, { children: action }, index);
50
42
  }
51
43
  const tooltipText = getTooltipText(action.actionType);
52
- const defaultIcon = action.actionType
53
- ? BaseMessageActionIcons[action.actionType]
54
- : undefined;
44
+ const defaultIcon = getDefaultIcon(action.actionType, userRating);
55
45
  // Determine tooltip title
56
46
  let tooltipTitle;
57
47
  if (action.label) {
@@ -77,3 +67,21 @@ export const BaseMessage = (props) => {
77
67
  return (_jsx(ActionButton, Object.assign({}, action, { tooltipTitle: tooltipTitle, view: action.view || 'flat-secondary', children: buttonContent }), action.actionType || index));
78
68
  }) })] }))] }));
79
69
  };
70
+ function getDefaultIcon(actionType, userRating) {
71
+ switch (actionType) {
72
+ case BaseMessageActionType.Copy:
73
+ return CopyIcon;
74
+ case BaseMessageActionType.Edit:
75
+ return Pencil;
76
+ case BaseMessageActionType.Retry:
77
+ return ArrowRotateLeft;
78
+ case BaseMessageActionType.Delete:
79
+ return TrashBin;
80
+ case BaseMessageActionType.Like:
81
+ return userRating === 'like' ? ThumbsUpFill : ThumbsUp;
82
+ case BaseMessageActionType.Unlike:
83
+ return userRating === 'dislike' ? ThumbsDownFill : ThumbsDown;
84
+ default:
85
+ return undefined;
86
+ }
87
+ }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { block } from '../../../utils/cn';
3
- import './ButtonGroup.scss';
3
+ import './ButtonGroup.css';
4
4
  const b = block('button-group');
5
5
  export const ButtonGroup = (props) => {
6
6
  const { orientation = 'horizontal', className, qa, children } = props;
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef } from 'react';
3
3
  import { TextArea } from '@gravity-ui/uikit';
4
4
  import { block } from '../../../utils/cn';
5
- import './PromptInputBody.scss';
5
+ import './PromptInputBody.css';
6
6
  const b = block('prompt-input-body');
7
7
  /**
8
8
  * PromptInputBody component displays the main input area
@@ -6,7 +6,7 @@ import { ActionButton } from '../../atoms/ActionButton';
6
6
  import { SubmitButton } from '../../atoms/SubmitButton';
7
7
  import { ButtonGroup } from '../ButtonGroup';
8
8
  import { i18n } from './i18n';
9
- import './PromptInputFooter.scss';
9
+ import './PromptInputFooter.css';
10
10
  const b = block('prompt-input-footer');
11
11
  /**
12
12
  * PromptInputFooter component displays the footer section with action icons
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { block } from '../../../utils/cn';
3
3
  import { ContextIndicator } from '../../atoms/ContextIndicator';
4
4
  import { ContextItem } from '../../atoms/ContextItem';
5
- import './PromptInputHeader.scss';
5
+ import './PromptInputHeader.css';
6
6
  const b = block('prompt-input-header');
7
7
  /**
8
8
  * PromptInputHeader component displays the header section of prompt input
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { block } from '../../../utils/cn';
3
- import './PromptInputPanel.scss';
3
+ import './PromptInputPanel.css';
4
4
  const b = block('prompt-input-panel');
5
5
  /**
6
6
  * PromptInputPanel component displays a panel with custom content
@@ -3,7 +3,7 @@ import { ChevronLeft, ChevronRight } from '@gravity-ui/icons';
3
3
  import { Icon, Text } from '@gravity-ui/uikit';
4
4
  import { block } from '../../../utils/cn';
5
5
  import { ActionButton } from '../../atoms';
6
- import './Suggestions.scss';
6
+ import './Suggestions.css';
7
7
  const b = block('suggestions');
8
8
  /**
9
9
  * Suggestions component displays a group of clickable suggestion buttons
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useCallback } from 'react';
4
4
  import { Label } from '@gravity-ui/uikit';
5
5
  import { block } from '../../../utils/cn';
6
- import './Tabs.scss';
6
+ import './Tabs.css';
7
7
  const b = block('tabs');
8
8
  export const Tabs = (props) => {
9
9
  const { items, activeId, onSelectItem, onDeleteItem, allowDelete = false, className, style, qa, } = props;
@@ -5,7 +5,7 @@ import { isActionConfig } from '../../../utils/actionUtils';
5
5
  import { block } from '../../../utils/cn';
6
6
  import { Loader, Shimmer } from '../../atoms';
7
7
  import { ButtonGroup } from '../ButtonGroup';
8
- import './ToolFooter.scss';
8
+ import './ToolFooter.css';
9
9
  const b = block('tool-footer');
10
10
  export function ToolFooter({ actions, content, showLoader = true, className, qa }) {
11
11
  return (_jsxs("div", { className: b('', className), "data-qa": qa, children: [_jsxs("div", { className: b('left'), children: [showLoader && _jsx(Loader, { view: "loading", size: "xs" }), content && (_jsx(Shimmer, { children: _jsx(Text, { children: content }) }))] }), _jsx(ButtonGroup, { children: actions.map((action, index) => {
@@ -6,7 +6,7 @@ import { block } from '../../../utils/cn';
6
6
  import { ActionButton } from '../../atoms';
7
7
  import { ButtonGroup } from '../ButtonGroup';
8
8
  import { ToolStatus } from '../ToolStatus';
9
- import './ToolHeader.scss';
9
+ import './ToolHeader.css';
10
10
  const b = block('tool-header');
11
11
  export function ToolHeader(props) {
12
12
  const { toolIcon, toolName, content, actions, status, className, qa } = props;
@@ -3,7 +3,7 @@ import { Text } from '@gravity-ui/uikit';
3
3
  import { block } from '../../../utils/cn';
4
4
  import { ToolIndicator } from '../../atoms';
5
5
  import { i18n } from './i18n';
6
- import './ToolStatus.scss';
6
+ import './ToolStatus.css';
7
7
  const b = block('tool-status');
8
8
  function getIndicatorStatus(status) {
9
9
  if (status === 'success' || status === 'error' || status === 'loading') {
@@ -3,7 +3,7 @@ import type { BaseMessageProps, TAssistantMessage, TMessageContent, TMessageMeta
3
3
  import { type MessageRendererRegistry } from '../../../utils/messageTypeRegistry';
4
4
  import './AssistantMessage.scss';
5
5
  type BaseMessagePick = Pick<BaseMessageProps, 'actions' | 'timestamp' | 'showActionsOnHover' | 'showTimestamp'>;
6
- type AssistantMessagePick<TContent extends TMessageContent> = Pick<TAssistantMessage<TContent, TMessageMetadata>, 'id' | 'content'>;
6
+ type AssistantMessagePick<TContent extends TMessageContent> = Pick<TAssistantMessage<TContent, TMessageMetadata>, 'id' | 'content' | 'userRating'>;
7
7
  export type AssistantMessageProps<TContent extends TMessageContent = never> = BaseMessagePick & AssistantMessagePick<TContent> & {
8
8
  messageRendererRegistry?: MessageRendererRegistry;
9
9
  transformOptions?: OptionsType;
@@ -11,5 +11,5 @@ export type AssistantMessageProps<TContent extends TMessageContent = never> = Ba
11
11
  className?: string;
12
12
  qa?: string;
13
13
  };
14
- export declare function AssistantMessage<TContent extends TMessageContent = never>({ content, actions, timestamp, id, messageRendererRegistry, transformOptions, shouldParseIncompleteMarkdown, showActionsOnHover, showTimestamp, className, qa, }: AssistantMessageProps<TContent>): import("react/jsx-runtime").JSX.Element | null;
14
+ export declare function AssistantMessage<TContent extends TMessageContent = never>({ content, actions, timestamp, id, messageRendererRegistry, transformOptions, shouldParseIncompleteMarkdown, showActionsOnHover, showTimestamp, userRating, className, qa, }: AssistantMessageProps<TContent>): import("react/jsx-runtime").JSX.Element | null;
15
15
  export {};
@@ -5,9 +5,9 @@ import { getMessageRenderer, mergeMessageRendererRegistries, } from '../../../ut
5
5
  import { normalizeContent } from '../../../utils/messageUtils';
6
6
  import { BaseMessage } from '../../molecules/BaseMessage';
7
7
  import { createDefaultMessageRegistry } from './defaultMessageTypeRegistry';
8
- import './AssistantMessage.scss';
8
+ import './AssistantMessage.css';
9
9
  const b = block('assistant-message');
10
- export function AssistantMessage({ content, actions, timestamp, id, messageRendererRegistry, transformOptions, shouldParseIncompleteMarkdown, showActionsOnHover, showTimestamp, className, qa, }) {
10
+ export function AssistantMessage({ content, actions, timestamp, id, messageRendererRegistry, transformOptions, shouldParseIncompleteMarkdown, showActionsOnHover, showTimestamp, userRating, className, qa, }) {
11
11
  const registry = useMemo(() => {
12
12
  const defaultRegistry = createDefaultMessageRegistry(transformOptions, shouldParseIncompleteMarkdown);
13
13
  if (messageRendererRegistry) {
@@ -27,5 +27,5 @@ export function AssistantMessage({ content, actions, timestamp, id, messageRende
27
27
  const key = part.id || `${id || 'message'}-part-${partIndex}`;
28
28
  return _jsx(PartComponent, { part: part }, key);
29
29
  };
30
- return (_jsx(BaseMessage, { role: "assistant", actions: actions, showActionsOnHover: showActionsOnHover, showTimestamp: showTimestamp, timestamp: timestamp, className: b(null, className), qa: qa, children: _jsx("div", { className: b('content'), children: parts.map((part, index) => renderPart(part, index)) }) }));
30
+ return (_jsx(BaseMessage, { role: "assistant", actions: actions, showActionsOnHover: showActionsOnHover, showTimestamp: showTimestamp, timestamp: timestamp, userRating: userRating, className: b(null, className), qa: qa, children: _jsx("div", { className: b('content'), children: parts.map((part, index) => renderPart(part, index)) }) }));
31
31
  }
@@ -4,6 +4,7 @@ import type { TMessageContent } from '../../../../types/messages';
4
4
  declare const _default: Meta;
5
5
  export default _default;
6
6
  export declare const Playground: StoryFn<AssistantMessageProps>;
7
+ export declare const WithUserRating: StoryObj<AssistantMessageProps>;
7
8
  export declare const WithToolCall: StoryObj<AssistantMessageProps>;
8
9
  export declare const WithThinkingContent: StoryObj<AssistantMessageProps>;
9
10
  interface CustomMessageData {
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /* eslint-disable no-console */
3
3
  import { Pencil } from '@gravity-ui/icons';
4
4
  import { Icon, Text } from '@gravity-ui/uikit';
@@ -45,6 +45,11 @@ export default {
45
45
  control: 'boolean',
46
46
  description: 'Show timestamp in actions area',
47
47
  },
48
+ userRating: {
49
+ control: 'radio',
50
+ options: [undefined, 'like', 'dislike'],
51
+ description: 'Current user rating (filled like/unlike icons when set)',
52
+ },
48
53
  className: {
49
54
  control: 'text',
50
55
  description: 'Additional CSS class',
@@ -102,6 +107,12 @@ Playground.args = {
102
107
  timestamp: simpleMessage.timestamp,
103
108
  id: simpleMessage.id,
104
109
  };
110
+ export const WithUserRating = {
111
+ render: (args) => (_jsxs(_Fragment, { children: [_jsx(ShowcaseItem, { title: "Rated like", width: "400px", children: _jsx(AssistantMessage, Object.assign({}, args, { content: "This message has userRating: like (thumb up filled).", actions: actions, userRating: "like" })) }), _jsx(ShowcaseItem, { title: "Rated dislike", width: "400px", children: _jsx(AssistantMessage, Object.assign({}, args, { content: "This message has userRating: dislike (thumb down filled).", actions: actions, userRating: "dislike" })) })] })),
112
+ decorators: [
113
+ (Story) => (_jsx(ContentWrapper, { children: _jsx(Showcase, { children: _jsx(Story, {}) }) })),
114
+ ],
115
+ };
105
116
  export const WithToolCall = {
106
117
  render: (args) => (_jsx(ShowcaseItem, { title: "With Tool Call", children: _jsx(ContentWrapper, { width: "480px", children: _jsx(AssistantMessage, Object.assign({}, args, { content: multiPartMessage.content, actions: actions, timestamp: multiPartMessage.timestamp, id: multiPartMessage.id })) }) })),
107
118
  decorators: [
@@ -9,7 +9,7 @@ import { ButtonGroup } from '../../molecules';
9
9
  import { i18n } from './i18n';
10
10
  import { HeaderAction } from './types';
11
11
  import { useHeader } from './useHeader';
12
- import './Header.scss';
12
+ import './Header.css';
13
13
  const b = block('header');
14
14
  // Icons for base actions
15
15
  const ACTION_ICONS = {
@@ -7,7 +7,7 @@ import { Loader } from '../../atoms/Loader';
7
7
  import { AssistantMessage } from '../AssistantMessage';
8
8
  import { UserMessage } from '../UserMessage';
9
9
  import { ErrorAlert } from './ErrorAlert';
10
- import './MessageList.scss';
10
+ import './MessageList.css';
11
11
  const b = block('message-list');
12
12
  export function MessageList({ messages, messageRendererRegistry, transformOptions, shouldParseIncompleteMarkdown, showActionsOnHover, showTimestamp, showAvatar, userActions, assistantActions, loaderStatuses = ['submitted', 'streaming_loading'], className, qa, status, errorMessage, onRetry, hasPreviousMessages = false, onLoadPreviousMessages, }) {
13
13
  const isStreaming = status === 'streaming' || status === 'streaming_loading';
@@ -34,7 +34,7 @@ export function MessageList({ messages, messageRendererRegistry, transformOption
34
34
  const actions = showActions && !hasOnlyThinkingContent(message.content)
35
35
  ? resolveMessageActions(message, assistantActions)
36
36
  : undefined;
37
- return (_jsx(AssistantMessage, { content: message.content, actions: actions, timestamp: message.timestamp, id: message.id, messageRendererRegistry: messageRendererRegistry, transformOptions: transformOptions, shouldParseIncompleteMarkdown: shouldParseIncompleteMarkdown, showActionsOnHover: showActionsOnHover, showTimestamp: showTimestamp }, message.id || `message-${index}`));
37
+ return (_jsx(AssistantMessage, { content: message.content, actions: actions, timestamp: message.timestamp, id: message.id, messageRendererRegistry: messageRendererRegistry, transformOptions: transformOptions, shouldParseIncompleteMarkdown: shouldParseIncompleteMarkdown, showActionsOnHover: showActionsOnHover, showTimestamp: showTimestamp, userRating: message.userRating }, message.id || `message-${index}`));
38
38
  }
39
39
  return null;
40
40
  };
@@ -22,4 +22,5 @@ type ChartMessageContent = TMessageContent<'chart', ChartMessageData>;
22
22
  export declare const WithCustomMessageType: StoryObj<MessageListProps<ChartMessageContent>>;
23
23
  export declare const WithStreamingMessage: StoryObj<MessageListProps>;
24
24
  export declare const WithDefaultActions: StoryObj<MessageListProps>;
25
+ export declare const WithUserRating: StoryObj<MessageListProps>;
25
26
  export declare const WithPreviousMessages: StoryObj<MessageListProps>;
@@ -223,14 +223,14 @@ export const WithDefaultActions = {
223
223
  render: (args) => {
224
224
  const userActions = [
225
225
  {
226
- actionType: BaseMessageActionType.Edit,
226
+ type: BaseMessageActionType.Edit,
227
227
  onClick: (message) => {
228
228
  // eslint-disable-next-line no-console
229
229
  console.log('Edit user message:', message.id);
230
230
  },
231
231
  },
232
232
  {
233
- actionType: BaseMessageActionType.Delete,
233
+ type: BaseMessageActionType.Delete,
234
234
  onClick: (message) => {
235
235
  // eslint-disable-next-line no-console
236
236
  console.log('Delete user message:', message.id);
@@ -239,14 +239,14 @@ export const WithDefaultActions = {
239
239
  ];
240
240
  const assistantActions = [
241
241
  {
242
- actionType: BaseMessageActionType.Copy,
242
+ type: BaseMessageActionType.Copy,
243
243
  onClick: (message) => {
244
244
  // eslint-disable-next-line no-console
245
245
  console.log('Copy assistant message:', message.id);
246
246
  },
247
247
  },
248
248
  {
249
- actionType: BaseMessageActionType.Like,
249
+ type: BaseMessageActionType.Like,
250
250
  onClick: (message) => {
251
251
  // eslint-disable-next-line no-console
252
252
  console.log('Like assistant message:', message.id);
@@ -291,6 +291,56 @@ export const WithDefaultActions = {
291
291
  },
292
292
  decorators: defaultDecorators,
293
293
  };
294
+ export const WithUserRating = {
295
+ render: (args) => {
296
+ const assistantActions = [
297
+ {
298
+ type: BaseMessageActionType.Copy,
299
+ onClick: (message) => {
300
+ // eslint-disable-next-line no-console
301
+ console.log('Copy:', message.id);
302
+ },
303
+ },
304
+ {
305
+ type: BaseMessageActionType.Like,
306
+ onClick: (message) => {
307
+ // eslint-disable-next-line no-console
308
+ console.log('Like:', message.id);
309
+ },
310
+ },
311
+ {
312
+ type: BaseMessageActionType.Unlike,
313
+ onClick: (message) => {
314
+ // eslint-disable-next-line no-console
315
+ console.log('Unlike:', message.id);
316
+ },
317
+ },
318
+ ];
319
+ return (_jsx(ShowcaseItem, { title: "With User Rating", children: _jsx(ContentWrapper, { width: "480px", children: _jsx(MessageList, Object.assign({}, args, { messages: [
320
+ {
321
+ id: 'user-1',
322
+ role: 'user',
323
+ timestamp: '2024-01-01T00:00:00Z',
324
+ content: 'Compare these two answers.',
325
+ },
326
+ {
327
+ id: 'assistant-like',
328
+ role: 'assistant',
329
+ timestamp: '2024-01-01T00:00:01Z',
330
+ content: 'This message is rated as liked (filled thumb up).',
331
+ userRating: 'like',
332
+ },
333
+ {
334
+ id: 'assistant-dislike',
335
+ role: 'assistant',
336
+ timestamp: '2024-01-01T00:00:02Z',
337
+ content: 'This message is rated as disliked (filled thumb down).',
338
+ userRating: 'dislike',
339
+ },
340
+ ], assistantActions: assistantActions, status: "ready" })) }) }));
341
+ },
342
+ decorators: defaultDecorators,
343
+ };
294
344
  export const WithPreviousMessages = {
295
345
  render: (args) => {
296
346
  const createMessage = (num) => {
@@ -4,7 +4,7 @@ import { PromptInputSimple } from './PromptInputSimple';
4
4
  import { PromptInputWithPanels } from './PromptInputWithPanels';
5
5
  import { PromptInputWithSuggestions } from './PromptInputWithSuggestions';
6
6
  import { usePromptInput } from './usePromptInput';
7
- import './PromptInput.scss';
7
+ import './PromptInput.css';
8
8
  /**
9
9
  * PromptInput component - a flexible input component for chat interfaces
10
10
  * with support for simple and full views, attachments, suggestions, and expandable panels
@@ -7,7 +7,7 @@ import { block } from '../../../utils/cn';
7
7
  import { ActionButton } from '../../atoms';
8
8
  import { Loader } from '../../atoms/Loader';
9
9
  import { useThinkingMessage } from './useThinkingMessage';
10
- import './ThinkingMessage.scss';
10
+ import './ThinkingMessage.css';
11
11
  const b = block('thinking-message');
12
12
  /**
13
13
  * ThinkingMessage component displays AI model's internal reasoning process.
@@ -3,7 +3,7 @@ import { Card } from '@gravity-ui/uikit';
3
3
  import { useToolMessage } from '../../../hooks/useToolMessage';
4
4
  import { block } from '../../../utils/cn';
5
5
  import { ToolFooter, ToolHeader } from '../../molecules';
6
- import './ToolMessage.scss';
6
+ import './ToolMessage.css';
7
7
  const b = block('tool-message');
8
8
  export function ToolMessage(props) {
9
9
  const { toolName, className, qa, bodyContent, status, toolIcon, headerContent } = props;
@@ -4,7 +4,7 @@ import { block, modsClassName } from '../../../utils/cn';
4
4
  import { MarkdownRenderer } from '../../atoms/MarkdownRenderer';
5
5
  import { MessageBalloon } from '../../atoms/MessageBalloon';
6
6
  import { BaseMessage } from '../../molecules/BaseMessage';
7
- import './UserMessage.scss';
7
+ import './UserMessage.css';
8
8
  const b = block('user-message');
9
9
  export const UserMessage = (props) => {
10
10
  const { className, qa, content, actions, showActionsOnHover, showAvatar, avatarUrl = '', timestamp = '', showTimestamp, format = 'plain', transformOptions, shouldParseIncompleteMarkdown, } = props;
@@ -8,7 +8,7 @@ import { ChatContent } from '../../templates/ChatContent';
8
8
  import { History } from '../../templates/History';
9
9
  import { i18n } from './i18n';
10
10
  import { useChatContainer } from './useChatContainer';
11
- import './ChatContainer.scss';
11
+ import './ChatContainer.css';
12
12
  const b = block('chat-container');
13
13
  /**
14
14
  * ChatContainer - fully assembled chat component, the main exportable component of the library.
@@ -83,3 +83,8 @@ export declare const EmbeddedInPageWithStreaming: Story;
83
83
  * Demonstrates passing additional actions to Header and custom actions to BaseMessage
84
84
  */
85
85
  export declare const WithAdditionalActions: Story;
86
+ /**
87
+ * Like / Unlike actions with local rating state.
88
+ * Only like and unlike actions; rating toggles on repeated click (like → clear, dislike → clear).
89
+ */
90
+ export declare const WithLikeUnlikeActions: Story;
@@ -5,6 +5,7 @@ import { Gear, Sparkles, Star } from '@gravity-ui/icons';
5
5
  import { Icon } from '@gravity-ui/uikit';
6
6
  import { ChatContainer } from '..';
7
7
  import { ContentWrapper } from '../../../../demo/ContentWrapper';
8
+ import { BaseMessageActionType } from '../../../molecules/BaseMessage';
8
9
  import MDXDocs from './Docs.mdx';
9
10
  export default {
10
11
  title: 'pages/ChatContainer',
@@ -1110,3 +1111,79 @@ export const WithAdditionalActions = {
1110
1111
  },
1111
1112
  decorators: defaultDecorators,
1112
1113
  };
1114
+ /**
1115
+ * Like / Unlike actions with local rating state.
1116
+ * Only like and unlike actions; rating toggles on repeated click (like → clear, dislike → clear).
1117
+ */
1118
+ export const WithLikeUnlikeActions = {
1119
+ args: {
1120
+ chats: mockChats,
1121
+ activeChat: mockChats[0],
1122
+ showHistory: true,
1123
+ showNewChat: true,
1124
+ showActionsOnHover: true,
1125
+ },
1126
+ render: (args) => {
1127
+ const initialChat = mockChats[0];
1128
+ const [messages, setMessages] = useState(() => mockChatMessages[initialChat.id] || []);
1129
+ const [status, setStatus] = useState('ready');
1130
+ const [activeChat, setActiveChat] = useState(initialChat);
1131
+ const assistantActions = [
1132
+ {
1133
+ type: BaseMessageActionType.Like,
1134
+ onClick: (message) => {
1135
+ setMessages((prev) => prev.map((m) => m.id === message.id
1136
+ ? Object.assign(Object.assign({}, m), { userRating: m.userRating === 'like'
1137
+ ? undefined
1138
+ : 'like' }) : m));
1139
+ },
1140
+ },
1141
+ {
1142
+ type: BaseMessageActionType.Unlike,
1143
+ onClick: (message) => {
1144
+ setMessages((prev) => prev.map((m) => m.id === message.id
1145
+ ? Object.assign(Object.assign({}, m), { userRating: m.userRating === 'dislike'
1146
+ ? undefined
1147
+ : 'dislike' }) : m));
1148
+ },
1149
+ },
1150
+ ];
1151
+ const handleSendMessage = async (data) => {
1152
+ const timestamp = Date.now();
1153
+ const userMessageId = `user-${timestamp}`;
1154
+ const userMessage = {
1155
+ id: userMessageId,
1156
+ role: 'user',
1157
+ content: data.content,
1158
+ timestamp: new Date().toISOString(),
1159
+ };
1160
+ setMessages((prev) => [...prev, userMessage]);
1161
+ setStatus('streaming');
1162
+ await new Promise((resolve) => setTimeout(resolve, 500));
1163
+ const assistantMessageId = `assistant-${timestamp + 1}`;
1164
+ const assistantMessage = {
1165
+ id: assistantMessageId,
1166
+ role: 'assistant',
1167
+ content: `Response to: "${data.content}". Use like/unlike below — they toggle on second click.`,
1168
+ timestamp: new Date().toISOString(),
1169
+ };
1170
+ setMessages((prev) => [...prev, assistantMessage]);
1171
+ setStatus('ready');
1172
+ };
1173
+ const handleSelectChat = (chat) => {
1174
+ setActiveChat(chat);
1175
+ setMessages(mockChatMessages[chat.id] || []);
1176
+ };
1177
+ const handleCreateChat = () => {
1178
+ setActiveChat(null);
1179
+ setMessages([]);
1180
+ };
1181
+ const handleCancel = async () => {
1182
+ setStatus('ready');
1183
+ };
1184
+ return (_jsx(ChatContainer, Object.assign({}, args, { messages: messages, activeChat: activeChat, onSendMessage: handleSendMessage, onCancel: handleCancel, onSelectChat: handleSelectChat, onCreateChat: handleCreateChat, status: status, messageListConfig: {
1185
+ assistantActions,
1186
+ } })));
1187
+ },
1188
+ decorators: defaultDecorators,
1189
+ };
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { block } from '../../../utils/cn';
3
3
  import { MessageList } from '../../organisms/MessageList';
4
4
  import { EmptyContainer } from '../EmptyContainer';
5
- import './ChatContent.scss';
5
+ import './ChatContent.css';
6
6
  const b = block('chat-content');
7
7
  /**
8
8
  * ChatContent - main chat content with state switching (EmptyContainer/MessageList).
@@ -4,7 +4,7 @@ import { Button, Text } from '@gravity-ui/uikit';
4
4
  import { block } from '../../../utils/cn';
5
5
  import { Suggestions } from '../../molecules/Suggestions';
6
6
  import { i18n } from './i18n';
7
- import './EmptyContainer.scss';
7
+ import './EmptyContainer.css';
8
8
  const b = block('empty-container');
9
9
  /**
10
10
  * EmptyContainer component - displays a welcome screen with image, title, description,
@@ -7,7 +7,7 @@ import { Loader } from '../../atoms/Loader';
7
7
  import { ChatItem } from './ChatItem';
8
8
  import { DateHeaderItem } from './DateHeaderItem';
9
9
  import { i18n } from './i18n';
10
- import './History.scss';
10
+ import './History.css';
11
11
  const b = block('history');
12
12
  /**
13
13
  * HistoryList component - displays a list of chats with search, grouping, and actions
@@ -1,7 +1,7 @@
1
1
  import { __rest } from "tslib";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { cn } from '../../utils/cn';
4
- import './ContentWrapper.scss';
4
+ import './ContentWrapper.css';
5
5
  const b = cn('content-wrapper');
6
6
  export function ContentWrapper(props) {
7
7
  const { children } = props, style = __rest(props, ["children"]);
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../../utils/cn';
3
- import './Showcase.scss';
3
+ import './Showcase.css';
4
4
  const b = cn('showcase');
5
5
  export function Showcase({ title, description, direction = 'row', className, children }) {
6
6
  return (_jsxs("div", { className: b({ direction }, className), children: [title && _jsx("div", { className: b('title'), children: title }), description && _jsx("div", { className: b('description'), children: description }), _jsx("div", { className: b('content'), children: children })] }));
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../../utils/cn';
3
- import './ShowcaseItem.scss';
3
+ import './ShowcaseItem.css';
4
4
  const b = cn('showcase-item');
5
5
  export function ShowcaseItem({ title, children, width }) {
6
6
  return (_jsxs("div", { className: b(), style: { width }, children: [_jsx("div", { className: b('title'), children: title }), _jsx("div", { className: b('content'), children: children })] }));
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { block } from '../../utils/cn';
3
- import './SwapArea.scss';
3
+ import './SwapArea.css';
4
4
  const b = block('swap-area');
5
5
  export const SwapArea = () => {
6
6
  return _jsx("div", { className: b(), children: "Swap Area" });
@@ -8,10 +8,12 @@ export type BaseMessageActionConfig = ActionConfig;
8
8
  * - React.ReactNode for fully custom content
9
9
  */
10
10
  export type BaseMessageAction = BaseMessageActionConfig | React.ReactNode;
11
+ export type UserRating = 'like' | 'dislike';
11
12
  export type BaseMessageProps = {
12
13
  children: React.ReactNode;
13
14
  role: TMessageRole;
14
15
  actions?: BaseMessageAction[];
16
+ userRating?: UserRating;
15
17
  timestamp?: string;
16
18
  showTimestamp?: boolean;
17
19
  showActionsOnHover?: boolean;
@@ -65,5 +67,6 @@ export type TUserMessage<Metadata = TMessageMetadata> = TBaseMessage<Metadata> &
65
67
  export type TAssistantMessage<TCustomMessageContent extends TMessageContent = never, Metadata = TMessageMetadata> = TBaseMessage<Metadata> & {
66
68
  role: 'assistant';
67
69
  content: TAssistantMessageContent<TCustomMessageContent>;
70
+ userRating?: UserRating;
68
71
  };
69
72
  export type TChatMessage<TCustomMessageContent extends TMessageContent = never, Metadata = TMessageMetadata> = TUserMessage<Metadata> | TAssistantMessage<TCustomMessageContent, Metadata>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/aikit",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Gravity UI base kit for building ai assistant chats",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -55,10 +55,11 @@
55
55
  "lint:prettier": "prettier --check '**/*.{js,jsx,ts,tsx,css,scss,json,yaml,yml,md,mdx}'",
56
56
  "lint:prettier:fix": "prettier --write '**/*.{js,jsx,ts,tsx,css,scss,json,yaml,yml,md,mdx}'",
57
57
  "test": "npm run test:unit && npm run test:server:unit",
58
- "build": "npm run build:clean && npm run build:ts && npm run build:compile:scss && npm run build:copy && npm run build:server",
58
+ "build": "npm run build:clean && npm run build:ts && npm run build:fix-imports && npm run build:compile:scss && npm run build:copy && npm run build:server",
59
59
  "build:clean": "rm -rf dist",
60
60
  "build:ts": "tsc",
61
61
  "build:compile:scss": "sass --no-source-map --load-path=src --load-path=node_modules src:dist",
62
+ "build:fix-imports": "find dist -type f -name '*.js' -exec perl -i -pe \"s/\\.scss'/.css'/g; s/\\.scss\\\"/.css\\\"/g\" {} +",
62
63
  "build:copy": "npm run build:copy:themes && npm run build:copy:i18n",
63
64
  "build:copy:themes": "copyfiles -u 1 'src/themes/**/*.css' dist",
64
65
  "build:copy:i18n": "copyfiles -u 1 'src/**/i18n/*.json' dist",