Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ build-s2-docs: check-starter-css
# Build old docs pages, which get inter-mixed with the new pages
# TODO: We probably don't need to build this on every PR
yarn parcel build 'packages/@adobe/react-spectrum/docs/**/*.mdx' 'packages/dev/docs/pages/{react-spectrum,releases}/**/*.mdx' --dist-dir dist/s2-docs/s2/$(PUBLIC_URL) --public-url $(PUBLIC_URL)
yarn parcel build 'packages/{react-aria,react-stately}/docs/**/*.mdx' --dist-dir dist/s2-docs/react-aria/$(PUBLIC_URL) --public-url $(PUBLIC_URL)

build-starters:
$(MAKE) starter-zip
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
"start:s2": "NODE_ENV=storybook storybook dev -p 6006 --ci -c '.storybook-s2'",
"build:storybook-s2": "NODE_ENV=storybook storybook build -c .storybook-s2 -o dist/$(git rev-parse HEAD)/storybook-s2",
"build:s2-storybook-docs": "NODE_ENV=storybook storybook build -c .storybook-s2 --docs",
"start:docs": "DOCS_ENV=dev parcel 'packages/{@adobe/react-spectrum,react-aria,react-stately}/docs/**/*.mdx' 'packages/dev/docs/pages/{react-spectrum,releases}/**/*.mdx'",
"build:docs": "DOCS_ENV=staging parcel build 'packages/{@adobe/react-spectrum,react-aria,react-stately}/docs/**/*.mdx' 'packages/dev/docs/pages/{react-spectrum,releases}/**/*.mdx'",
"start:docs": "DOCS_ENV=dev parcel 'packages/@adobe/react-spectrum/docs/**/*.mdx' 'packages/dev/docs/pages/{react-spectrum,releases}/**/*.mdx'",
"build:docs": "DOCS_ENV=staging parcel build 'packages/@adobe/react-spectrum/docs/**/*.mdx' 'packages/dev/docs/pages/{react-spectrum,releases}/**/*.mdx'",
"start:s2-docs": "yarn workspace @react-spectrum/s2-docs start",
"build:s2-docs": "yarn workspace @react-spectrum/s2-docs build",
"check:s2-docs-build": "node packages/dev/s2-docs/scripts/validateS2DocsBuild.mjs",
Expand Down
12 changes: 6 additions & 6 deletions packages/@adobe/react-spectrum/docs/dnd/dnd.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import docs from 'docs:@react-spectrum/dnd';
import dndDocs from 'docs:@react-types/shared/src/dnd.d.ts';
import {FunctionAPI, PageDescription, TypeContext, InterfaceType} from '@react-spectrum/docs';
import {Keyboard} from '@react-spectrum/text';
import DragPreview from '/packages/react-aria/docs/dnd/DragPreview.svg';
import BetweenDropPosition from '/packages/react-aria/docs/dnd/BetweenDropPosition.svg';
import OnDropPosition from '/packages/react-aria/docs/dnd/OnDropPosition.svg';
import RootDropPosition from '/packages/react-aria/docs/dnd/RootDropPosition.svg';
import DragAffordance from '/packages/react-aria/docs/dnd/DragAffordance.svg';
import Anatomy from '/packages/react-aria/docs/dnd/Anatomy.svg';
import DragPreview from '/packages/dev/s2-docs/pages/react-aria/DndDragPreview.svg';
import BetweenDropPosition from '/packages/dev/s2-docs/pages/react-aria/DndBetweenDropPosition.svg';
import OnDropPosition from '/packages/dev/s2-docs/pages/react-aria/DndOnDropPosition.svg';
import RootDropPosition from '/packages/dev/s2-docs/pages/react-aria/DndRootDropPosition.svg';
import DragAffordance from '/packages/dev/s2-docs/pages/react-aria/DndDragAffordance.svg';
import Anatomy from '/packages/dev/s2-docs/pages/react-aria/DndAnatomy.svg';

