| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | import { formatProviderSkillDisplayName } from "@t3tools/shared/inlineSkills"; | 1 | import { formatProviderSkillDisplayName } from "@t3tools/shared/inlineSkills"; |
| | 2 | import { useComposerTypingGuard } from "./useComposerTypingGuard"; |
| 2 | import { DESKTOP_PASTE_AS_TEXT_EVENT } from "../../lib/desktopPasteAsText"; | 3 | import { DESKTOP_PASTE_AS_TEXT_EVENT } from "../../lib/desktopPasteAsText"; |
| 3 | import { runtimeModeConfig, runtimeModeOptions as runtimeModes } from "./runtimeModeConfig"; | 4 | import { runtimeModeConfig, runtimeModeOptions as runtimeModes } from "./runtimeModeConfig"; |
| 4 | import { isLocalEnvironmentDisabled } from "../../localEnvironment"; | 5 | import { isLocalEnvironmentDisabled } from "../../localEnvironment"; |
| ... | @@ -1485,6 +1486,7 @@ export interface ChatComposerHandle { | ... | @@ -1485,6 +1486,7 @@ export interface ChatComposerHandle { |
| 1485 | addTerminalContext: (selection: TerminalContextSelection) => void; | 1486 | addTerminalContext: (selection: TerminalContextSelection) => void; |
| 1486 | /** Get the current prompt/effort/model state for use in send. */ | 1487 | /** Get the current prompt/effort/model state for use in send. */ |
| 1487 | getSendContext: () => { | 1488 | getSendContext: () => { |
| | 1489 | answeringPendingUserInput: boolean; |
| 1488 | prompt: string; | 1490 | prompt: string; |
| 1489 | images: ComposerImageAttachment[]; | 1491 | images: ComposerImageAttachment[]; |
| 1490 | files: ComposerFileAttachment[]; | 1492 | files: ComposerFileAttachment[]; |
| ... | @@ -1737,12 +1739,12 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -1737,12 +1739,12 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 1737 | sendDisabledReason: externalSendDisabledReason, | 1739 | sendDisabledReason: externalSendDisabledReason, |
| 1738 | isPreparingWorktree, | 1740 | isPreparingWorktree, |
| 1739 | environmentUnavailable, | 1741 | environmentUnavailable, |
| 1740 | activePendingApproval, | 1742 | activePendingApproval: incomingPendingApproval, |
| 1741 | pendingApprovals, | 1743 | pendingApprovals, |
| 1742 | pendingUserInputs, | 1744 | pendingUserInputs: incomingPendingUserInputs, |
| 1743 | activePendingProgress, | 1745 | activePendingProgress: incomingPendingProgress, |
| 1744 | activePendingResolvedAnswers, | 1746 | activePendingResolvedAnswers, |
| 1745 | activePendingIsResponding, | 1747 | activePendingIsResponding: incomingPendingIsResponding, |
| 1746 | activePendingDraftAnswers, | 1748 | activePendingDraftAnswers, |
| 1747 | activePendingQuestionIndex, | 1749 | activePendingQuestionIndex, |
| 1748 | respondingRequestIds, | 1750 | respondingRequestIds, |
| ... | @@ -1808,9 +1810,30 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -1808,9 +1810,30 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 1808 | editingQueuedAttachments, | 1810 | editingQueuedAttachments, |
| 1809 | onRemoveEditingQueuedAttachment, | 1811 | onRemoveEditingQueuedAttachment, |
| 1810 | } = props; | 1812 | } = props; |
| 1811 | const isLiteralPendingAnswer = activePendingProgress?.activeQuestion?.initialAnswer !== undefined; | | |
| 1812 | const primaryEnvironmentId = usePrimaryEnvironmentId(); | 1813 | const primaryEnvironmentId = usePrimaryEnvironmentId(); |
| 1813 | const composerDraftTargetKey = composerTargetKey(composerDraftTarget); | 1814 | const composerDraftTargetKey = composerTargetKey(composerDraftTarget); |
| | 1815 | const { |
| | 1816 | heldRequestIds, |
| | 1817 | onDraftChange: onTypingGuardDraftChange, |
| | 1818 | onFocus: onTypingGuardFocus, |
| | 1819 | onBlur: onTypingGuardBlur, |
| | 1820 | onSend: onTypingGuardSend, |
| | 1821 | } = useComposerTypingGuard(composerDraftTargetKey, [ |
| | 1822 | ...pendingApprovals.map((request) => request.requestId), |
| | 1823 | ...incomingPendingUserInputs.map((request) => request.requestId), |
| | 1824 | ]); |
| | 1825 | // Hold composer takeover while the user finishes their thread draft. |
| | 1826 | const activePendingApproval = |
| | 1827 | incomingPendingApproval && !heldRequestIds.has(incomingPendingApproval.requestId) |
| | 1828 | ? incomingPendingApproval |
| | 1829 | : null; |
| | 1830 | const holdingUserInput = incomingPendingUserInputs[0] |
| | 1831 | ? heldRequestIds.has(incomingPendingUserInputs[0].requestId) |
| | 1832 | : false; |
| | 1833 | const pendingUserInputs = holdingUserInput ? [] : incomingPendingUserInputs; |
| | 1834 | const activePendingProgress = holdingUserInput ? null : incomingPendingProgress; |
| | 1835 | const activePendingIsResponding = !holdingUserInput && incomingPendingIsResponding; |
| | 1836 | const isLiteralPendingAnswer = activePendingProgress?.activeQuestion?.initialAnswer !== undefined; |
| 1814 | // Opening a running thread resyncs for a few frames. Show the sync row, and | 1837 | // Opening a running thread resyncs for a few frames. Show the sync row, and |
| 1815 | // hide the tasks row for it, only when the sync lasts. Logic that depends on | 1838 | // hide the tasks row for it, only when the sync lasts. Logic that depends on |
| 1816 | // the real phase keeps reading `props.threadSyncPhase`. | 1839 | // the real phase keeps reading `props.threadSyncPhase`. |
| ... | @@ -3712,6 +3735,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -3712,6 +3735,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 3712 | ); | 3735 | ); |
| 3713 | return; | 3736 | return; |
| 3714 | } | 3737 | } |
| | 3738 | onTypingGuardDraftChange(); |
| 3715 | promptRef.current = nextPrompt; | 3739 | promptRef.current = nextPrompt; |
| 3716 | setPrompt(nextPrompt); | 3740 | setPrompt(nextPrompt); |
| 3717 | // Any edit ends browsing, even one later undone by hand: typing a | 3741 | // Any edit ends browsing, even one later undone by hand: typing a |
| ... | @@ -3818,6 +3842,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -3818,6 +3842,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 3818 | [ | 3842 | [ |
| 3819 | activePendingProgress?.activeQuestion, | 3843 | activePendingProgress?.activeQuestion, |
| 3820 | expandComposerForEditorChange, | 3844 | expandComposerForEditorChange, |
| | 3845 | onTypingGuardDraftChange, |
| 3821 | pendingUserInputs.length, | 3846 | pendingUserInputs.length, |
| 3822 | onChangeActivePendingUserInputCustomAnswer, | 3847 | onChangeActivePendingUserInputCustomAnswer, |
| 3823 | promptRef, | 3848 | promptRef, |
| ... | @@ -3909,6 +3934,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -3909,6 +3934,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 3909 | ); | 3934 | ); |
| 3910 | } else { | 3935 | } else { |
| 3911 | setPrompt(next.text); | 3936 | setPrompt(next.text); |
| | 3937 | onTypingGuardDraftChange(); |
| 3912 | } | 3938 | } |
| 3913 | setComposerCursor(nextCursor); | 3939 | setComposerCursor(nextCursor); |
| 3914 | setComposerTrigger( | 3940 | setComposerTrigger( |
| ... | @@ -3931,6 +3957,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -3931,6 +3957,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 3931 | activePendingUserInput, | 3957 | activePendingUserInput, |
| 3932 | isLiteralPendingAnswer, | 3958 | isLiteralPendingAnswer, |
| 3933 | onChangeActivePendingUserInputCustomAnswer, | 3959 | onChangeActivePendingUserInputCustomAnswer, |
| | 3960 | onTypingGuardDraftChange, |
| 3934 | promptRef, | 3961 | promptRef, |
| 3935 | setPrompt, | 3962 | setPrompt, |
| 3936 | setComposerTrigger, | 3963 | setComposerTrigger, |
| ... | @@ -4269,6 +4296,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -4269,6 +4296,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 4269 | }); | 4296 | }); |
| 4270 | setComposerSubmissionError(submission.validationMessage); | 4297 | setComposerSubmissionError(submission.validationMessage); |
| 4271 | if (!submission.didDispatch) return; | 4298 | if (!submission.didDispatch) return; |
| | 4299 | onTypingGuardSend(); |
| 4272 | if (shouldBlurMobileComposerOnSubmit()) { | 4300 | if (shouldBlurMobileComposerOnSubmit()) { |
| 4273 | blurMobileComposerAfterSend(); | 4301 | blurMobileComposerAfterSend(); |
| 4274 | } | 4302 | } |
| ... | @@ -4277,6 +4305,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -4277,6 +4305,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 4277 | activeThreadId, | 4305 | activeThreadId, |
| 4278 | activePendingProgress, | 4306 | activePendingProgress, |
| 4279 | attachmentTargetKey, | 4307 | attachmentTargetKey, |
| | 4308 | onTypingGuardSend, |
| 4280 | blurMobileComposerAfterSend, | 4309 | blurMobileComposerAfterSend, |
| 4281 | environmentId, | 4310 | environmentId, |
| 4282 | isSendDisabled, | 4311 | isSendDisabled, |
| ... | @@ -6532,6 +6561,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -6532,6 +6561,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 6532 | }); | 6561 | }); |
| 6533 | }, | 6562 | }, |
| 6534 | getSendContext: () => ({ | 6563 | getSendContext: () => ({ |
| | 6564 | answeringPendingUserInput: activePendingProgress !== null, |
| 6535 | prompt: promptRef.current, | 6565 | prompt: promptRef.current, |
| 6536 | images: composerImagesRef.current, | 6566 | images: composerImagesRef.current, |
| 6537 | files: composerFilesRef.current, | 6567 | files: composerFilesRef.current, |
| ... | @@ -6578,6 +6608,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -6578,6 +6608,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 6578 | foldPastedText, | 6608 | foldPastedText, |
| 6579 | composerDraftTarget, | 6609 | composerDraftTarget, |
| 6580 | composerCursor, | 6610 | composerCursor, |
| | 6611 | activePendingProgress, |
| 6581 | composerTerminalContexts, | 6612 | composerTerminalContexts, |
| 6582 | insertComposerDraftTerminalContext, | 6613 | insertComposerDraftTerminalContext, |
| 6583 | insertComposerText, | 6614 | insertComposerText, |
| ... | @@ -6652,6 +6683,12 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -6652,6 +6683,12 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 6652 | }} | 6683 | }} |
| 6653 | onFocusCapture={(event) => { | 6684 | onFocusCapture={(event) => { |
| 6654 | const activeElement = event.target; | 6685 | const activeElement = event.target; |
| | 6686 | if ( |
| | 6687 | activeElement instanceof Element && |
| | 6688 | activeElement.closest('[data-testid="composer-editor"]') |
| | 6689 | ) { |
| | 6690 | onTypingGuardFocus(); |
| | 6691 | } |
| 6655 | if (composerControlsCollapsed && isInsideRestingComposerControlScope(activeElement)) { | 6692 | if (composerControlsCollapsed && isInsideRestingComposerControlScope(activeElement)) { |
| 6656 | return; | 6693 | return; |
| 6657 | } | 6694 | } |
| ... | @@ -6670,7 +6707,13 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -6670,7 +6707,13 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 6670 | } | 6707 | } |
| 6671 | setIsComposerFocused(true); | 6708 | setIsComposerFocused(true); |
| 6672 | }} | 6709 | }} |
| 6673 | onBlurCapture={() => { | 6710 | onBlurCapture={(event) => { |
| | 6711 | if ( |
| | 6712 | event.target instanceof Element && |
| | 6713 | event.target.closest('[data-testid="composer-editor"]') |
| | 6714 | ) { |
| | 6715 | onTypingGuardBlur(); |
| | 6716 | } |
| 6674 | scheduleComposerCollapseCheck(); | 6717 | scheduleComposerCollapseCheck(); |
| 6675 | }} | 6718 | }} |
| 6676 | onDragEnterCapture={(event) => { | 6719 | onDragEnterCapture={(event) => { |