@gravity-ui/aikit 0.6.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/atoms/ActionButton/__stories__/ActionButton.stories.d.ts +8 -0
- package/dist/components/atoms/ActionButton/__stories__/ActionButton.stories.js +48 -0
- package/dist/components/atoms/Alert/__stories__/Alert.stories.d.ts +10 -0
- package/dist/components/atoms/Alert/__stories__/Alert.stories.js +72 -0
- package/dist/components/atoms/ChatDate/__stories__/ChatDate.stories.d.ts +16 -0
- package/dist/components/atoms/ChatDate/__stories__/ChatDate.stories.js +83 -0
- package/dist/components/atoms/ContextIndicator/__stories__/ContextIndicator.stories.d.ts +17 -0
- package/dist/components/atoms/ContextIndicator/__stories__/ContextIndicator.stories.js +72 -0
- package/dist/components/atoms/ContextItem/__stories__/ContextItem.stories.d.ts +8 -0
- package/dist/components/atoms/ContextItem/__stories__/ContextItem.stories.js +36 -0
- package/dist/components/atoms/DiffStat/__stories__/DiffStat.stories.d.ts +8 -0
- package/dist/components/atoms/DiffStat/__stories__/DiffStat.stories.js +45 -0
- package/dist/components/atoms/Disclaimer/__stories__/Disclaimer.stories.d.ts +12 -0
- package/dist/components/atoms/Disclaimer/__stories__/Disclaimer.stories.js +64 -0
- package/dist/components/atoms/Loader/__stories__/Loader.stories.d.ts +8 -0
- package/dist/components/atoms/Loader/__stories__/Loader.stories.js +47 -0
- package/dist/components/atoms/MarkdownRenderer/__stories__/MarkdownRenderer.stories.d.ts +6 -0
- package/dist/components/atoms/MarkdownRenderer/__stories__/MarkdownRenderer.stories.js +49 -0
- package/dist/components/atoms/MessageBalloon/__stories__/MessageBalloon.stories.d.ts +6 -0
- package/dist/components/atoms/MessageBalloon/__stories__/MessageBalloon.stories.js +32 -0
- package/dist/components/atoms/Shimmer/__stories__/Shimmer.stories.d.ts +5 -0
- package/dist/components/atoms/Shimmer/__stories__/Shimmer.stories.js +28 -0
- package/dist/components/atoms/SubmitButton/__stories__/SubmitButton.stories.d.ts +13 -0
- package/dist/components/atoms/SubmitButton/__stories__/SubmitButton.stories.js +98 -0
- package/dist/components/atoms/ToolIndicator/__stories__/ToolIndicator.stories.d.ts +9 -0
- package/dist/components/atoms/ToolIndicator/__stories__/ToolIndicator.stories.js +34 -0
- package/dist/components/molecules/BaseMessage/__stories__/BaseMessage.stories.d.ts +9 -0
- package/dist/components/molecules/BaseMessage/__stories__/BaseMessage.stories.js +77 -0
- package/dist/components/molecules/BaseMessage/index.d.ts +1 -1
- package/dist/components/molecules/BaseMessage/index.js +51 -20
- package/dist/components/molecules/ButtonGroup/__stories__/ButtonGroup.stories.d.ts +6 -0
- package/dist/components/molecules/ButtonGroup/__stories__/ButtonGroup.stories.js +44 -0
- package/dist/components/molecules/PromptInputBody/__stories__/PromptInputBody.stories.d.ts +11 -0
- package/dist/components/molecules/PromptInputBody/__stories__/PromptInputBody.stories.js +62 -0
- package/dist/components/molecules/PromptInputFooter/__stories__/PromptInputFooter.stories.d.ts +11 -0
- package/dist/components/molecules/PromptInputFooter/__stories__/PromptInputFooter.stories.js +96 -0
- package/dist/components/molecules/PromptInputHeader/__stories__/PromptInputHeader.stories.d.ts +15 -0
- package/dist/components/molecules/PromptInputHeader/__stories__/PromptInputHeader.stories.js +123 -0
- package/dist/components/molecules/PromptInputPanel/__stories__/PromptInputPanel.stories.d.ts +8 -0
- package/dist/components/molecules/PromptInputPanel/__stories__/PromptInputPanel.stories.js +38 -0
- package/dist/components/molecules/Suggestions/__stories__/Suggestions.stories.d.ts +22 -0
- package/dist/components/molecules/Suggestions/__stories__/Suggestions.stories.js +182 -0
- package/dist/components/molecules/Tabs/__stories__/Tabs.stories.d.ts +9 -0
- package/dist/components/molecules/Tabs/__stories__/Tabs.stories.js +103 -0
- package/dist/components/molecules/ToolFooter/__stories__/ToolFooter.stories.d.ts +7 -0
- package/dist/components/molecules/ToolFooter/__stories__/ToolFooter.stories.js +58 -0
- package/dist/components/molecules/ToolFooter/index.js +8 -1
- package/dist/components/molecules/ToolHeader/__stories__/ToolHeader.stories.d.ts +8 -0
- package/dist/components/molecules/ToolHeader/__stories__/ToolHeader.stories.js +59 -0
- package/dist/components/molecules/ToolHeader/index.js +10 -1
- package/dist/components/molecules/ToolStatus/__stories__/ToolStatus.stories.d.ts +9 -0
- package/dist/components/molecules/ToolStatus/__stories__/ToolStatus.stories.js +44 -0
- package/dist/components/organisms/AssistantMessage/__stories__/AssistantMessage.stories.d.ts +13 -0
- package/dist/components/organisms/AssistantMessage/__stories__/AssistantMessage.stories.js +151 -0
- package/dist/components/organisms/Header/Header.js +16 -17
- package/dist/components/organisms/Header/__stories__/Header.stories.d.ts +15 -0
- package/dist/components/organisms/Header/__stories__/Header.stories.js +159 -0
- package/dist/components/organisms/Header/types.d.ts +2 -3
- package/dist/components/organisms/Header/useHeader.d.ts +2 -4
- package/dist/components/organisms/Header/useHeader.js +2 -24
- package/dist/components/organisms/MessageList/MessageList.js +5 -6
- package/dist/components/organisms/MessageList/__stories__/MessageList.stories.d.ts +25 -0
- package/dist/components/organisms/MessageList/__stories__/MessageList.stories.js +340 -0
- package/dist/components/organisms/PromptInput/__stories__/PromptInput.stories.d.ts +19 -0
- package/dist/components/organisms/PromptInput/__stories__/PromptInput.stories.js +304 -0
- package/dist/components/organisms/ThinkingMessage/__stories__/ThinkingMessage.stories.d.ts +12 -0
- package/dist/components/organisms/ThinkingMessage/__stories__/ThinkingMessage.stories.js +105 -0
- package/dist/components/organisms/ToolMessage/__stories__/ToolMessage.stories.d.ts +11 -0
- package/dist/components/organisms/ToolMessage/__stories__/ToolMessage.stories.js +70 -0
- package/dist/components/organisms/UserMessage/__stories__/UserMessage.stories.d.ts +9 -0
- package/dist/components/organisms/UserMessage/__stories__/UserMessage.stories.js +118 -0
- package/dist/components/pages/ChatContainer/__stories__/ChatContainer.stories.d.ts +79 -0
- package/dist/components/pages/ChatContainer/__stories__/ChatContainer.stories.js +1006 -0
- package/dist/components/templates/ChatContent/__stories__/ChatContent.stories.d.ts +14 -0
- package/dist/components/templates/ChatContent/__stories__/ChatContent.stories.js +315 -0
- package/dist/components/templates/EmptyContainer/__stories__/EmptyContainer.stories.d.ts +20 -0
- package/dist/components/templates/EmptyContainer/__stories__/EmptyContainer.stories.js +250 -0
- package/dist/components/templates/History/ChatItem.d.ts +2 -2
- package/dist/components/templates/History/ChatItem.js +2 -5
- package/dist/components/templates/History/History.scss +13 -9
- package/dist/components/templates/History/HistoryList.d.ts +3 -1
- package/dist/components/templates/History/HistoryList.js +9 -5
- package/dist/components/templates/History/__stories__/History.stories.d.ts +18 -0
- package/dist/components/templates/History/__stories__/History.stories.js +289 -0
- package/dist/demo/ContentWrapper/ContentWrapper.d.ts +4 -0
- package/dist/demo/ContentWrapper/ContentWrapper.js +9 -0
- package/dist/demo/ContentWrapper/index.d.ts +1 -0
- package/dist/demo/ContentWrapper/index.js +1 -0
- package/dist/demo/Showcase/Showcase.d.ts +9 -0
- package/dist/demo/Showcase/Showcase.js +7 -0
- package/dist/demo/Showcase/index.d.ts +1 -0
- package/dist/demo/Showcase/index.js +1 -0
- package/dist/demo/ShowcaseItem/ShowcaseItem.d.ts +8 -0
- package/dist/demo/ShowcaseItem/ShowcaseItem.js +7 -0
- package/dist/demo/ShowcaseItem/index.d.ts +1 -0
- package/dist/demo/ShowcaseItem/index.js +1 -0
- package/dist/demo/SwapArea/SwapArea.d.ts +2 -0
- package/dist/demo/SwapArea/SwapArea.js +7 -0
- package/dist/demo/SwapArea/index.d.ts +1 -0
- package/dist/demo/SwapArea/index.js +1 -0
- package/dist/hooks/useSmartScroll.d.ts +7 -2
- package/dist/hooks/useSmartScroll.js +24 -22
- package/dist/types/common.d.ts +13 -5
- package/dist/types/messages.d.ts +9 -6
- package/dist/utils/actionUtils.d.ts +14 -0
- package/dist/utils/actionUtils.js +17 -0
- package/dist/utils/messageUtils.d.ts +7 -9
- package/dist/utils/messageUtils.js +7 -1
- package/package.json +12 -7
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { DOMProps, QAProps } from '@gravity-ui/uikit';
|
|
2
|
+
import { DOMProps, InputControlSize, QAProps } from '@gravity-ui/uikit';
|
|
3
3
|
import { ChatType } from '../../../types';
|
|
4
4
|
import { ChatFilterFunction } from '../../../utils/chatUtils';
|
|
5
5
|
import './History.scss';
|
|
@@ -37,6 +37,8 @@ export interface HistoryListProps extends QAProps, DOMProps {
|
|
|
37
37
|
loading?: boolean;
|
|
38
38
|
/** Callback when chat is clicked (for closing popup in parent) */
|
|
39
39
|
onChatClick?: (chat: ChatType) => void;
|
|
40
|
+
/** Size of the list */
|
|
41
|
+
size?: InputControlSize;
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
42
44
|
* HistoryList component - displays a list of chats with search, grouping, and actions
|
|
@@ -16,7 +16,7 @@ const b = block('history');
|
|
|
16
16
|
* @returns React component
|
|
17
17
|
*/
|
|
18
18
|
export function HistoryList(props) {
|
|
19
|
-
const { chats, selectedChat, onSelectChat, onDeleteChat, onLoadMore, hasMore = false, searchable = true, groupBy = 'date', showActions = true, emptyPlaceholder, emptyFilteredPlaceholder, className, qa, style, filterFunction = defaultChatFilter, loading = false, onChatClick, } = props;
|
|
19
|
+
const { chats, selectedChat, onSelectChat, onDeleteChat, onLoadMore, hasMore = false, searchable = true, groupBy = 'date', showActions = true, emptyPlaceholder, emptyFilteredPlaceholder, className, qa, style, filterFunction = defaultChatFilter, loading = false, onChatClick, size = 'm', } = props;
|
|
20
20
|
const [filteredItemCount, setFilteredItemCount] = useState(null);
|
|
21
21
|
// Group chats if needed
|
|
22
22
|
const groupedChats = useMemo(() => {
|
|
@@ -57,7 +57,11 @@ export function HistoryList(props) {
|
|
|
57
57
|
const selectedItemIndex = useMemo(() => {
|
|
58
58
|
return listItems.findIndex((item) => item.type === 'chat' && item.id === (selectedChat === null || selectedChat === void 0 ? void 0 : selectedChat.id));
|
|
59
59
|
}, [listItems, selectedChat]);
|
|
60
|
-
const handleChatClick = (chat) => {
|
|
60
|
+
const handleChatClick = (chat, _, fromKeyboard, event) => {
|
|
61
|
+
var _a;
|
|
62
|
+
if (fromKeyboard && ((_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a.type) === 'button') {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
61
65
|
onSelectChat === null || onSelectChat === void 0 ? void 0 : onSelectChat(chat);
|
|
62
66
|
onChatClick === null || onChatClick === void 0 ? void 0 : onChatClick(chat);
|
|
63
67
|
};
|
|
@@ -78,11 +82,11 @@ export function HistoryList(props) {
|
|
|
78
82
|
};
|
|
79
83
|
};
|
|
80
84
|
}, [filterFunction]);
|
|
81
|
-
const renderItem = (item) => {
|
|
85
|
+
const renderItem = (item, isActive) => {
|
|
82
86
|
if (item.type === 'date-header') {
|
|
83
87
|
return _jsx(DateHeaderItem, { date: item.date }, `date-${item.date}`);
|
|
84
88
|
}
|
|
85
|
-
return (_jsx(ChatItem, { chat: item, showActions: showActions,
|
|
89
|
+
return (_jsx(ChatItem, { chat: item, showActions: showActions, onDeleteClick: onDeleteChat ? handleDeleteClick : undefined, isActive: isActive }, item.id));
|
|
86
90
|
};
|
|
87
91
|
const emptyState = emptyPlaceholder || _jsx("div", { className: b('empty'), children: i18n('empty-state') });
|
|
88
92
|
const emptyFilteredState = emptyFilteredPlaceholder || (_jsx("div", { className: b('empty'), children: i18n('empty-filtered-state') }));
|
|
@@ -90,5 +94,5 @@ export function HistoryList(props) {
|
|
|
90
94
|
const finalEmptyPlaceholder = filteredItemCount === null || filteredItemCount === listItems.length
|
|
91
95
|
? emptyState
|
|
92
96
|
: emptyFilteredState;
|
|
93
|
-
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, { size:
|
|
97
|
+
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, { size: size, items: listItems, renderItem: renderItem, virtualized: false, filterable: searchable, filterItem: wrappedFilterFunction, filterPlaceholder: i18n('search-placeholder'), filterClassName: b('filter'), emptyPlaceholder: finalEmptyPlaceholder, selectedItemIndex: selectedItemIndex, itemsClassName: b('list'), itemClassName: b('list-item'), onFilterEnd: (data) => setFilteredItemCount(data.items.length), onItemClick: handleChatClick })) }), hasMore && onLoadMore && (_jsx(Button, { view: "flat-action", size: "m", width: "max", onClick: onLoadMore, children: i18n('action-load-more') }))] }));
|
|
94
98
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { History } from '..';
|
|
3
|
+
declare const _default: Meta;
|
|
4
|
+
export default _default;
|
|
5
|
+
type Story = StoryObj<typeof History>;
|
|
6
|
+
export declare const Playground: Story;
|
|
7
|
+
export declare const WithSelectedChat: Story;
|
|
8
|
+
export declare const WithLoadMore: Story;
|
|
9
|
+
export declare const WithoutSearch: Story;
|
|
10
|
+
export declare const WithoutGrouping: Story;
|
|
11
|
+
export declare const WithoutActions: Story;
|
|
12
|
+
export declare const EmptyState: Story;
|
|
13
|
+
export declare const WithLoadMoreAndDelete: Story;
|
|
14
|
+
export declare const Interactive: Story;
|
|
15
|
+
export declare const WithCustomEmptyPlaceholder: Story;
|
|
16
|
+
export declare const WithCustomFilter: Story;
|
|
17
|
+
export declare const NotForceOpen: Story;
|
|
18
|
+
export declare const Loading: Story;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { __rest } from "tslib";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { ClockArrowRotateLeft } from '@gravity-ui/icons';
|
|
5
|
+
import { Icon } from '@gravity-ui/uikit';
|
|
6
|
+
import { History } from '..';
|
|
7
|
+
import { ContentWrapper } from '../../../../demo/ContentWrapper';
|
|
8
|
+
import { ActionButton } from '../../../atoms/ActionButton';
|
|
9
|
+
import MDXDocs from './Docs.mdx';
|
|
10
|
+
export default {
|
|
11
|
+
title: 'templates/History',
|
|
12
|
+
component: History,
|
|
13
|
+
parameters: {
|
|
14
|
+
docs: {
|
|
15
|
+
page: MDXDocs,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
// Generate mock chats
|
|
20
|
+
const generateMockChats = (count) => {
|
|
21
|
+
const now = new Date();
|
|
22
|
+
const chats = [];
|
|
23
|
+
for (let i = 0; i < count; i++) {
|
|
24
|
+
const daysAgo = Math.floor(i / 3); // Group 3 chats per day
|
|
25
|
+
const date = new Date(now);
|
|
26
|
+
date.setDate(date.getDate() - daysAgo);
|
|
27
|
+
chats.push({
|
|
28
|
+
id: `chat-${i}`,
|
|
29
|
+
name: `Chat ${i + 1}`,
|
|
30
|
+
createTime: date.toISOString(),
|
|
31
|
+
lastMessage: i % 3 === 0
|
|
32
|
+
? `Looooooooong last message for example ellipsis from chat ${i + 1}`
|
|
33
|
+
: `Last message from chat ${i + 1}`,
|
|
34
|
+
metadata: {},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return chats;
|
|
38
|
+
};
|
|
39
|
+
const mockChats = generateMockChats(15);
|
|
40
|
+
function HistoryWithTrigger(_a) {
|
|
41
|
+
var { initialOpen = true } = _a, props = __rest(_a, ["initialOpen"]);
|
|
42
|
+
const [open, setOpen] = useState(false);
|
|
43
|
+
const anchorRef = useRef(null);
|
|
44
|
+
const setInitialOpen = useRef(false);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (initialOpen && !open && anchorRef.current && !setInitialOpen.current) {
|
|
47
|
+
setOpen(true);
|
|
48
|
+
setInitialOpen.current = true;
|
|
49
|
+
}
|
|
50
|
+
}, [initialOpen, open, anchorRef.current, setInitialOpen.current]);
|
|
51
|
+
return (_jsxs("div", { style: { paddingLeft: '400px' }, children: [_jsx(ActionButton, { ref: anchorRef, view: "flat", size: "m", onClick: () => setOpen(!open), tooltipTitle: "Chat History", children: _jsx(Icon, { data: ClockArrowRotateLeft, size: 16 }) }), _jsx(History, Object.assign({}, props, { open: open, onOpenChange: setOpen, anchorElement: anchorRef.current }))] }));
|
|
52
|
+
}
|
|
53
|
+
const defaultDecorators = [
|
|
54
|
+
(Story) => (_jsx(ContentWrapper, { width: "600px", height: "800px", children: _jsx(Story, {}) })),
|
|
55
|
+
];
|
|
56
|
+
export const Playground = {
|
|
57
|
+
args: {
|
|
58
|
+
chats: mockChats,
|
|
59
|
+
searchable: true,
|
|
60
|
+
groupBy: 'date',
|
|
61
|
+
showActions: true,
|
|
62
|
+
onSelectChat: (chat) => {
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.log('Selected chat:', chat);
|
|
65
|
+
},
|
|
66
|
+
onDeleteChat: (chat) => {
|
|
67
|
+
// eslint-disable-next-line no-console
|
|
68
|
+
console.log('Delete chat:', chat);
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({}, args)),
|
|
72
|
+
decorators: defaultDecorators,
|
|
73
|
+
};
|
|
74
|
+
export const WithSelectedChat = {
|
|
75
|
+
args: {
|
|
76
|
+
chats: mockChats,
|
|
77
|
+
selectedChat: mockChats[2],
|
|
78
|
+
searchable: true,
|
|
79
|
+
groupBy: 'date',
|
|
80
|
+
showActions: true,
|
|
81
|
+
onSelectChat: (chat) => {
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
console.log('Selected chat:', chat);
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({}, args)),
|
|
87
|
+
decorators: defaultDecorators,
|
|
88
|
+
};
|
|
89
|
+
export const WithLoadMore = {
|
|
90
|
+
args: {
|
|
91
|
+
searchable: true,
|
|
92
|
+
groupBy: 'date',
|
|
93
|
+
showActions: true,
|
|
94
|
+
onSelectChat: (chat) => {
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
console.log('Selected chat:', chat);
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
render: (args) => {
|
|
100
|
+
const [chats, setChats] = useState(mockChats.slice(0, 6));
|
|
101
|
+
const [hasMore, setHasMore] = useState(true);
|
|
102
|
+
const handleLoadMore = () => {
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.log('Loading more chats...');
|
|
105
|
+
const currentLength = chats.length;
|
|
106
|
+
const nextChats = mockChats.slice(0, currentLength + 6);
|
|
107
|
+
setChats(nextChats);
|
|
108
|
+
if (nextChats.length >= mockChats.length) {
|
|
109
|
+
setHasMore(false);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return (_jsx(HistoryWithTrigger, Object.assign({}, args, { chats: chats, hasMore: hasMore, onLoadMore: handleLoadMore })));
|
|
113
|
+
},
|
|
114
|
+
decorators: defaultDecorators,
|
|
115
|
+
};
|
|
116
|
+
export const WithoutSearch = {
|
|
117
|
+
args: {
|
|
118
|
+
chats: mockChats,
|
|
119
|
+
searchable: false,
|
|
120
|
+
groupBy: 'date',
|
|
121
|
+
showActions: true,
|
|
122
|
+
onSelectChat: (chat) => {
|
|
123
|
+
// eslint-disable-next-line no-console
|
|
124
|
+
console.log('Selected chat:', chat);
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({}, args)),
|
|
128
|
+
decorators: defaultDecorators,
|
|
129
|
+
};
|
|
130
|
+
export const WithoutGrouping = {
|
|
131
|
+
args: {
|
|
132
|
+
chats: mockChats,
|
|
133
|
+
searchable: true,
|
|
134
|
+
groupBy: 'none',
|
|
135
|
+
showActions: true,
|
|
136
|
+
onSelectChat: (chat) => {
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.log('Selected chat:', chat);
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({}, args)),
|
|
142
|
+
decorators: defaultDecorators,
|
|
143
|
+
};
|
|
144
|
+
export const WithoutActions = {
|
|
145
|
+
args: {
|
|
146
|
+
chats: mockChats,
|
|
147
|
+
searchable: true,
|
|
148
|
+
groupBy: 'date',
|
|
149
|
+
showActions: false,
|
|
150
|
+
onSelectChat: (chat) => {
|
|
151
|
+
// eslint-disable-next-line no-console
|
|
152
|
+
console.log('Selected chat:', chat);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({}, args)),
|
|
156
|
+
decorators: defaultDecorators,
|
|
157
|
+
};
|
|
158
|
+
export const EmptyState = {
|
|
159
|
+
args: {
|
|
160
|
+
chats: [],
|
|
161
|
+
searchable: true,
|
|
162
|
+
groupBy: 'date',
|
|
163
|
+
showActions: true,
|
|
164
|
+
},
|
|
165
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({}, args)),
|
|
166
|
+
decorators: defaultDecorators,
|
|
167
|
+
};
|
|
168
|
+
export const WithLoadMoreAndDelete = {
|
|
169
|
+
args: {
|
|
170
|
+
chats: mockChats,
|
|
171
|
+
searchable: true,
|
|
172
|
+
groupBy: 'date',
|
|
173
|
+
showActions: true,
|
|
174
|
+
hasMore: true,
|
|
175
|
+
onSelectChat: (chat) => {
|
|
176
|
+
// eslint-disable-next-line no-console
|
|
177
|
+
console.log('Selected chat:', chat);
|
|
178
|
+
},
|
|
179
|
+
onDeleteChat: (chat) => {
|
|
180
|
+
// eslint-disable-next-line no-console
|
|
181
|
+
console.log('Delete chat:', chat);
|
|
182
|
+
},
|
|
183
|
+
onLoadMore: () => {
|
|
184
|
+
// eslint-disable-next-line no-console
|
|
185
|
+
console.log('Load more chats');
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({}, args)),
|
|
189
|
+
decorators: defaultDecorators,
|
|
190
|
+
};
|
|
191
|
+
export const Interactive = {
|
|
192
|
+
args: {
|
|
193
|
+
searchable: true,
|
|
194
|
+
groupBy: 'date',
|
|
195
|
+
showActions: true,
|
|
196
|
+
},
|
|
197
|
+
render: (args) => {
|
|
198
|
+
const [chats, setChats] = useState(mockChats);
|
|
199
|
+
const [selectedChat, setSelectedChat] = useState(null);
|
|
200
|
+
const handleDeleteChat = (chat) => {
|
|
201
|
+
setChats((prev) => prev.filter((c) => c.id !== chat.id));
|
|
202
|
+
if ((selectedChat === null || selectedChat === void 0 ? void 0 : selectedChat.id) === chat.id) {
|
|
203
|
+
setSelectedChat(null);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
return (_jsx(HistoryWithTrigger, Object.assign({}, args, { chats: chats, selectedChat: selectedChat, onSelectChat: setSelectedChat, onDeleteChat: handleDeleteChat })));
|
|
207
|
+
},
|
|
208
|
+
decorators: defaultDecorators,
|
|
209
|
+
};
|
|
210
|
+
export const WithCustomEmptyPlaceholder = {
|
|
211
|
+
args: {
|
|
212
|
+
chats: [],
|
|
213
|
+
searchable: true,
|
|
214
|
+
groupBy: 'date',
|
|
215
|
+
showActions: true,
|
|
216
|
+
emptyPlaceholder: (_jsxs("div", { style: {
|
|
217
|
+
padding: '40px',
|
|
218
|
+
color: '#999',
|
|
219
|
+
width: '100%',
|
|
220
|
+
display: 'flex',
|
|
221
|
+
flexDirection: 'column',
|
|
222
|
+
alignItems: 'center',
|
|
223
|
+
justifyContent: 'center',
|
|
224
|
+
}, children: [_jsx("div", { style: {
|
|
225
|
+
fontSize: '48px',
|
|
226
|
+
height: '48px',
|
|
227
|
+
display: 'flex',
|
|
228
|
+
alignItems: 'center',
|
|
229
|
+
justifyContent: 'center',
|
|
230
|
+
marginBottom: '16px',
|
|
231
|
+
}, children: "\uD83D\uDCAC" }), _jsx("div", { style: {
|
|
232
|
+
fontSize: '16px',
|
|
233
|
+
fontWeight: 'bold',
|
|
234
|
+
marginBottom: '8px',
|
|
235
|
+
}, children: "No conversations yet" }), _jsx("div", { style: { fontSize: '14px' }, children: "Start a new chat to begin" })] })),
|
|
236
|
+
},
|
|
237
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({}, args)),
|
|
238
|
+
decorators: defaultDecorators,
|
|
239
|
+
};
|
|
240
|
+
export const WithCustomFilter = {
|
|
241
|
+
args: {
|
|
242
|
+
chats: mockChats,
|
|
243
|
+
searchable: true,
|
|
244
|
+
groupBy: 'date',
|
|
245
|
+
showActions: true,
|
|
246
|
+
onSelectChat: (chat) => {
|
|
247
|
+
// eslint-disable-next-line no-console
|
|
248
|
+
console.log('Selected chat:', chat);
|
|
249
|
+
},
|
|
250
|
+
// Custom filter that searches only in chat names (not in messages)
|
|
251
|
+
filterFunction: (filter) => (item) => {
|
|
252
|
+
if (item.type === 'date-header') {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
return item.name.toLowerCase().includes(filter.toLowerCase());
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({}, args)),
|
|
259
|
+
decorators: defaultDecorators,
|
|
260
|
+
};
|
|
261
|
+
export const NotForceOpen = {
|
|
262
|
+
args: {
|
|
263
|
+
chats: mockChats,
|
|
264
|
+
searchable: true,
|
|
265
|
+
groupBy: 'date',
|
|
266
|
+
showActions: true,
|
|
267
|
+
onSelectChat: (chat) => {
|
|
268
|
+
// eslint-disable-next-line no-console
|
|
269
|
+
console.log('Selected chat:', chat);
|
|
270
|
+
},
|
|
271
|
+
onDeleteChat: (chat) => {
|
|
272
|
+
// eslint-disable-next-line no-console
|
|
273
|
+
console.log('Delete chat:', chat);
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({ initialOpen: false }, args)),
|
|
277
|
+
decorators: defaultDecorators,
|
|
278
|
+
};
|
|
279
|
+
export const Loading = {
|
|
280
|
+
args: {
|
|
281
|
+
chats: [],
|
|
282
|
+
loading: true,
|
|
283
|
+
searchable: true,
|
|
284
|
+
groupBy: 'date',
|
|
285
|
+
showActions: true,
|
|
286
|
+
},
|
|
287
|
+
render: (args) => _jsx(HistoryWithTrigger, Object.assign({}, args)),
|
|
288
|
+
decorators: defaultDecorators,
|
|
289
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { __rest } from "tslib";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
import './ContentWrapper.scss';
|
|
5
|
+
const b = cn('content-wrapper');
|
|
6
|
+
export function ContentWrapper(props) {
|
|
7
|
+
const { children } = props, style = __rest(props, ["children"]);
|
|
8
|
+
return (_jsx("div", { className: b(), style: style, children: children }));
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ContentWrapper } from './ContentWrapper';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ContentWrapper } from './ContentWrapper';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import './Showcase.scss';
|
|
2
|
+
type Props = React.PropsWithChildren<{
|
|
3
|
+
title?: string;
|
|
4
|
+
description?: React.ReactNode;
|
|
5
|
+
direction?: 'row' | 'column';
|
|
6
|
+
className?: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function Showcase({ title, description, direction, className, children }: Props): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../utils/cn';
|
|
3
|
+
import './Showcase.scss';
|
|
4
|
+
const b = cn('showcase');
|
|
5
|
+
export function Showcase({ title, description, direction = 'row', className, children }) {
|
|
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 })] }));
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Showcase } from './Showcase';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Showcase } from './Showcase';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import './ShowcaseItem.scss';
|
|
2
|
+
interface ShowcaseItemProps {
|
|
3
|
+
title: string;
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
width?: string | number;
|
|
6
|
+
}
|
|
7
|
+
export declare function ShowcaseItem({ title, children, width }: ShowcaseItemProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../utils/cn';
|
|
3
|
+
import './ShowcaseItem.scss';
|
|
4
|
+
const b = cn('showcase-item');
|
|
5
|
+
export function ShowcaseItem({ title, children, width }) {
|
|
6
|
+
return (_jsxs("div", { className: b(), style: { width }, children: [_jsx("div", { className: b('title'), children: title }), _jsx("div", { className: b('content'), children: children })] }));
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ShowcaseItem } from './ShowcaseItem';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ShowcaseItem } from './ShowcaseItem';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SwapArea } from './SwapArea';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SwapArea } from './SwapArea';
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { type RefObject } from 'react';
|
|
2
|
+
import type { ChatStatus } from '../types';
|
|
2
3
|
export interface UseSmartScrollReturn<T extends HTMLElement> {
|
|
3
4
|
containerRef: RefObject<T>;
|
|
4
5
|
endRef: RefObject<T>;
|
|
5
|
-
scrollToBottom: () => void;
|
|
6
|
+
scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
6
7
|
}
|
|
7
|
-
export declare function useSmartScroll<T extends HTMLElement>(isStreaming
|
|
8
|
+
export declare function useSmartScroll<T extends HTMLElement>({ isStreaming, messagesCount, status, }: {
|
|
9
|
+
isStreaming?: boolean;
|
|
10
|
+
messagesCount: number;
|
|
11
|
+
status?: ChatStatus;
|
|
12
|
+
}): UseSmartScrollReturn<T>;
|
|
@@ -1,26 +1,21 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
2
|
const SCROLL_THRESHOLD = 10;
|
|
3
|
-
export function useSmartScroll(isStreaming = false, messagesCount
|
|
3
|
+
export function useSmartScroll({ isStreaming = false, messagesCount, status, }) {
|
|
4
4
|
const containerRef = useRef(null);
|
|
5
5
|
const endRef = useRef(null);
|
|
6
|
-
const
|
|
7
|
-
const prevMessagesCount = useRef(messagesCount);
|
|
8
|
-
const checkIfUserScrolledUp = useCallback(() => {
|
|
9
|
-
const container = containerRef.current;
|
|
10
|
-
if (!container)
|
|
11
|
-
return false;
|
|
12
|
-
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
13
|
-
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
|
14
|
-
return distanceFromBottom > SCROLL_THRESHOLD;
|
|
15
|
-
}, []);
|
|
6
|
+
const userScrolledUpRef = useRef(false);
|
|
16
7
|
const scrollToBottom = useCallback((behavior = 'instant') => {
|
|
17
|
-
if (!
|
|
8
|
+
if (!userScrolledUpRef.current) {
|
|
18
9
|
const end = endRef.current;
|
|
19
10
|
if (end) {
|
|
20
11
|
end.scrollIntoView({ behavior, block: 'end' });
|
|
21
12
|
}
|
|
22
13
|
}
|
|
23
|
-
}, [
|
|
14
|
+
}, []);
|
|
15
|
+
// Initial scroll to bottom
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
scrollToBottom();
|
|
18
|
+
}, []);
|
|
24
19
|
// Handle user scroll events
|
|
25
20
|
useEffect(() => {
|
|
26
21
|
const container = containerRef.current;
|
|
@@ -28,14 +23,19 @@ export function useSmartScroll(isStreaming = false, messagesCount = 0) {
|
|
|
28
23
|
return undefined;
|
|
29
24
|
}
|
|
30
25
|
const handleScroll = () => {
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
if (!container) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
30
|
+
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
|
31
|
+
const scrolledUp = distanceFromBottom > SCROLL_THRESHOLD;
|
|
32
|
+
userScrolledUpRef.current = scrolledUp;
|
|
33
33
|
};
|
|
34
34
|
container.addEventListener('scroll', handleScroll, { passive: true });
|
|
35
35
|
return () => {
|
|
36
36
|
container.removeEventListener('scroll', handleScroll);
|
|
37
37
|
};
|
|
38
|
-
}, [
|
|
38
|
+
}, []);
|
|
39
39
|
// Handle DOM mutations during streaming
|
|
40
40
|
useEffect(() => {
|
|
41
41
|
const container = containerRef.current;
|
|
@@ -54,14 +54,16 @@ export function useSmartScroll(isStreaming = false, messagesCount = 0) {
|
|
|
54
54
|
return () => {
|
|
55
55
|
observer.disconnect();
|
|
56
56
|
};
|
|
57
|
-
}, [isStreaming
|
|
58
|
-
// Handle
|
|
57
|
+
}, [isStreaming]);
|
|
58
|
+
// Handle status changes
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
scrollToBottom('smooth');
|
|
61
|
+
}, [status]);
|
|
59
62
|
useEffect(() => {
|
|
60
|
-
if (messagesCount
|
|
63
|
+
if (messagesCount) {
|
|
61
64
|
scrollToBottom('smooth');
|
|
62
65
|
}
|
|
63
|
-
|
|
64
|
-
}, [messagesCount, scrollToBottom]);
|
|
66
|
+
}, [messagesCount]);
|
|
65
67
|
return {
|
|
66
68
|
containerRef,
|
|
67
69
|
endRef,
|
package/dist/types/common.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { ButtonButtonProps } from '@gravity-ui/uikit';
|
|
2
|
+
/**
|
|
3
|
+
* Action configuration object with properties
|
|
4
|
+
*/
|
|
5
|
+
export type ActionConfig = {
|
|
6
|
+
actionType?: string;
|
|
3
7
|
label?: string;
|
|
4
|
-
onClick?: () => void;
|
|
5
|
-
view?: ButtonView;
|
|
6
8
|
icon?: React.ReactNode;
|
|
7
|
-
};
|
|
9
|
+
} & ButtonButtonProps;
|
|
10
|
+
/**
|
|
11
|
+
* Action can be either:
|
|
12
|
+
* - ActionConfig object with properties (label, icon, onClick, view)
|
|
13
|
+
* - React.ReactNode for fully custom content
|
|
14
|
+
*/
|
|
15
|
+
export type Action = ActionConfig | React.ReactNode;
|
package/dist/types/messages.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import { ActionConfig } from './common';
|
|
3
3
|
import { ToolMessageProps } from './tool';
|
|
4
|
+
export type BaseMessageActionConfig = ActionConfig;
|
|
5
|
+
/**
|
|
6
|
+
* BaseMessage action can be either:
|
|
7
|
+
* - BaseMessageActionConfig object with properties (including type for default icons)
|
|
8
|
+
* - React.ReactNode for fully custom content
|
|
9
|
+
*/
|
|
10
|
+
export type BaseMessageAction = BaseMessageActionConfig | React.ReactNode;
|
|
4
11
|
export type BaseMessageProps = {
|
|
5
12
|
children: React.ReactNode;
|
|
6
13
|
role: TMessageRole;
|
|
7
|
-
actions?:
|
|
8
|
-
type: string;
|
|
9
|
-
onClick: () => void;
|
|
10
|
-
icon?: IconData;
|
|
11
|
-
}>;
|
|
14
|
+
actions?: BaseMessageAction[];
|
|
12
15
|
timestamp?: string;
|
|
13
16
|
showTimestamp?: boolean;
|
|
14
17
|
showActionsOnHover?: boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Action, ActionConfig } from '../types/common';
|
|
3
|
+
/**
|
|
4
|
+
* Type guard для проверки, является ли action React элементом
|
|
5
|
+
* @param action - Проверяемое значение
|
|
6
|
+
* @returns true если action является React.ReactNode, false если это ActionConfig
|
|
7
|
+
*/
|
|
8
|
+
export declare function isReactNodeAction(action: Action): action is React.ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Type guard для проверки, является ли action объектом конфигурации
|
|
11
|
+
* @param action - Проверяемое значение
|
|
12
|
+
* @returns true если action является ActionConfig, false если это React.ReactNode
|
|
13
|
+
*/
|
|
14
|
+
export declare function isActionConfig(action: Action): action is ActionConfig;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Type guard для проверки, является ли action React элементом
|
|
4
|
+
* @param action - Проверяемое значение
|
|
5
|
+
* @returns true если action является React.ReactNode, false если это ActionConfig
|
|
6
|
+
*/
|
|
7
|
+
export function isReactNodeAction(action) {
|
|
8
|
+
return React.isValidElement(action);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Type guard для проверки, является ли action объектом конфигурации
|
|
12
|
+
* @param action - Проверяемое значение
|
|
13
|
+
* @returns true если action является ActionConfig, false если это React.ReactNode
|
|
14
|
+
*/
|
|
15
|
+
export function isActionConfig(action) {
|
|
16
|
+
return !React.isValidElement(action);
|
|
17
|
+
}
|