diff --git a/packages/@react-spectrum/ai/src/Chat.tsx b/packages/@react-spectrum/ai/src/Chat.tsx
index 896eb42c226..53c53b6f88c 100644
--- a/packages/@react-spectrum/ai/src/Chat.tsx
+++ b/packages/@react-spectrum/ai/src/Chat.tsx
@@ -10,8 +10,10 @@
* governing permissions and limitations under the License.
*/
+import {ActionButton} from '@react-spectrum/s2';
import {announce} from 'react-aria/private/live-announcer/LiveAnnouncer';
import {ButtonContext} from 'react-aria-components/Button';
+import ChevronDown from '@react-spectrum/s2/icons/ChevronDown';
import {
CollectionRendererContext,
createLeafComponent
@@ -34,6 +36,7 @@ import {DEFAULT_SLOT, Provider} from 'react-aria-components/slots';
import {DOMRef, forwardRefType, Node} from '@react-types/shared';
import {filterDOMProps} from 'react-aria/filterDOMProps';
import {focusRing, style, StyleString} from '@react-spectrum/s2/style' with {type: 'macro'};
+// @ts-ignore
import {
GridList,
GridListItem,
@@ -42,12 +45,12 @@ import {
GridListProps
} from 'react-aria-components/GridList';
import {inertValue} from 'react-aria/private/utils/inertValue';
-// @ts-ignore
import intlMessages from '../intl/*.json';
import {ListLayout} from './ListLayout';
import {ListStateContext} from 'react-aria-components/ListBox';
import {LoaderNode} from 'react-aria/private/collections/BaseCollection';
import {mergeStyles} from '@react-spectrum/s2/mergeStyles';
+import {scrollFade} from './tokens.macro' with {type: 'macro'};
import {useDOMRef} from './useDOMRef';
import {useEnterAnimation, useExitAnimation} from 'react-aria/private/utils/animation';
import {useFocusWithin} from 'react-aria/useFocusWithin';
@@ -227,7 +230,23 @@ export const Chat = /*#__PURE__*/ (forwardRef as forwardRefType)(function Chat(
}
]
]}>
-
+
{children}
@@ -288,39 +307,77 @@ export function Thread
(props: ThreadProps) {
}, [setIsNearBottom, scrollEndThreshold]);
return (
-
-
- {children}
-
-
+
+ {/* TODO: do we want the scroll button to be optional? */}
+
+
+
+ {children}
+
+
+
);
}
diff --git a/packages/@react-spectrum/ai/stories/Chat.stories.tsx b/packages/@react-spectrum/ai/stories/Chat.stories.tsx
index 25535f6ae81..3f9c9d17cba 100644
--- a/packages/@react-spectrum/ai/stories/Chat.stories.tsx
+++ b/packages/@react-spectrum/ai/stories/Chat.stories.tsx
@@ -16,7 +16,6 @@ import {ActionMenu} from '@react-spectrum/s2/ActionMenu';
import {AssetCard, CardPreview} from '@react-spectrum/s2/Card';
import {Chat} from '../src/Chat';
import ChatIcon from '@react-spectrum/s2/icons/Chat';
-import ChevronDown from '@react-spectrum/s2/icons/ChevronDown';
import {Collection} from 'react-aria-components';
import {Content} from '@react-spectrum/s2/Content';
import {DialogTrigger, Popover} from '@react-spectrum/s2/Popover';
@@ -40,7 +39,6 @@ import {
Thread,
ThreadItem,
ThreadLoadMoreItem,
- ThreadScrollButton,
TokenFieldValue,
UserMessage
} from '@react-spectrum/ai';
@@ -483,160 +481,106 @@ export function VirtualizedStreamingChat() {
}
return (
- // TODO: these extra div wrappers would need to be implemented by the RAC user, maybe we can internalize some more?
- // of particular note is the scroll button. Same for the other styles
-
-
-
-
-
- {(msg: StreamingMessage) => {
- if (msg.type === 'user') {
- // TODO: probably want ThreadItem to be a part of UserMessage?
- return (
-
- {msg.content}
-
- );
- }
- if (msg.type === 'status') {
- return ;
- }
- if (msg.type === 'card') {
- return (
-
- );
- }
- if (msg.type === 'suggestions') {
- // TODO: probably should have ThreadItem auto wrap MessageSuggestionList as well
- // but this one I could see perhaps being a standalone component to be used outside of thread
- return (
-
-
- {msg.suggestions.map((s, i) => (
- {s}
- ))}
-
-
- );
- }
- return (
-
-
- {!msg.isStreaming && }
-
- );
- }}
-
-
- {
- setPromptValue(new PromptFieldValue([]));
- handleSend(prompt);
- }}
- isGenerating={isGenerating}
- onStop={handleStop}>
-
+
+ {(msg: StreamingMessage) => {
+ if (msg.type === 'user') {
+ // TODO: probably want ThreadItem to be a part of UserMessage?
+ return (
+
+ {msg.content}
+
+ );
+ }
+ if (msg.type === 'status') {
+ return ;
+ }
+ if (msg.type === 'card') {
+ return (
+
+ );
+ }
+ if (msg.type === 'suggestions') {
+ // TODO: probably should have ThreadItem auto wrap MessageSuggestionList as well
+ // but this one I could see perhaps being a standalone component to be used outside of thread
+ // DG: maybe we could auto-wrap if it's inside a Thread?
+ return (
+
+
+ {msg.suggestions.map((s, i) => (
+ {s}
+ ))}
+
+
+ );
+ }
+ return (
+
+ {/* TODO: make this a component? Build it into SystemMessage? */}
+
+ {!msg.isStreaming && }
+
+ );
+ }}
+
+ {
+ setPromptValue(new PromptFieldValue([]));
+ handleSend(prompt);
+ }}
+ isGenerating={isGenerating}
+ onStop={handleStop}>
+ {
+ if (!isGenerating) {
+ return;
}
- onKeyDown={e => {
- if (!isGenerating) {
- return;
- }
- // TODO: we could make this even more realistic but for now just fire storybook event
- // and add follow up message to queue
- if (e.key === 'Enter' && !e.altKey) {
- e.preventDefault();
- if (promptValue.segments.length > 0) {
- action('onSteer')(promptValue.toString());
- setPromptValue(new PromptFieldValue([]));
- }
- } else if (e.key === 'Enter' && e.altKey) {
- e.preventDefault();
- if (promptValue.segments.length > 0) {
- action('onFollowUp')(promptValue.toString());
- followUpMessage.current = promptValue;
- setPromptValue(new PromptFieldValue([]));
- }
- } else if (e.key === 'Escape') {
- e.preventDefault();
- handleStop();
+ // TODO: we could make this even more realistic but for now just fire storybook event
+ // and add follow up message to queue
+ if (e.key === 'Enter' && !e.altKey) {
+ e.preventDefault();
+ if (promptValue.segments.length > 0) {
+ action('onSteer')(promptValue.toString());
+ setPromptValue(new PromptFieldValue([]));
}
- }}
- />
-
-
-
-
-
-
-
+ } else if (e.key === 'Enter' && e.altKey) {
+ e.preventDefault();
+ if (promptValue.segments.length > 0) {
+ action('onFollowUp')(promptValue.toString());
+ followUpMessage.current = promptValue;
+ setPromptValue(new PromptFieldValue([]));
+ }
+ } else if (e.key === 'Escape') {
+ e.preventDefault();
+ handleStop();
+ }
+ }}
+ />
+
+
+
+
+
+
);
}
@@ -696,119 +640,65 @@ export function EmptyChat() {
}
return (
-
-
-
-
-
- {(msg: StreamingMessage) => {
- if (msg.type === 'user') {
- return (
-
- {msg.content}
-
- );
- }
- if (msg.type === 'status') {
- return ;
- }
- if (msg.type === 'card') {
- return (
-
- );
- }
- if (msg.type === 'suggestions') {
- return (
-
-
- {msg.suggestions.map((s, i) => (
- {s}
- ))}
-
-
- );
- }
- return (
-
-
- {!msg.isStreaming && }
-
- );
- }}
-
+
+
+ {(msg: StreamingMessage) => {
+ if (msg.type === 'user') {
+ return (
+
+ {msg.content}
+
+ );
+ }
+ if (msg.type === 'status') {
+ return ;
+ }
+ if (msg.type === 'card') {
+ return (
+
+ );
+ }
+ if (msg.type === 'suggestions') {
+ return (
+
+
+ {msg.suggestions.map((s, i) => (
+ {s}
+ ))}
+
+
+ );
+ }
+ return (
+
+
+ {!msg.isStreaming && }
+
+ );
+ }}
+
+ {
+ setGenerating(false);
+ timeouts.current.forEach(clearTimeout);
+ timeouts.current = [];
+ }}>
+
- {
- setGenerating(false);
- timeouts.current.forEach(clearTimeout);
- timeouts.current = [];
- }}>
-
-
-
-
+
+
);
}
@@ -875,28 +765,8 @@ export function ChatPopover() {
-
-
+
+
{(msg: PopoverMessage) => {
if (msg.type === 'user') {
return (
@@ -1236,77 +1106,23 @@ export function AsyncLoadingChat() {
const {messages, isLoadingMore, handleLoadMore, hasMore} = useAsyncMessages();
return (
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
- {renderAsyncMessage}
-
+
+ {renderAsyncMessage}
+
+
+
-
-
-
-
-
+
+
);
}