| author | |
| committer | |
| log | 77e35c561259733d880ab62a43aad0894d301d9b |
| tree | 6b4092239d75930a54de5cc65bbac3d5701c4301 |
| parent | fff33f9e851912363c5b1f3ac65598be35eb5f0d |
| signature | Signed by PGP key B5690EEEBB952194 |
5 files changed, 49 insertions(+), 4 deletions(-)
apps/web/src/components/ComposerCitationNode.tsx+7-1| ... | ... | @@ -47,7 +47,8 @@ export type ComposerCitationCommentTarget = { |
| 47 | 47 | export const ComposerCitationCommentContext = createContext<{ |
| 48 | 48 | openComment: ComposerCitationCommentTarget | null; |
| 49 | 49 | onOpenChange: (nodeKey: NodeKey, open: boolean) => void; |
| 50 | }>({ openComment: null, onOpenChange: () => {} }); | |
| 50 | onSubmitAndSend: () => void; | |
| 51 | }>({ openComment: null, onOpenChange: () => {}, onSubmitAndSend: () => {} }); | |
| 51 | 52 | |
| 52 | 53 | /** Consume a cite action once its controlled prompt has been committed to the editor. */ |
| 53 | 54 | export function $consumeComposerCitationCommentRequest(requestRef: { |
| ... | ... | @@ -127,6 +128,11 @@ function ComposerCitationDecorator(props: { citation: AssistantCitation; nodeKey |
| 127 | 128 | commentContext.onOpenChange(props.nodeKey, open); |
| 128 | 129 | }, |
| 129 | 130 | onSave: onSaveComment, |
| 131 | onSaveAndSend: (comment) => { | |
| 132 | if (!onSaveComment(comment)) return false; | |
| 133 | commentContext.onSubmitAndSend(); | |
| 134 | return true; | |
| 135 | }, | |
| 130 | 136 | }} |
| 131 | 137 | onRemove={onRemove} |
| 132 | 138 | /> |
apps/web/src/components/ComposerPromptEditor.tsx+6-1| ... | ... | @@ -924,6 +924,7 @@ interface ComposerPromptEditorProps { |
| 924 | 924 | onPageScrollKeyDown?: (key: "PageUp" | "PageDown") => void; |
| 925 | 925 | onPageScrollKeyUp?: (key: string) => void; |
| 926 | 926 | onPageScrollRelease?: () => void; |
| 927 | onCitationSubmitAndSend?: () => void; | |
| 927 | 928 | onPaste: React.ClipboardEventHandler<HTMLElement>; |
| 928 | 929 | editorRef: React.RefObject<ComposerPromptEditorHandle | null>; |
| 929 | 930 | } |
| ... | ... | @@ -1571,6 +1572,7 @@ function ComposerPromptEditorInner({ |
| 1571 | 1572 | onPageScrollKeyDown, |
| 1572 | 1573 | onPageScrollKeyUp, |
| 1573 | 1574 | onPageScrollRelease, |
| 1575 | onCitationSubmitAndSend, | |
| 1574 | 1576 | onPaste, |
| 1575 | 1577 | editorRef, |
| 1576 | 1578 | }: ComposerPromptEditorProps) { |
| ... | ... | @@ -1603,8 +1605,9 @@ function ComposerPromptEditorInner({ |
| 1603 | 1605 | open ? { nodeKey } : current?.nodeKey === nodeKey ? null : current, |
| 1604 | 1606 | ); |
| 1605 | 1607 | }, |
| 1608 | onSubmitAndSend: onCitationSubmitAndSend ?? (() => {}), | |
| 1606 | 1609 | }), |
| 1607 | [openCitationComment], | |
| 1610 | [onCitationSubmitAndSend, openCitationComment], | |
| 1608 | 1611 | ); |
| 1609 | 1612 | const terminalContextActions = useMemo( |
| 1610 | 1613 | () => ({ onRemoveTerminalContext }), |
| ... | ... | @@ -1969,6 +1972,7 @@ export function ComposerPromptEditor({ |
| 1969 | 1972 | onPageScrollKeyDown, |
| 1970 | 1973 | onPageScrollKeyUp, |
| 1971 | 1974 | onPageScrollRelease, |
| 1975 | onCitationSubmitAndSend, | |
| 1972 | 1976 | onPaste, |
| 1973 | 1977 | editorRef, |
| 1974 | 1978 | }: ComposerPromptEditorProps) { |
| ... | ... | @@ -2013,6 +2017,7 @@ export function ComposerPromptEditor({ |
| 2013 | 2017 | onChange={onChange} |
| 2014 | 2018 | {...(onVisibleSelectionChange ? { onVisibleSelectionChange } : {})} |
| 2015 | 2019 | onPaste={onPaste} |
| 2020 | {...(onCitationSubmitAndSend ? { onCitationSubmitAndSend } : {})} | |
| 2016 | 2021 | editorRef={editorRef} |
| 2017 | 2022 | {...(onCommandKeyDown ? { onCommandKeyDown } : {})} |
| 2018 | 2023 | {...(onPageScrollKeyDown ? { onPageScrollKeyDown } : {})} |
apps/web/src/components/chat/AssistantCitationChip.tsx+10| ... | ... | @@ -42,6 +42,7 @@ export function AssistantCitationChip({ |
| 42 | 42 | sourceAnchor?: AssistantCitationSourceAnchor | undefined; |
| 43 | 43 | onOpenChange: (open: boolean) => void; |
| 44 | 44 | onSave: (comment: string) => boolean; |
| 45 | onSaveAndSend?: (comment: string) => boolean; | |
| 45 | 46 | }; |
| 46 | 47 | }) { |
| 47 | 48 | const navigate = useNavigate(); |
| ... | ... | @@ -158,6 +159,15 @@ export function AssistantCitationChip({ |
| 158 | 159 | commentEditor.onOpenChange(false); |
| 159 | 160 | return true; |
| 160 | 161 | }} |
| 162 | {...(commentEditor.onSaveAndSend | |
| 163 | ? { | |
| 164 | onSubmitAndSend: (comment: string) => { | |
| 165 | if (!commentEditor.onSaveAndSend?.(comment)) return false; | |
| 166 | commentEditor.onOpenChange(false); | |
| 167 | return true; | |
| 168 | }, | |
| 169 | } | |
| 170 | : {})} | |
| 161 | 171 | onCancel={() => commentEditor.onOpenChange(false)} |
| 162 | 172 | /> |
| 163 | 173 | </PopoverPopup> |
apps/web/src/components/chat/AssistantCitationCommentEditor.tsx+16-2| ... | ... | @@ -7,11 +7,13 @@ export function AssistantCitationCommentEditor({ |
| 7 | 7 | citation, |
| 8 | 8 | inputRef, |
| 9 | 9 | onSubmit, |
| 10 | onSubmitAndSend, | |
| 10 | 11 | onCancel, |
| 11 | 12 | }: { |
| 12 | 13 | citation: AssistantCitation; |
| 13 | 14 | inputRef?: Ref<HTMLTextAreaElement>; |
| 14 | 15 | onSubmit: (comment: string) => boolean; |
| 16 | onSubmitAndSend?: (comment: string) => boolean; | |
| 15 | 17 | onCancel: () => void; |
| 16 | 18 | }) { |
| 17 | 19 | const [comment, setComment] = useState(citation.comment ?? ""); |
| ... | ... | @@ -19,6 +21,14 @@ export function AssistantCitationCommentEditor({ |
| 19 | 21 | const submit = () => { |
| 20 | 22 | if (!commentTooLong) onSubmit(comment); |
| 21 | 23 | }; |
| 24 | const submitAndSend = () => { | |
| 25 | if (commentTooLong) return; | |
| 26 | if (onSubmitAndSend) { | |
| 27 | onSubmitAndSend(comment); | |
| 28 | } else { | |
| 29 | onSubmit(comment); | |
| 30 | } | |
| 31 | }; | |
| 22 | 32 | |
| 23 | 33 | return ( |
| 24 | 34 | <div |
| ... | ... | @@ -35,7 +45,7 @@ export function AssistantCitationCommentEditor({ |
| 35 | 45 | <textarea |
| 36 | 46 | ref={inputRef} |
| 37 | 47 | aria-label="Comment on selected text" |
| 38 | aria-description="Enter to save the citation comment; Shift+Enter for a new line." | |
| 48 | aria-description="Enter to save the citation comment; Command/Ctrl+Enter to save and send; Shift+Enter for a new line." | |
| 39 | 49 | aria-invalid={commentTooLong || undefined} |
| 40 | 50 | placeholder="Add an optional comment..." |
| 41 | 51 | rows={2} |
| ... | ... | @@ -50,7 +60,11 @@ export function AssistantCitationCommentEditor({ |
| 50 | 60 | event.keyCode !== 229 |
| 51 | 61 | ) { |
| 52 | 62 | event.preventDefault(); |
| 53 | submit(); | |
| 63 | if (event.metaKey || event.ctrlKey) { | |
| 64 | submitAndSend(); | |
| 65 | } else { | |
| 66 | submit(); | |
| 67 | } | |
| 54 | 68 | } |
| 55 | 69 | }} |
| 56 | 70 | /> |
apps/web/src/components/chat/ChatComposer.tsx+10| ... | ... | @@ -2824,6 +2824,15 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 2824 | 2824 | shouldBlurMobileComposerOnSubmit, |
| 2825 | 2825 | ], |
| 2826 | 2826 | ); |
| 2827 | const submitCitationAndSend = useCallback(() => { | |
| 2828 | const intent = composerSubmissionIntentForEnter({ | |
| 2829 | isMobileViewport, | |
| 2830 | shiftKey: false, | |
| 2831 | modifierKey: true, | |
| 2832 | isDraftThread: routeKind === "draft", | |
| 2833 | }); | |
| 2834 | submitComposer(undefined, intent ?? "foreground"); | |
| 2835 | }, [isMobileViewport, routeKind, submitComposer]); | |
| 2827 | 2836 | const compactThreadContext = useCallback(() => { |
| 2828 | 2837 | if ( |
| 2829 | 2838 | compactDisabled || |
| ... | ... | @@ -5246,6 +5255,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5246 | 5255 | onPageScrollKeyDown={onPageScrollKeyDown} |
| 5247 | 5256 | onPageScrollKeyUp={onPageScrollKeyUp} |
| 5248 | 5257 | onPageScrollRelease={onPageScrollRelease} |
| 5258 | onCitationSubmitAndSend={submitCitationAndSend} | |
| 5249 | 5259 | onPaste={onComposerPaste} |
| 5250 | 5260 | placeholder={ |
| 5251 | 5261 | isComposerApprovalState |