---
category: Concepts
Expand Down
7 changes: 5 additions & 2 deletions packages/@react-spectrum/ai/exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export {MessageFeedback} from '../src/MessageFeedback';
export {MessageSource, SourceList, SourceListItem} from '../src/MessageSource';
export {MessageSuggestion, MessageSuggestionList} from '../src/MessageSuggestion';
export {
AutoLinkingTokenFieldValue,
PromptFieldValue,
PromptField,
PromptFieldSubmitButton,
PromptTokenField,
Expand Down Expand Up @@ -47,7 +47,10 @@ export type {
PromptTokenFieldPopoverProps,
PromptFieldToolbarProps,
InsertMenuItemProps,
PromptFieldVoiceButtonProps
PromptFieldVoiceButtonProps,
InsertTokenMenuItemProps,
InsertTextMenuItemProps,
CommandMenuItemProps
} from '../src/PromptField';
export type {MessageFeedbackProps} from '../src/MessageFeedback';
export type {MessageSourceProps, SourceListProps, SourceListItemProps} from '../src/MessageSource';
Expand Down
1 change: 0 additions & 1 deletion packages/@react-spectrum/ai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
"dependencies": {
"@internationalized/date": "^3.12.2",
"@internationalized/number": "^3.6.7",
"@react-aria/utils": "^3.8.0",
"@react-spectrum/s2": "1.5.1",
"@react-types/shared": "^3.36.0",
"react-aria": "3.50.0",
Expand Down
5 changes: 4 additions & 1 deletion packages/@react-spectrum/ai/src/AttachmentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ export const AttachmentList = (forwardRef as forwardRefType)(function Attachment

export interface AttachmentProps
extends
Omit<CardProps, 'styles' | 'UNSAFE_className' | 'UNSAFE_style'>,
Omit<
CardProps,
'styles' | 'UNSAFE_className' | 'UNSAFE_style' | 'allowsArrowNavigation' | 'focusMode'
>,
AriaLabelingProps,
Pick<TagProps, 'id' | 'textValue' | 'render'> {
/** The children of the Attachment. */
Expand Down
112 changes: 84 additions & 28 deletions packages/@react-spectrum/ai/src/PromptField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,26 @@ import Microphone from '@react-spectrum/s2/icons/Microphone';
import {PixelLoader} from './loader/react';
import Plus from '@react-spectrum/s2/icons/Add';
import {Popover, PopoverProps} from '@react-spectrum/s2/Popover';
import {PromptFieldContainer} from './PromptFieldContainer';
import {PromptFocusContext} from './Chat';
import Send from '@react-spectrum/s2/icons/ArrowUpSend';
import {setTokenFieldSelection} from 'react-aria/useTokenField';
import Stop from '@react-spectrum/s2/icons/StopProcessing';
import {ToggleButton} from '@react-spectrum/s2/ToggleButton';
import {
positionToDOMRange,
Token,
TokenField,
tokenFieldPositionToDOMRange,
TokenInput,
TokenProps
} from 'react-aria-components/TokenField';
import {PromptFieldContainer} from './PromptFieldContainer';
import {PromptFocusContext} from './Chat';
import Send from '@react-spectrum/s2/icons/ArrowUpSend';
import {setSelection} from 'react-aria/useTokenField';
import Stop from '@react-spectrum/s2/icons/StopProcessing';
import {ToggleButton} from '@react-spectrum/s2/ToggleButton';
import {Tooltip, TooltipTrigger} from '@react-spectrum/s2/Tooltip';
import {useControlledState} from 'react-stately/useControlledState';
import {useDOMRef} from './useDOMRef';
import {useEffectEvent} from 'react-aria/private/utils/useEffectEvent';
import {useFocusWithin} from 'react-aria/useFocusWithin';
import {useKeyboard} from 'react-aria/useKeyboard';
import {useLocale} from 'react-aria/I18nProvider';
import {useLocalizedStringFormatter} from 'react-aria/useLocalizedStringFormatter';
import {useVoiceInput, VoiceInputErrorCode} from './useVoiceInput';

Expand Down Expand Up @@ -146,7 +147,7 @@ function tokenizeURLs(text: string): TokenFieldSegment[] {
return segments;
}

export class AutoLinkingTokenFieldValue extends TokenFieldValue {
export class PromptFieldValue extends TokenFieldValue {
tokenize(text: string): TokenFieldSegment[] {
return tokenizeURLs(text);
}
Expand All @@ -155,7 +156,7 @@ export class AutoLinkingTokenFieldValue extends TokenFieldValue {
const PromptFieldContext = createContext<PromptFieldState>({
attachments: [],
setAttachments: () => {},
prompt: new AutoLinkingTokenFieldValue([]),
prompt: new PromptFieldValue([]),
setPrompt: () => {},
inputRef: createRef(),
isGenerating: false,
Expand Down Expand Up @@ -199,7 +200,7 @@ export const PromptField = forwardRef(function PromptField(
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/ai');
let [prompt, setPrompt] = useControlledState(
props.value,
props.defaultValue ?? new AutoLinkingTokenFieldValue([]),
props.defaultValue ?? new PromptFieldValue([]),
props.onChange
);
let [attachments, setAttachments] = useControlledState(
Expand Down Expand Up @@ -247,7 +248,7 @@ export const PromptField = forwardRef(function PromptField(

props.onSubmit?.(prompt, attachments);
if (!isPromptControlled) {
setPrompt(new AutoLinkingTokenFieldValue([]));
setPrompt(new PromptFieldValue([]));
}
if (!isAttachmentsControlled) {
setAttachments([]);
Expand Down Expand Up @@ -479,7 +480,7 @@ export function PromptTokenField(props: PromptTokenFieldProps) {
);
}

export interface PromptTokenFieldPopoverProps extends PopoverProps {
export interface PromptTokenFieldPopoverProps extends Omit<PopoverProps, 'shouldSkipAnimation'> {
filterAnchor?: Position | null;
items?: React.ReactNode[] | null | Promise<React.ReactNode[] | null>;
isFocused?: boolean;
Expand Down Expand Up @@ -507,7 +508,7 @@ function PromptTokenFieldPopover(props: PromptTokenFieldPopoverProps) {
hideArrow
placement="bottom start"
getTargetRect={target => {
return positionToDOMRange(target, filterAnchor!).getBoundingClientRect();
return tokenFieldPositionToDOMRange(target, filterAnchor!).getBoundingClientRect();
}}>
<PromptCompletionAnchorContext.Provider value={filterAnchor ?? null}>
<Menu>{menuItems}</Menu>
Expand Down Expand Up @@ -614,7 +615,9 @@ export interface PromptFieldVoiceButtonProps {
}

export function PromptFieldVoiceButton(props: PromptFieldVoiceButtonProps) {
let {lang, isDisabled: isDisabledProp, onError} = props;
let {lang: langProp, isDisabled: isDisabledProp, onError} = props;
let {locale} = useLocale();
let lang = langProp ?? locale;
let {prompt, setPrompt, inputRef, setListening} = useContext(PromptFieldContext);
let isDisabled = isDisabledProp;
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/ai');
Expand All @@ -640,7 +643,7 @@ export function PromptFieldVoiceButton(props: PromptFieldVoiceButtonProps) {
// to be inaccurate
let finalPrompt = buildVoicePrompt(basePromptRef.current, transcript);
inputRef.current.focus();
setSelection(inputRef.current, finalPrompt.caretPosition, finalPrompt.caretPosition);
setTokenFieldSelection(inputRef.current, finalPrompt.caretPosition, finalPrompt.caretPosition);
setPrompt(finalPrompt);
});

Expand Down Expand Up @@ -696,15 +699,11 @@ export function PromptFieldVoiceButton(props: PromptFieldVoiceButtonProps) {
);
}

function buildVoicePrompt(base: TokenFieldValue, voiceText: string): AutoLinkingTokenFieldValue {
function buildVoicePrompt(base: TokenFieldValue, voiceText: string): PromptFieldValue {
if (!voiceText) {
return base as AutoLinkingTokenFieldValue;
return base as PromptFieldValue;
}
return base.replaceRange(
base.caretPosition,
base.caretPosition,
voiceText
) as AutoLinkingTokenFieldValue;
return base.replaceRange(base.caretPosition, base.caretPosition, voiceText) as PromptFieldValue;
}

export interface InsertMenuItemProps {
Expand Down Expand Up @@ -788,7 +787,7 @@ function useInsertPromptSegment(buildSegments: (item: any) => TokenFieldSegment[
inputRef.current.focus();
// we need to update the position manually since TokenField's update caret logic only happens if the field is focused
// but this insert can happen from the + menu aka the field isn't focused until this gets called which is too late
setSelection(inputRef.current, position, position);
setTokenFieldSelection(inputRef.current, position, position);
// the above focus and setCursor call can cause the internally tracked caret position to be reset incorrectly
// seemingly due to TokenField's isProgrammaticSelectionChange being flipped to false by setCursor and thus reset to 0 by the .focus
// fix this by resetting to proper position below
Expand All @@ -800,27 +799,84 @@ function useInsertPromptSegment(buildSegments: (item: any) => TokenFieldSegment[
};
}

export function InsertTokenMenuItem(props: MenuItemProps) {
export interface InsertTokenMenuItemProps extends Omit<
MenuItemProps,
| 'UNSAFE_className'
| 'UNSAFE_style'
| 'download'
| 'href'
| 'hrefLang'
| 'ping'
| 'referrerPolicy'
| 'rel'
| 'routerOptions'
| 'target'
> {}

export function InsertTokenMenuItem(props: InsertTokenMenuItemProps) {
let insert = useInsertPromptSegment(item => [
{type: 'token', text: 'command' in item ? item.command : item.title, value: item},
{type: 'text', text: ' '}
]);

return <MenuItem {...props} onAction={() => insert(props.value)} />;
return (
<MenuItem
{...props}
onAction={() => {
insert(props.value);
props.onAction?.();
}}
/>
);
}

export function InsertTextMenuItem(props: MenuItemProps) {
export interface InsertTextMenuItemProps extends Omit<
MenuItemProps,
| 'UNSAFE_className'
| 'UNSAFE_style'
| 'download'
| 'href'
| 'hrefLang'
| 'ping'
| 'referrerPolicy'
| 'rel'
| 'routerOptions'
| 'target'
> {}

export function InsertTextMenuItem(props: InsertTextMenuItemProps) {
let insert = useInsertPromptSegment(item => [
{type: 'text', text: `${'command' in item ? item.command : item.title} `}
]);

return <MenuItem {...props} onAction={() => insert(props.value)} />;
return (
<MenuItem
{...props}
onAction={() => {
insert(props.value);
props.onAction?.();
}}
/>
);
}

export interface CommandMenuItemProps extends Omit<
MenuItemProps,
| 'UNSAFE_className'
| 'UNSAFE_style'
| 'download'
| 'href'
| 'hrefLang'
| 'ping'
| 'referrerPolicy'
| 'rel'
| 'routerOptions'
| 'target'
> {}
// specifically for menu items that only trigger a callback in the autocomplete menu
// since they dont end up inserting a token or text, we need to clear the partial text that the user used
// to filter the menu
export function CommandMenuItem(props: MenuItemProps) {
export function CommandMenuItem(props: CommandMenuItemProps) {
let insert = useInsertPromptSegment(() => []);
return (
<MenuItem
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-spectrum/ai/src/ResponseStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import React, {
} from 'react';
import {StyleString} from '@react-spectrum/s2/style' with {type: 'macro'};
import {useDOMRef} from './useDOMRef';
import {useLayoutEffect} from '@react-aria/utils';
import {useLayoutEffect} from 'react-aria/private/utils/useLayoutEffect';
import {useLocale} from 'react-aria/I18nProvider';
import {useLocalizedStringFormatter} from 'react-aria/useLocalizedStringFormatter';

Expand Down
26 changes: 13 additions & 13 deletions packages/@react-spectrum/ai/stories/Chat.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@ import {action} from 'storybook/actions';
import {ActionButton} from '@react-spectrum/s2/ActionButton';
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';
import {Image} from '@react-spectrum/s2/Image';
import {MenuItem} from '@react-spectrum/s2/Menu';
import {
AutoLinkingTokenFieldValue,
MessageFeedback,
MessageSource,
MessageSuggestion,
MessageSuggestionList,
PromptField,
PromptFieldSubmitButton,
PromptFieldValue,
PromptTokenField,
ResponseStatus,
ResponseStatusPanel,
Expand All @@ -35,14 +43,6 @@ import {
TokenFieldValue,
UserMessage
} from '@react-spectrum/ai';
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';
import {Image} from '@react-spectrum/s2/Image';
import {MenuItem} from '@react-spectrum/s2/Menu';
import type {Meta} from '@storybook/react';
import {ProgressCircle} from '@react-spectrum/s2/ProgressCircle';
import {prose} from '../src/style/prose' with {type: 'macro'};
Expand Down Expand Up @@ -215,7 +215,7 @@ export function VirtualizedStreamingChat() {
let nextId = useRef(initialResponses.length);
let [isGenerating, setGenerating] = useState(false);
let timeouts = useRef<NodeJS.Timeout[]>([]);
let [promptValue, setPromptValue] = useState<TokenFieldValue>(new AutoLinkingTokenFieldValue([]));
let [promptValue, setPromptValue] = useState<TokenFieldValue>(new PromptFieldValue([]));
let followUpMessage = useRef<TokenFieldValue | null>(null);

function handleSend(prompt: TokenFieldValue) {
Expand Down Expand Up @@ -561,7 +561,7 @@ export function VirtualizedStreamingChat() {
value={promptValue}
onChange={setPromptValue}
onSubmit={prompt => {
setPromptValue(new AutoLinkingTokenFieldValue([]));
setPromptValue(new PromptFieldValue([]));
handleSend(prompt);
}}
isGenerating={isGenerating}
Expand All @@ -584,14 +584,14 @@ export function VirtualizedStreamingChat() {
e.preventDefault();
if (promptValue.segments.length > 0) {
action('onSteer')(promptValue.toString());
setPromptValue(new AutoLinkingTokenFieldValue([]));
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 AutoLinkingTokenFieldValue([]));
setPromptValue(new PromptFieldValue([]));
}
} else if (e.key === 'Escape') {
e.preventDefault();
Expand Down
Loading
Loading