@gravity-ui/aikit 2.25.1-beta.c31bfaac02b26694749dd7a5d2532192c8e5fdc2.0 → 2.26.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.
@@ -10,6 +10,11 @@ const cn_1 = require("../../../utils/cn.js");
10
10
  const ActionButton_1 = require("../../atoms/ActionButton/index.js");
11
11
  const i18n_1 = require("./i18n/index.js");
12
12
  const b = (0, cn_1.block)('history');
13
+ function handleActionKeyDown(event) {
14
+ if (event.key === ' ' || event.key === 'Enter') {
15
+ event.stopPropagation();
16
+ }
17
+ }
13
18
  /**
14
19
  * Chat item component with hover state for actions.
15
20
  * In mobile mode there is no hover, so actions are always visible.
@@ -53,13 +58,11 @@ function ChatItem({ chat, showActions, isActive, onDeleteClick, size, pinned, on
53
58
  active: isActive,
54
59
  mobile: isMobile,
55
60
  ['is-delete-processing']: isDeleteProccesing,
56
- }), onClick: handleClick, children: [pinned && ((0, jsx_runtime_1.jsx)("span", { role: "img", "aria-label": (0, i18n_1.i18n)('pinned'), children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Pin, size: 16 }) })), hasOverflow ? ((0, jsx_runtime_1.jsx)(uikit_1.Tooltip, { content: chatLabel, openDelay: 300, children: label })) : (label), showDeleteAction ? ((0, jsx_runtime_1.jsx)(ActionButton_1.ActionButton, { view: "flat", size: deleteButtonSize, color: "secondary", loading: isDeleteProccesing, className: b('delete-button'), onClick: handleDeleteChat, tooltipTitle: (0, i18n_1.i18n)('tooltip-delete'), children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { className: b('icon-button'), data: icons_1.TrashBin, size: deleteIconSize }) })) : null, showActions && onPinnedChange && ((0, jsx_runtime_1.jsx)(ActionButton_1.ActionButton, { view: "flat", size: deleteButtonSize, disabled: pinningDisabled || isDeleteProccesing, "aria-label": (0, i18n_1.i18n)(pinned ? 'action-unpin' : 'action-pin'), tooltipTitle: (0, i18n_1.i18n)(pinned ? 'action-unpin' : 'action-pin'), onKeyDown: (event) => {
57
- if (event.key === ' ' || event.key === 'Enter') {
58
- event.stopPropagation();
59
- }
60
- }, onClick: (event) => {
61
+ }), onClick: handleClick, children: [pinned && ((0, jsx_runtime_1.jsx)("span", { role: "img", "aria-label": (0, i18n_1.i18n)('pinned'), children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Pin, size: 16 }) })), hasOverflow ? ((0, jsx_runtime_1.jsx)(uikit_1.Tooltip, { content: chatLabel, openDelay: 300, children: label })) : (label), showDeleteAction ? ((0, jsx_runtime_1.jsx)(ActionButton_1.ActionButton, { view: "flat", size: deleteButtonSize, color: "secondary", loading: isDeleteProccesing, className: b('delete-button'), "aria-label": (0, i18n_1.i18n)('tooltip-delete'), onClick: handleDeleteChat, onKeyDown: handleActionKeyDown, tooltipTitle: (0, i18n_1.i18n)('tooltip-delete'), children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { className: b('icon-button'), data: icons_1.TrashBin, size: deleteIconSize }) })) : null, showActions && onPinnedChange && ((0, jsx_runtime_1.jsx)(ActionButton_1.ActionButton, { view: "flat", size: deleteButtonSize, "aria-disabled": Boolean(pinningDisabled || isDeleteProccesing), className: b('pin-button'), "aria-label": (0, i18n_1.i18n)(pinned ? 'action-unpin' : 'action-pin'), tooltipTitle: (0, i18n_1.i18n)(pinned ? 'action-unpin' : 'action-pin'), onKeyDown: handleActionKeyDown, onClick: (event) => {
61
62
  event.stopPropagation();
62
- onPinnedChange(chat.id, !pinned);
63
+ if (!pinningDisabled && !isDeleteProccesing) {
64
+ onPinnedChange(chat.id, !pinned);
65
+ }
63
66
  }, children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: pinned ? icons_1.PinSlash : icons_1.Pin, size: deleteIconSize }) }))] }));
64
67
  }
65
68
  //# sourceMappingURL=ChatItem.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatItem.js","sourceRoot":"../../../../../src","sources":["components/templates/History/ChatItem.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useEffect, useRef, useState} from 'react';\n\nimport {Pin, PinSlash, TrashBin} from '@gravity-ui/icons';\nimport {Icon, InputControlSize, Text, Tooltip, useMobile} from '@gravity-ui/uikit';\n\nimport {getControlIconSize, resolveMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {ChatType} from '../../../types';\nimport {block} from '../../../utils/cn';\nimport {ActionButton} from '../../atoms/ActionButton';\n\nimport {i18n} from './i18n';\n\nconst b = block('history');\n\n/**\n * Props for ChatItem component\n */\nexport interface ChatItemProps {\n chat: ChatType;\n showActions: boolean;\n pinned?: boolean;\n onPinnedChange?: (chatId: string, pinned: boolean) => void;\n pinningDisabled?: boolean;\n isActive?: boolean;\n onDeleteClick?: (e: React.MouseEvent, chat: ChatType) => Promise<void>;\n /** Size of the row actions (default: `s`, `xl` in mobile mode) */\n size?: InputControlSize;\n}\n\n/**\n * Chat item component with hover state for actions.\n * In mobile mode there is no hover, so actions are always visible.\n *\n * @returns React element\n */\nexport function ChatItem({\n chat,\n showActions,\n isActive,\n onDeleteClick,\n size,\n pinned,\n onPinnedChange,\n pinningDisabled,\n}: ChatItemProps) {\n const [isDeleteProccesing, setIsDeleteProcessing] = useState(false);\n const [hasOverflow, setHasOverflow] = useState(false);\n const labelRef = useRef<HTMLDivElement>(null);\n const chatLabel = chat.lastMessage || chat.name;\n const isMobile = useMobile();\n const deleteButtonSize = resolveMobileControlSize({\n size,\n desktopDefault: 's',\n mobileDefault: 'xl',\n isMobile,\n });\n const deleteIconSize = getControlIconSize(deleteButtonSize);\n\n useEffect(() => {\n const label = labelRef.current;\n setHasOverflow(Boolean(label && label.scrollWidth > label.clientWidth));\n }, [chatLabel, pinned, showActions, onPinnedChange]);\n\n const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {\n if (isDeleteProccesing) {\n e.stopPropagation();\n }\n };\n\n const handleDeleteChat = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {\n try {\n setIsDeleteProcessing(true);\n await onDeleteClick?.(e, chat);\n } finally {\n setIsDeleteProcessing(false);\n }\n };\n\n const showDeleteAction = isDeleteProccesing || (showActions && onDeleteClick);\n\n const label = (\n <div className={b('chat-content')}>\n <Text variant=\"body-1\" ref={labelRef}>\n {chatLabel}\n </Text>\n </div>\n );\n\n return (\n <div\n className={b('chat-item', {\n active: isActive,\n mobile: isMobile,\n ['is-delete-processing']: isDeleteProccesing,\n })}\n onClick={handleClick}\n >\n {pinned && (\n <span role=\"img\" aria-label={i18n('pinned')}>\n <Icon data={Pin} size={16} />\n </span>\n )}\n {hasOverflow ? (\n <Tooltip content={chatLabel} openDelay={300}>\n {label}\n </Tooltip>\n ) : (\n label\n )}\n\n {showDeleteAction ? (\n <ActionButton\n view=\"flat\"\n size={deleteButtonSize}\n color=\"secondary\"\n loading={isDeleteProccesing}\n className={b('delete-button')}\n onClick={handleDeleteChat}\n tooltipTitle={i18n('tooltip-delete')}\n >\n <Icon className={b('icon-button')} data={TrashBin} size={deleteIconSize} />\n </ActionButton>\n ) : null}\n {showActions && onPinnedChange && (\n <ActionButton\n view=\"flat\"\n size={deleteButtonSize}\n disabled={pinningDisabled || isDeleteProccesing}\n aria-label={i18n(pinned ? 'action-unpin' : 'action-pin')}\n tooltipTitle={i18n(pinned ? 'action-unpin' : 'action-pin')}\n onKeyDown={(event) => {\n if (event.key === ' ' || event.key === 'Enter') {\n event.stopPropagation();\n }\n }}\n onClick={(event) => {\n event.stopPropagation();\n onPinnedChange(chat.id, !pinned);\n }}\n >\n <Icon data={pinned ? PinSlash : Pin} size={deleteIconSize} />\n </ActionButton>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"ChatItem.js","sourceRoot":"../../../../../src","sources":["components/templates/History/ChatItem.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useEffect, useRef, useState} from 'react';\n\nimport {Pin, PinSlash, TrashBin} from '@gravity-ui/icons';\nimport {Icon, InputControlSize, Text, Tooltip, useMobile} from '@gravity-ui/uikit';\n\nimport {getControlIconSize, resolveMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {ChatType} from '../../../types';\nimport {block} from '../../../utils/cn';\nimport {ActionButton} from '../../atoms/ActionButton';\n\nimport {i18n} from './i18n';\n\nconst b = block('history');\n\nfunction handleActionKeyDown(event: React.KeyboardEvent<HTMLButtonElement>) {\n if (event.key === ' ' || event.key === 'Enter') {\n event.stopPropagation();\n }\n}\n\n/**\n * Props for ChatItem component\n */\nexport interface ChatItemProps {\n chat: ChatType;\n showActions: boolean;\n pinned?: boolean;\n onPinnedChange?: (chatId: string, pinned: boolean) => void;\n pinningDisabled?: boolean;\n isActive?: boolean;\n onDeleteClick?: (e: React.MouseEvent, chat: ChatType) => Promise<void>;\n /** Size of the row actions (default: `s`, `xl` in mobile mode) */\n size?: InputControlSize;\n}\n\n/**\n * Chat item component with hover state for actions.\n * In mobile mode there is no hover, so actions are always visible.\n *\n * @returns React element\n */\nexport function ChatItem({\n chat,\n showActions,\n isActive,\n onDeleteClick,\n size,\n pinned,\n onPinnedChange,\n pinningDisabled,\n}: ChatItemProps) {\n const [isDeleteProccesing, setIsDeleteProcessing] = useState(false);\n const [hasOverflow, setHasOverflow] = useState(false);\n const labelRef = useRef<HTMLDivElement>(null);\n const chatLabel = chat.lastMessage || chat.name;\n const isMobile = useMobile();\n const deleteButtonSize = resolveMobileControlSize({\n size,\n desktopDefault: 's',\n mobileDefault: 'xl',\n isMobile,\n });\n const deleteIconSize = getControlIconSize(deleteButtonSize);\n\n useEffect(() => {\n const label = labelRef.current;\n setHasOverflow(Boolean(label && label.scrollWidth > label.clientWidth));\n }, [chatLabel, pinned, showActions, onPinnedChange]);\n\n const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {\n if (isDeleteProccesing) {\n e.stopPropagation();\n }\n };\n\n const handleDeleteChat = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {\n try {\n setIsDeleteProcessing(true);\n await onDeleteClick?.(e, chat);\n } finally {\n setIsDeleteProcessing(false);\n }\n };\n\n const showDeleteAction = isDeleteProccesing || (showActions && onDeleteClick);\n\n const label = (\n <div className={b('chat-content')}>\n <Text variant=\"body-1\" ref={labelRef}>\n {chatLabel}\n </Text>\n </div>\n );\n\n return (\n <div\n className={b('chat-item', {\n active: isActive,\n mobile: isMobile,\n ['is-delete-processing']: isDeleteProccesing,\n })}\n onClick={handleClick}\n >\n {pinned && (\n <span role=\"img\" aria-label={i18n('pinned')}>\n <Icon data={Pin} size={16} />\n </span>\n )}\n {hasOverflow ? (\n <Tooltip content={chatLabel} openDelay={300}>\n {label}\n </Tooltip>\n ) : (\n label\n )}\n\n {showDeleteAction ? (\n <ActionButton\n view=\"flat\"\n size={deleteButtonSize}\n color=\"secondary\"\n loading={isDeleteProccesing}\n className={b('delete-button')}\n aria-label={i18n('tooltip-delete')}\n onClick={handleDeleteChat}\n onKeyDown={handleActionKeyDown}\n tooltipTitle={i18n('tooltip-delete')}\n >\n <Icon className={b('icon-button')} data={TrashBin} size={deleteIconSize} />\n </ActionButton>\n ) : null}\n {showActions && onPinnedChange && (\n <ActionButton\n view=\"flat\"\n size={deleteButtonSize}\n aria-disabled={Boolean(pinningDisabled || isDeleteProccesing)}\n className={b('pin-button')}\n aria-label={i18n(pinned ? 'action-unpin' : 'action-pin')}\n tooltipTitle={i18n(pinned ? 'action-unpin' : 'action-pin')}\n onKeyDown={handleActionKeyDown}\n onClick={(event) => {\n event.stopPropagation();\n if (!pinningDisabled && !isDeleteProccesing) {\n onPinnedChange(chat.id, !pinned);\n }\n }}\n >\n <Icon data={pinned ? PinSlash : Pin} size={deleteIconSize} />\n </ActionButton>\n )}\n </div>\n );\n}\n"]}
@@ -43,8 +43,14 @@
43
43
  flex: 1;
44
44
  min-width: 0;
45
45
  }
46
- .g-aikit-history__chat-item_active .g-aikit-history__delete-button, .g-aikit-history__chat-item_is-delete-processing .g-aikit-history__delete-button, .g-aikit-history__chat-item_mobile .g-aikit-history__delete-button {
47
- display: block;
46
+ .g-aikit-history__chat-item:hover .g-aikit-history__delete-button,
47
+ .g-aikit-history__chat-item:hover .g-aikit-history__pin-button, .g-aikit-history__chat-item:focus-within .g-aikit-history__delete-button,
48
+ .g-aikit-history__chat-item:focus-within .g-aikit-history__pin-button, .g-aikit-history__chat-item_active .g-aikit-history__delete-button,
49
+ .g-aikit-history__chat-item_active .g-aikit-history__pin-button, .g-aikit-history__chat-item_is-delete-processing .g-aikit-history__delete-button,
50
+ .g-aikit-history__chat-item_is-delete-processing .g-aikit-history__pin-button, .g-aikit-history__chat-item_mobile .g-aikit-history__delete-button,
51
+ .g-aikit-history__chat-item_mobile .g-aikit-history__pin-button {
52
+ opacity: 1;
53
+ pointer-events: auto;
48
54
  }
49
55
  .g-aikit-history__chat-item_is-delete-processing {
50
56
  color: var(--g-color-text-secondary);
@@ -66,9 +72,17 @@
66
72
  .g-aikit-history__icon-button {
67
73
  color: var(--g-color-text-secondary);
68
74
  }
75
+ .g-aikit-history__delete-button, .g-aikit-history__pin-button {
76
+ opacity: 0;
77
+ pointer-events: none;
78
+ flex-shrink: 0;
79
+ }
80
+ .g-aikit-history__pin-button[aria-disabled=true] {
81
+ color: var(--g-color-text-hint);
82
+ cursor: default;
83
+ }
69
84
  .g-aikit-history__delete-button {
70
85
  margin-left: calc(var(--g-spacing-base) * 2);
71
- display: none;
72
86
  }
73
87
  .g-aikit-history__empty {
74
88
  display: flex;
@@ -1 +1 @@
1
- {"version":"2.25.1-beta.c31bfaac02b26694749dd7a5d2532192c8e5fdc2.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
1
+ {"version":"2.26.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
@@ -16,6 +16,9 @@ function normalizeContent(content) {
16
16
  return [];
17
17
  }
18
18
  if (typeof content === 'string') {
19
+ if (!content.trim()) {
20
+ return [];
21
+ }
19
22
  return [
20
23
  {
21
24
  type: 'text',
@@ -25,10 +28,18 @@ function normalizeContent(content) {
25
28
  },
26
29
  ];
27
30
  }
28
- if (Array.isArray(content)) {
29
- return content;
30
- }
31
- return [content];
31
+ const parts = Array.isArray(content) ? content : [content];
32
+ return parts.filter((part) => {
33
+ const data = part.data;
34
+ if (part.type !== 'text' ||
35
+ typeof data !== 'object' ||
36
+ data === null ||
37
+ !('text' in data) ||
38
+ typeof data.text !== 'string') {
39
+ return true;
40
+ }
41
+ return data.text.trim().length > 0;
42
+ });
32
43
  }
33
44
  /**
34
45
  * Check if message content contains ONLY thinking content (no other content types).
@@ -1 +1 @@
1
- {"version":3,"file":"messageUtils.js","sourceRoot":"../../../src","sources":["utils/messageUtils.ts"],"names":[],"mappings":";;AAaA,sCAKC;AAED,gDAOC;AAED,4CAuBC;AAOD,wDAQC;AAKD,sDA4BC;AAvFD,SAAgB,aAAa,CAG3B,OAAsD;IACpD,OAAO,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC;AACnC,CAAC;AAED,SAAgB,kBAAkB,CAI9B,OAAsD;IAEtD,OAAO,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC;AACxC,CAAC;AAED,SAAgB,gBAAgB,CAC5B,OAA8E;IAE9E,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACd,CAAC;IAED,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO;YACH;gBACI,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE;oBACF,IAAI,EAAE,OAAO;iBAChB;aACJ;SACJ,CAAC;IACN,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,OAAO,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,SAAgB,sBAAsB,CAClC,OAA8E;IAE9E,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;AAC3D,CAAC;AAKD,SAAgB,qBAAqB,CAGjC,OAAiB,EACjB,cAAiD;IAEjD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO,OAAO,CAAC,OAAO,CAAC;IAC3B,CAAC;IAED,IAAI,cAAc,EAAE,CAAC;QACjB,OAAO,cAAc,CAAC,GAAG,CACrB,CAAC,MAAM,EAA2B,EAAE,CAAC,CAAC;YAClC,UAAU,EAAE,MAAM,CAAC,IAAI;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EACJ,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU;gBACjC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAC1B,CAAC,CAAC,MAAM,CAAC,QAAQ;YACzB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;YACtC,KAAK,EAAE,MAAM,CAAC,KAAmC;SACpD,CAAC,CACL,CAAC;IACN,CAAC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC","sourcesContent":["import type {\n ActionPopupConfig,\n BaseMessageAction,\n BaseMessageActionConfig,\n DefaultMessageAction,\n TAssistantMessage,\n TChatMessage,\n TMessageContent,\n TMessageContentUnion,\n TMessageMetadata,\n TUserMessage,\n} from '../types';\n\nexport function isUserMessage<\n Metadata = TMessageMetadata,\n TCustomMessageContent extends TMessageContent = never,\n>(message: TChatMessage<TCustomMessageContent, Metadata>): message is TUserMessage<Metadata> {\n return message.role === 'user';\n}\n\nexport function isAssistantMessage<\n Metadata = TMessageMetadata,\n TCustomMessageContent extends TMessageContent = never,\n>(\n message: TChatMessage<TCustomMessageContent, Metadata>,\n): message is TAssistantMessage<TCustomMessageContent, Metadata> {\n return message.role === 'assistant';\n}\n\nexport function normalizeContent<TCustomMessageContent extends TMessageContent = never>(\n content: TAssistantMessage<TCustomMessageContent, TMessageMetadata>['content'],\n): TMessageContentUnion<TCustomMessageContent>[] {\n if (!content) {\n return [];\n }\n\n if (typeof content === 'string') {\n return [\n {\n type: 'text',\n data: {\n text: content,\n },\n },\n ];\n }\n\n if (Array.isArray(content)) {\n return content;\n }\n\n return [content];\n}\n\n/**\n * Check if message content contains ONLY thinking content (no other content types).\n * @param content - Message content to check\n * @returns true if content contains only thinking parts and nothing else\n */\nexport function hasOnlyThinkingContent<TCustomMessageContent extends TMessageContent = never>(\n content: TAssistantMessage<TCustomMessageContent, TMessageMetadata>['content'],\n): boolean {\n const parts = normalizeContent(content);\n if (parts.length === 0) {\n return false;\n }\n return parts.every((part) => part.type === 'thinking');\n}\n\n// Re-export ActionPopup types for convenience\nexport type {ActionPopupContext, ActionPopupConfig} from '../types';\n\nexport function resolveMessageActions<\n TMessage extends TChatMessage<TMessageContent, TMessageMetadata>,\n>(\n message: TMessage,\n defaultActions?: DefaultMessageAction<TMessage>[],\n): BaseMessageAction[] | undefined {\n if (message.actions) {\n return message.actions;\n }\n\n if (defaultActions) {\n return defaultActions.map(\n (action): BaseMessageActionConfig => ({\n actionType: action.type,\n icon: action.icon,\n children:\n typeof action.children === 'function'\n ? action.children(message)\n : action.children,\n label: action.label,\n view: action.view,\n onClick: () => action.onClick(message),\n popup: action.popup as ActionPopupConfig<unknown>,\n }),\n );\n }\n\n return undefined;\n}\n"]}
1
+ {"version":3,"file":"messageUtils.js","sourceRoot":"../../../src","sources":["utils/messageUtils.ts"],"names":[],"mappings":";;AAaA,sCAKC;AAED,gDAOC;AAED,4CAsCC;AAOD,wDAQC;AAKD,sDA4BC;AAtGD,SAAgB,aAAa,CAG3B,OAAsD;IACpD,OAAO,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC;AACnC,CAAC;AAED,SAAgB,kBAAkB,CAI9B,OAAsD;IAEtD,OAAO,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC;AACxC,CAAC;AAED,SAAgB,gBAAgB,CAC5B,OAA8E;IAE9E,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACd,CAAC;IAED,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAClB,OAAO,EAAE,CAAC;QACd,CAAC;QAED,OAAO;YACH;gBACI,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE;oBACF,IAAI,EAAE,OAAO;iBAChB;aACJ;SACJ,CAAC;IACN,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAEvB,IACI,IAAI,CAAC,IAAI,KAAK,MAAM;YACpB,OAAO,IAAI,KAAK,QAAQ;YACxB,IAAI,KAAK,IAAI;YACb,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC;YACjB,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAC/B,CAAC;YACC,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;GAIG;AACH,SAAgB,sBAAsB,CAClC,OAA8E;IAE9E,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;AAC3D,CAAC;AAKD,SAAgB,qBAAqB,CAGjC,OAAiB,EACjB,cAAiD;IAEjD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO,OAAO,CAAC,OAAO,CAAC;IAC3B,CAAC;IAED,IAAI,cAAc,EAAE,CAAC;QACjB,OAAO,cAAc,CAAC,GAAG,CACrB,CAAC,MAAM,EAA2B,EAAE,CAAC,CAAC;YAClC,UAAU,EAAE,MAAM,CAAC,IAAI;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EACJ,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU;gBACjC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAC1B,CAAC,CAAC,MAAM,CAAC,QAAQ;YACzB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;YACtC,KAAK,EAAE,MAAM,CAAC,KAAmC;SACpD,CAAC,CACL,CAAC;IACN,CAAC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC","sourcesContent":["import type {\n ActionPopupConfig,\n BaseMessageAction,\n BaseMessageActionConfig,\n DefaultMessageAction,\n TAssistantMessage,\n TChatMessage,\n TMessageContent,\n TMessageContentUnion,\n TMessageMetadata,\n TUserMessage,\n} from '../types';\n\nexport function isUserMessage<\n Metadata = TMessageMetadata,\n TCustomMessageContent extends TMessageContent = never,\n>(message: TChatMessage<TCustomMessageContent, Metadata>): message is TUserMessage<Metadata> {\n return message.role === 'user';\n}\n\nexport function isAssistantMessage<\n Metadata = TMessageMetadata,\n TCustomMessageContent extends TMessageContent = never,\n>(\n message: TChatMessage<TCustomMessageContent, Metadata>,\n): message is TAssistantMessage<TCustomMessageContent, Metadata> {\n return message.role === 'assistant';\n}\n\nexport function normalizeContent<TCustomMessageContent extends TMessageContent = never>(\n content: TAssistantMessage<TCustomMessageContent, TMessageMetadata>['content'],\n): TMessageContentUnion<TCustomMessageContent>[] {\n if (!content) {\n return [];\n }\n\n if (typeof content === 'string') {\n if (!content.trim()) {\n return [];\n }\n\n return [\n {\n type: 'text',\n data: {\n text: content,\n },\n },\n ];\n }\n\n const parts = Array.isArray(content) ? content : [content];\n return parts.filter((part) => {\n const data = part.data;\n\n if (\n part.type !== 'text' ||\n typeof data !== 'object' ||\n data === null ||\n !('text' in data) ||\n typeof data.text !== 'string'\n ) {\n return true;\n }\n\n return data.text.trim().length > 0;\n });\n}\n\n/**\n * Check if message content contains ONLY thinking content (no other content types).\n * @param content - Message content to check\n * @returns true if content contains only thinking parts and nothing else\n */\nexport function hasOnlyThinkingContent<TCustomMessageContent extends TMessageContent = never>(\n content: TAssistantMessage<TCustomMessageContent, TMessageMetadata>['content'],\n): boolean {\n const parts = normalizeContent(content);\n if (parts.length === 0) {\n return false;\n }\n return parts.every((part) => part.type === 'thinking');\n}\n\n// Re-export ActionPopup types for convenience\nexport type {ActionPopupContext, ActionPopupConfig} from '../types';\n\nexport function resolveMessageActions<\n TMessage extends TChatMessage<TMessageContent, TMessageMetadata>,\n>(\n message: TMessage,\n defaultActions?: DefaultMessageAction<TMessage>[],\n): BaseMessageAction[] | undefined {\n if (message.actions) {\n return message.actions;\n }\n\n if (defaultActions) {\n return defaultActions.map(\n (action): BaseMessageActionConfig => ({\n actionType: action.type,\n icon: action.icon,\n children:\n typeof action.children === 'function'\n ? action.children(message)\n : action.children,\n label: action.label,\n view: action.view,\n onClick: () => action.onClick(message),\n popup: action.popup as ActionPopupConfig<unknown>,\n }),\n );\n }\n\n return undefined;\n}\n"]}
@@ -89,8 +89,9 @@ import ruLocale from 'dayjs/locale/ru.js';
89
89
  Pinning is opt-in. Pass `pinnedChatIds` and `onPinnedChange` to `History` or
90
90
  `HistoryList`. Omit both to retain the existing behavior. Passing only identifiers
91
91
  shows read-only pin indicators. `showActions={false}` hides actions, not indicators.
92
- Pin actions use native buttons with accessible names and are always visible on
93
- both desktop and mobile. Activating a pin does not select the chat or close history.
92
+ Pin and delete actions appear on hover or keyboard focus on desktop and stay visible
93
+ on mobile. Their space is reserved to avoid shifting the row content. Pending pin
94
+ actions remain focusable and use `aria-disabled` to prevent repeat submissions. Activating a pin does not select the chat or close history.
94
95
 
95
96
  With pinning enabled, pinned chats precede other chats, preserving the application's
96
97
  input order within both groups. Pinned rows have no date headers; regular rows keep
@@ -7,6 +7,11 @@ import { block } from "../../../utils/cn.js";
7
7
  import { ActionButton } from "../../atoms/ActionButton/index.js";
8
8
  import { i18n } from "./i18n/index.js";
9
9
  const b = block('history');
10
+ function handleActionKeyDown(event) {
11
+ if (event.key === ' ' || event.key === 'Enter') {
12
+ event.stopPropagation();
13
+ }
14
+ }
10
15
  /**
11
16
  * Chat item component with hover state for actions.
12
17
  * In mobile mode there is no hover, so actions are always visible.
@@ -50,13 +55,11 @@ export function ChatItem({ chat, showActions, isActive, onDeleteClick, size, pin
50
55
  active: isActive,
51
56
  mobile: isMobile,
52
57
  ['is-delete-processing']: isDeleteProccesing,
53
- }), onClick: handleClick, children: [pinned && (_jsx("span", { role: "img", "aria-label": i18n('pinned'), children: _jsx(Icon, { data: Pin, size: 16 }) })), hasOverflow ? (_jsx(Tooltip, { content: chatLabel, openDelay: 300, children: label })) : (label), showDeleteAction ? (_jsx(ActionButton, { view: "flat", size: deleteButtonSize, color: "secondary", loading: isDeleteProccesing, className: b('delete-button'), onClick: handleDeleteChat, tooltipTitle: i18n('tooltip-delete'), children: _jsx(Icon, { className: b('icon-button'), data: TrashBin, size: deleteIconSize }) })) : null, showActions && onPinnedChange && (_jsx(ActionButton, { view: "flat", size: deleteButtonSize, disabled: pinningDisabled || isDeleteProccesing, "aria-label": i18n(pinned ? 'action-unpin' : 'action-pin'), tooltipTitle: i18n(pinned ? 'action-unpin' : 'action-pin'), onKeyDown: (event) => {
54
- if (event.key === ' ' || event.key === 'Enter') {
55
- event.stopPropagation();
56
- }
57
- }, onClick: (event) => {
58
+ }), onClick: handleClick, children: [pinned && (_jsx("span", { role: "img", "aria-label": i18n('pinned'), children: _jsx(Icon, { data: Pin, size: 16 }) })), hasOverflow ? (_jsx(Tooltip, { content: chatLabel, openDelay: 300, children: label })) : (label), showDeleteAction ? (_jsx(ActionButton, { view: "flat", size: deleteButtonSize, color: "secondary", loading: isDeleteProccesing, className: b('delete-button'), "aria-label": i18n('tooltip-delete'), onClick: handleDeleteChat, onKeyDown: handleActionKeyDown, tooltipTitle: i18n('tooltip-delete'), children: _jsx(Icon, { className: b('icon-button'), data: TrashBin, size: deleteIconSize }) })) : null, showActions && onPinnedChange && (_jsx(ActionButton, { view: "flat", size: deleteButtonSize, "aria-disabled": Boolean(pinningDisabled || isDeleteProccesing), className: b('pin-button'), "aria-label": i18n(pinned ? 'action-unpin' : 'action-pin'), tooltipTitle: i18n(pinned ? 'action-unpin' : 'action-pin'), onKeyDown: handleActionKeyDown, onClick: (event) => {
58
59
  event.stopPropagation();
59
- onPinnedChange(chat.id, !pinned);
60
+ if (!pinningDisabled && !isDeleteProccesing) {
61
+ onPinnedChange(chat.id, !pinned);
62
+ }
60
63
  }, children: _jsx(Icon, { data: pinned ? PinSlash : Pin, size: deleteIconSize }) }))] }));
61
64
  }
62
65
  //# sourceMappingURL=ChatItem.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatItem.js","sourceRoot":"../../../../../src","sources":["components/templates/History/ChatItem.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useEffect, useRef, useState} from 'react';\n\nimport {Pin, PinSlash, TrashBin} from '@gravity-ui/icons';\nimport {Icon, InputControlSize, Text, Tooltip, useMobile} from '@gravity-ui/uikit';\n\nimport {getControlIconSize, resolveMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {ChatType} from '../../../types';\nimport {block} from '../../../utils/cn';\nimport {ActionButton} from '../../atoms/ActionButton';\n\nimport {i18n} from './i18n';\n\nconst b = block('history');\n\n/**\n * Props for ChatItem component\n */\nexport interface ChatItemProps {\n chat: ChatType;\n showActions: boolean;\n pinned?: boolean;\n onPinnedChange?: (chatId: string, pinned: boolean) => void;\n pinningDisabled?: boolean;\n isActive?: boolean;\n onDeleteClick?: (e: React.MouseEvent, chat: ChatType) => Promise<void>;\n /** Size of the row actions (default: `s`, `xl` in mobile mode) */\n size?: InputControlSize;\n}\n\n/**\n * Chat item component with hover state for actions.\n * In mobile mode there is no hover, so actions are always visible.\n *\n * @returns React element\n */\nexport function ChatItem({\n chat,\n showActions,\n isActive,\n onDeleteClick,\n size,\n pinned,\n onPinnedChange,\n pinningDisabled,\n}: ChatItemProps) {\n const [isDeleteProccesing, setIsDeleteProcessing] = useState(false);\n const [hasOverflow, setHasOverflow] = useState(false);\n const labelRef = useRef<HTMLDivElement>(null);\n const chatLabel = chat.lastMessage || chat.name;\n const isMobile = useMobile();\n const deleteButtonSize = resolveMobileControlSize({\n size,\n desktopDefault: 's',\n mobileDefault: 'xl',\n isMobile,\n });\n const deleteIconSize = getControlIconSize(deleteButtonSize);\n\n useEffect(() => {\n const label = labelRef.current;\n setHasOverflow(Boolean(label && label.scrollWidth > label.clientWidth));\n }, [chatLabel, pinned, showActions, onPinnedChange]);\n\n const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {\n if (isDeleteProccesing) {\n e.stopPropagation();\n }\n };\n\n const handleDeleteChat = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {\n try {\n setIsDeleteProcessing(true);\n await onDeleteClick?.(e, chat);\n } finally {\n setIsDeleteProcessing(false);\n }\n };\n\n const showDeleteAction = isDeleteProccesing || (showActions && onDeleteClick);\n\n const label = (\n <div className={b('chat-content')}>\n <Text variant=\"body-1\" ref={labelRef}>\n {chatLabel}\n </Text>\n </div>\n );\n\n return (\n <div\n className={b('chat-item', {\n active: isActive,\n mobile: isMobile,\n ['is-delete-processing']: isDeleteProccesing,\n })}\n onClick={handleClick}\n >\n {pinned && (\n <span role=\"img\" aria-label={i18n('pinned')}>\n <Icon data={Pin} size={16} />\n </span>\n )}\n {hasOverflow ? (\n <Tooltip content={chatLabel} openDelay={300}>\n {label}\n </Tooltip>\n ) : (\n label\n )}\n\n {showDeleteAction ? (\n <ActionButton\n view=\"flat\"\n size={deleteButtonSize}\n color=\"secondary\"\n loading={isDeleteProccesing}\n className={b('delete-button')}\n onClick={handleDeleteChat}\n tooltipTitle={i18n('tooltip-delete')}\n >\n <Icon className={b('icon-button')} data={TrashBin} size={deleteIconSize} />\n </ActionButton>\n ) : null}\n {showActions && onPinnedChange && (\n <ActionButton\n view=\"flat\"\n size={deleteButtonSize}\n disabled={pinningDisabled || isDeleteProccesing}\n aria-label={i18n(pinned ? 'action-unpin' : 'action-pin')}\n tooltipTitle={i18n(pinned ? 'action-unpin' : 'action-pin')}\n onKeyDown={(event) => {\n if (event.key === ' ' || event.key === 'Enter') {\n event.stopPropagation();\n }\n }}\n onClick={(event) => {\n event.stopPropagation();\n onPinnedChange(chat.id, !pinned);\n }}\n >\n <Icon data={pinned ? PinSlash : Pin} size={deleteIconSize} />\n </ActionButton>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"ChatItem.js","sourceRoot":"../../../../../src","sources":["components/templates/History/ChatItem.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useEffect, useRef, useState} from 'react';\n\nimport {Pin, PinSlash, TrashBin} from '@gravity-ui/icons';\nimport {Icon, InputControlSize, Text, Tooltip, useMobile} from '@gravity-ui/uikit';\n\nimport {getControlIconSize, resolveMobileControlSize} from '../../../hooks/useMobileControlSize';\nimport {ChatType} from '../../../types';\nimport {block} from '../../../utils/cn';\nimport {ActionButton} from '../../atoms/ActionButton';\n\nimport {i18n} from './i18n';\n\nconst b = block('history');\n\nfunction handleActionKeyDown(event: React.KeyboardEvent<HTMLButtonElement>) {\n if (event.key === ' ' || event.key === 'Enter') {\n event.stopPropagation();\n }\n}\n\n/**\n * Props for ChatItem component\n */\nexport interface ChatItemProps {\n chat: ChatType;\n showActions: boolean;\n pinned?: boolean;\n onPinnedChange?: (chatId: string, pinned: boolean) => void;\n pinningDisabled?: boolean;\n isActive?: boolean;\n onDeleteClick?: (e: React.MouseEvent, chat: ChatType) => Promise<void>;\n /** Size of the row actions (default: `s`, `xl` in mobile mode) */\n size?: InputControlSize;\n}\n\n/**\n * Chat item component with hover state for actions.\n * In mobile mode there is no hover, so actions are always visible.\n *\n * @returns React element\n */\nexport function ChatItem({\n chat,\n showActions,\n isActive,\n onDeleteClick,\n size,\n pinned,\n onPinnedChange,\n pinningDisabled,\n}: ChatItemProps) {\n const [isDeleteProccesing, setIsDeleteProcessing] = useState(false);\n const [hasOverflow, setHasOverflow] = useState(false);\n const labelRef = useRef<HTMLDivElement>(null);\n const chatLabel = chat.lastMessage || chat.name;\n const isMobile = useMobile();\n const deleteButtonSize = resolveMobileControlSize({\n size,\n desktopDefault: 's',\n mobileDefault: 'xl',\n isMobile,\n });\n const deleteIconSize = getControlIconSize(deleteButtonSize);\n\n useEffect(() => {\n const label = labelRef.current;\n setHasOverflow(Boolean(label && label.scrollWidth > label.clientWidth));\n }, [chatLabel, pinned, showActions, onPinnedChange]);\n\n const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {\n if (isDeleteProccesing) {\n e.stopPropagation();\n }\n };\n\n const handleDeleteChat = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {\n try {\n setIsDeleteProcessing(true);\n await onDeleteClick?.(e, chat);\n } finally {\n setIsDeleteProcessing(false);\n }\n };\n\n const showDeleteAction = isDeleteProccesing || (showActions && onDeleteClick);\n\n const label = (\n <div className={b('chat-content')}>\n <Text variant=\"body-1\" ref={labelRef}>\n {chatLabel}\n </Text>\n </div>\n );\n\n return (\n <div\n className={b('chat-item', {\n active: isActive,\n mobile: isMobile,\n ['is-delete-processing']: isDeleteProccesing,\n })}\n onClick={handleClick}\n >\n {pinned && (\n <span role=\"img\" aria-label={i18n('pinned')}>\n <Icon data={Pin} size={16} />\n </span>\n )}\n {hasOverflow ? (\n <Tooltip content={chatLabel} openDelay={300}>\n {label}\n </Tooltip>\n ) : (\n label\n )}\n\n {showDeleteAction ? (\n <ActionButton\n view=\"flat\"\n size={deleteButtonSize}\n color=\"secondary\"\n loading={isDeleteProccesing}\n className={b('delete-button')}\n aria-label={i18n('tooltip-delete')}\n onClick={handleDeleteChat}\n onKeyDown={handleActionKeyDown}\n tooltipTitle={i18n('tooltip-delete')}\n >\n <Icon className={b('icon-button')} data={TrashBin} size={deleteIconSize} />\n </ActionButton>\n ) : null}\n {showActions && onPinnedChange && (\n <ActionButton\n view=\"flat\"\n size={deleteButtonSize}\n aria-disabled={Boolean(pinningDisabled || isDeleteProccesing)}\n className={b('pin-button')}\n aria-label={i18n(pinned ? 'action-unpin' : 'action-pin')}\n tooltipTitle={i18n(pinned ? 'action-unpin' : 'action-pin')}\n onKeyDown={handleActionKeyDown}\n onClick={(event) => {\n event.stopPropagation();\n if (!pinningDisabled && !isDeleteProccesing) {\n onPinnedChange(chat.id, !pinned);\n }\n }}\n >\n <Icon data={pinned ? PinSlash : Pin} size={deleteIconSize} />\n </ActionButton>\n )}\n </div>\n );\n}\n"]}
@@ -43,8 +43,14 @@
43
43
  flex: 1;
44
44
  min-width: 0;
45
45
  }
46
- .g-aikit-history__chat-item_active .g-aikit-history__delete-button, .g-aikit-history__chat-item_is-delete-processing .g-aikit-history__delete-button, .g-aikit-history__chat-item_mobile .g-aikit-history__delete-button {
47
- display: block;
46
+ .g-aikit-history__chat-item:hover .g-aikit-history__delete-button,
47
+ .g-aikit-history__chat-item:hover .g-aikit-history__pin-button, .g-aikit-history__chat-item:focus-within .g-aikit-history__delete-button,
48
+ .g-aikit-history__chat-item:focus-within .g-aikit-history__pin-button, .g-aikit-history__chat-item_active .g-aikit-history__delete-button,
49
+ .g-aikit-history__chat-item_active .g-aikit-history__pin-button, .g-aikit-history__chat-item_is-delete-processing .g-aikit-history__delete-button,
50
+ .g-aikit-history__chat-item_is-delete-processing .g-aikit-history__pin-button, .g-aikit-history__chat-item_mobile .g-aikit-history__delete-button,
51
+ .g-aikit-history__chat-item_mobile .g-aikit-history__pin-button {
52
+ opacity: 1;
53
+ pointer-events: auto;
48
54
  }
49
55
  .g-aikit-history__chat-item_is-delete-processing {
50
56
  color: var(--g-color-text-secondary);
@@ -66,9 +72,17 @@
66
72
  .g-aikit-history__icon-button {
67
73
  color: var(--g-color-text-secondary);
68
74
  }
75
+ .g-aikit-history__delete-button, .g-aikit-history__pin-button {
76
+ opacity: 0;
77
+ pointer-events: none;
78
+ flex-shrink: 0;
79
+ }
80
+ .g-aikit-history__pin-button[aria-disabled=true] {
81
+ color: var(--g-color-text-hint);
82
+ cursor: default;
83
+ }
69
84
  .g-aikit-history__delete-button {
70
85
  margin-left: calc(var(--g-spacing-base) * 2);
71
- display: none;
72
86
  }
73
87
  .g-aikit-history__empty {
74
88
  display: flex;
@@ -1 +1 @@
1
- {"version":"2.25.1-beta.c31bfaac02b26694749dd7a5d2532192c8e5fdc2.0","type":"module","sideEffects":["*.css","*.scss"]}
1
+ {"version":"2.26.0","type":"module","sideEffects":["*.css","*.scss"]}
@@ -9,6 +9,9 @@ export function normalizeContent(content) {
9
9
  return [];
10
10
  }
11
11
  if (typeof content === 'string') {
12
+ if (!content.trim()) {
13
+ return [];
14
+ }
12
15
  return [
13
16
  {
14
17
  type: 'text',
@@ -18,10 +21,18 @@ export function normalizeContent(content) {
18
21
  },
19
22
  ];
20
23
  }
21
- if (Array.isArray(content)) {
22
- return content;
23
- }
24
- return [content];
24
+ const parts = Array.isArray(content) ? content : [content];
25
+ return parts.filter((part) => {
26
+ const data = part.data;
27
+ if (part.type !== 'text' ||
28
+ typeof data !== 'object' ||
29
+ data === null ||
30
+ !('text' in data) ||
31
+ typeof data.text !== 'string') {
32
+ return true;
33
+ }
34
+ return data.text.trim().length > 0;
35
+ });
25
36
  }
26
37
  /**
27
38
  * Check if message content contains ONLY thinking content (no other content types).
@@ -1 +1 @@
1
- {"version":3,"file":"messageUtils.js","sourceRoot":"../../../src","sources":["utils/messageUtils.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,aAAa,CAG3B,OAAsD;IACpD,OAAO,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAI9B,OAAsD;IAEtD,OAAO,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC5B,OAA8E;IAE9E,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACd,CAAC;IAED,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO;YACH;gBACI,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE;oBACF,IAAI,EAAE,OAAO;iBAChB;aACJ;SACJ,CAAC;IACN,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,OAAO,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAClC,OAA8E;IAE9E,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;AAC3D,CAAC;AAKD,MAAM,UAAU,qBAAqB,CAGjC,OAAiB,EACjB,cAAiD;IAEjD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO,OAAO,CAAC,OAAO,CAAC;IAC3B,CAAC;IAED,IAAI,cAAc,EAAE,CAAC;QACjB,OAAO,cAAc,CAAC,GAAG,CACrB,CAAC,MAAM,EAA2B,EAAE,CAAC,CAAC;YAClC,UAAU,EAAE,MAAM,CAAC,IAAI;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EACJ,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU;gBACjC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAC1B,CAAC,CAAC,MAAM,CAAC,QAAQ;YACzB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;YACtC,KAAK,EAAE,MAAM,CAAC,KAAmC;SACpD,CAAC,CACL,CAAC;IACN,CAAC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC","sourcesContent":["import type {\n ActionPopupConfig,\n BaseMessageAction,\n BaseMessageActionConfig,\n DefaultMessageAction,\n TAssistantMessage,\n TChatMessage,\n TMessageContent,\n TMessageContentUnion,\n TMessageMetadata,\n TUserMessage,\n} from '../types';\n\nexport function isUserMessage<\n Metadata = TMessageMetadata,\n TCustomMessageContent extends TMessageContent = never,\n>(message: TChatMessage<TCustomMessageContent, Metadata>): message is TUserMessage<Metadata> {\n return message.role === 'user';\n}\n\nexport function isAssistantMessage<\n Metadata = TMessageMetadata,\n TCustomMessageContent extends TMessageContent = never,\n>(\n message: TChatMessage<TCustomMessageContent, Metadata>,\n): message is TAssistantMessage<TCustomMessageContent, Metadata> {\n return message.role === 'assistant';\n}\n\nexport function normalizeContent<TCustomMessageContent extends TMessageContent = never>(\n content: TAssistantMessage<TCustomMessageContent, TMessageMetadata>['content'],\n): TMessageContentUnion<TCustomMessageContent>[] {\n if (!content) {\n return [];\n }\n\n if (typeof content === 'string') {\n return [\n {\n type: 'text',\n data: {\n text: content,\n },\n },\n ];\n }\n\n if (Array.isArray(content)) {\n return content;\n }\n\n return [content];\n}\n\n/**\n * Check if message content contains ONLY thinking content (no other content types).\n * @param content - Message content to check\n * @returns true if content contains only thinking parts and nothing else\n */\nexport function hasOnlyThinkingContent<TCustomMessageContent extends TMessageContent = never>(\n content: TAssistantMessage<TCustomMessageContent, TMessageMetadata>['content'],\n): boolean {\n const parts = normalizeContent(content);\n if (parts.length === 0) {\n return false;\n }\n return parts.every((part) => part.type === 'thinking');\n}\n\n// Re-export ActionPopup types for convenience\nexport type {ActionPopupContext, ActionPopupConfig} from '../types';\n\nexport function resolveMessageActions<\n TMessage extends TChatMessage<TMessageContent, TMessageMetadata>,\n>(\n message: TMessage,\n defaultActions?: DefaultMessageAction<TMessage>[],\n): BaseMessageAction[] | undefined {\n if (message.actions) {\n return message.actions;\n }\n\n if (defaultActions) {\n return defaultActions.map(\n (action): BaseMessageActionConfig => ({\n actionType: action.type,\n icon: action.icon,\n children:\n typeof action.children === 'function'\n ? action.children(message)\n : action.children,\n label: action.label,\n view: action.view,\n onClick: () => action.onClick(message),\n popup: action.popup as ActionPopupConfig<unknown>,\n }),\n );\n }\n\n return undefined;\n}\n"]}
1
+ {"version":3,"file":"messageUtils.js","sourceRoot":"../../../src","sources":["utils/messageUtils.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,aAAa,CAG3B,OAAsD;IACpD,OAAO,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAI9B,OAAsD;IAEtD,OAAO,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC5B,OAA8E;IAE9E,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACd,CAAC;IAED,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAClB,OAAO,EAAE,CAAC;QACd,CAAC;QAED,OAAO;YACH;gBACI,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE;oBACF,IAAI,EAAE,OAAO;iBAChB;aACJ;SACJ,CAAC;IACN,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QAEvB,IACI,IAAI,CAAC,IAAI,KAAK,MAAM;YACpB,OAAO,IAAI,KAAK,QAAQ;YACxB,IAAI,KAAK,IAAI;YACb,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC;YACjB,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAC/B,CAAC;YACC,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAClC,OAA8E;IAE9E,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;AAC3D,CAAC;AAKD,MAAM,UAAU,qBAAqB,CAGjC,OAAiB,EACjB,cAAiD;IAEjD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAClB,OAAO,OAAO,CAAC,OAAO,CAAC;IAC3B,CAAC;IAED,IAAI,cAAc,EAAE,CAAC;QACjB,OAAO,cAAc,CAAC,GAAG,CACrB,CAAC,MAAM,EAA2B,EAAE,CAAC,CAAC;YAClC,UAAU,EAAE,MAAM,CAAC,IAAI;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EACJ,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU;gBACjC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAC1B,CAAC,CAAC,MAAM,CAAC,QAAQ;YACzB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;YACtC,KAAK,EAAE,MAAM,CAAC,KAAmC;SACpD,CAAC,CACL,CAAC;IACN,CAAC;IAED,OAAO,SAAS,CAAC;AACrB,CAAC","sourcesContent":["import type {\n ActionPopupConfig,\n BaseMessageAction,\n BaseMessageActionConfig,\n DefaultMessageAction,\n TAssistantMessage,\n TChatMessage,\n TMessageContent,\n TMessageContentUnion,\n TMessageMetadata,\n TUserMessage,\n} from '../types';\n\nexport function isUserMessage<\n Metadata = TMessageMetadata,\n TCustomMessageContent extends TMessageContent = never,\n>(message: TChatMessage<TCustomMessageContent, Metadata>): message is TUserMessage<Metadata> {\n return message.role === 'user';\n}\n\nexport function isAssistantMessage<\n Metadata = TMessageMetadata,\n TCustomMessageContent extends TMessageContent = never,\n>(\n message: TChatMessage<TCustomMessageContent, Metadata>,\n): message is TAssistantMessage<TCustomMessageContent, Metadata> {\n return message.role === 'assistant';\n}\n\nexport function normalizeContent<TCustomMessageContent extends TMessageContent = never>(\n content: TAssistantMessage<TCustomMessageContent, TMessageMetadata>['content'],\n): TMessageContentUnion<TCustomMessageContent>[] {\n if (!content) {\n return [];\n }\n\n if (typeof content === 'string') {\n if (!content.trim()) {\n return [];\n }\n\n return [\n {\n type: 'text',\n data: {\n text: content,\n },\n },\n ];\n }\n\n const parts = Array.isArray(content) ? content : [content];\n return parts.filter((part) => {\n const data = part.data;\n\n if (\n part.type !== 'text' ||\n typeof data !== 'object' ||\n data === null ||\n !('text' in data) ||\n typeof data.text !== 'string'\n ) {\n return true;\n }\n\n return data.text.trim().length > 0;\n });\n}\n\n/**\n * Check if message content contains ONLY thinking content (no other content types).\n * @param content - Message content to check\n * @returns true if content contains only thinking parts and nothing else\n */\nexport function hasOnlyThinkingContent<TCustomMessageContent extends TMessageContent = never>(\n content: TAssistantMessage<TCustomMessageContent, TMessageMetadata>['content'],\n): boolean {\n const parts = normalizeContent(content);\n if (parts.length === 0) {\n return false;\n }\n return parts.every((part) => part.type === 'thinking');\n}\n\n// Re-export ActionPopup types for convenience\nexport type {ActionPopupContext, ActionPopupConfig} from '../types';\n\nexport function resolveMessageActions<\n TMessage extends TChatMessage<TMessageContent, TMessageMetadata>,\n>(\n message: TMessage,\n defaultActions?: DefaultMessageAction<TMessage>[],\n): BaseMessageAction[] | undefined {\n if (message.actions) {\n return message.actions;\n }\n\n if (defaultActions) {\n return defaultActions.map(\n (action): BaseMessageActionConfig => ({\n actionType: action.type,\n icon: action.icon,\n children:\n typeof action.children === 'function'\n ? action.children(message)\n : action.children,\n label: action.label,\n view: action.view,\n onClick: () => action.onClick(message),\n popup: action.popup as ActionPopupConfig<unknown>,\n }),\n );\n }\n\n return undefined;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/aikit",
3
- "version": "2.25.1-beta.c31bfaac02b26694749dd7a5d2532192c8e5fdc2.0",
3
+ "version": "2.26.0",
4
4
  "description": "Gravity UI base kit for building ai assistant chats",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",