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
10 changes: 1 addition & 9 deletions apps/web/src/components/chat/AssistantCitationCommentEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,7 @@ export function AssistantCitationCommentEditor({
</p>
) : null}
<div className="mt-2 flex flex-wrap items-center justify-end gap-2">
<div
className={
speech.state.phase === "idle"
? "contents"
: "flex basis-full flex-wrap gap-2 *:min-w-0 *:grow **:data-[slot=voice-input-pill]:w-full"
}
>
<TextFieldSpeechControls speech={speech} />
</div>
<TextFieldSpeechControls speech={speech} />
<Button
variant="outline"
size="xs"
Expand Down
28 changes: 17 additions & 11 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
null,
);
const [isDragOverComposer, setIsDragOverComposer] = useState(false);
const [isComposerFooterCompact, setIsComposerFooterCompact] = useState(false);
const [isComposerPrimaryActionsCompact, setIsComposerPrimaryActionsCompact] = useState(false);
const [isComposerModelPickerOpen, setIsComposerModelPickerOpen] = useState(false);
const isMobileViewport = useMediaQuery("max-sm");
Expand Down Expand Up @@ -1484,19 +1485,18 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
useLayoutEffect(() => {
const composerForm = composerFormRef.current;
if (!composerForm) return;
const measurePrimaryActionsCompact = () => {
const measureCompactLayouts = () => {
const composerFormWidth = composerForm.clientWidth;
const options = { hasWideActions: composerFooterHasWideActions };
return (
setIsComposerFooterCompact(shouldUseCompactComposerFooter(composerFormWidth));
setIsComposerPrimaryActionsCompact(
shouldUseCompactComposerFooter(composerFormWidth, options) &&
shouldUseCompactComposerPrimaryActions(composerFormWidth, options)
shouldUseCompactComposerPrimaryActions(composerFormWidth, options),
);
};

setIsComposerPrimaryActionsCompact(measurePrimaryActionsCompact());
return observeResize(composerForm, () => {
setIsComposerPrimaryActionsCompact(measurePrimaryActionsCompact());
});
measureCompactLayouts();
return observeResize(composerForm, measureCompactLayouts);
}, [composerFooterHasWideActions]);

// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- reads draft images as data URLs and persists them to the draft store, dropping stale results on cleanup
Expand Down Expand Up @@ -1825,6 +1825,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
},
});
const { blocksSubmission: voiceInputActive, submitAfterDictation } = speechInput;
// A compact footer gives the dictation capsule the whole row beside the send button.
const dictationTakesFooter = isComposerFooterCompact && speechInput.state.phase !== "idle";
const submissionTarget = activePendingProgress ? "pending-user-input" : "provider-turn";
const submissionBlocked = isComposerSubmissionBlocked({
submissionTarget,
Expand Down Expand Up @@ -3580,7 +3582,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
"hidden sm:flex",
)}
>
{composerAttachButton ? <div className="me-2">{composerAttachButton}</div> : null}
{composerAttachButton && !dictationTakesFooter ? (
<div className="me-2">{composerAttachButton}</div>
) : null}
<div
data-chat-composer-actions="right"
data-chat-composer-transition-actions="true"
Expand All @@ -3593,7 +3597,10 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
ref={attachExpandedComposerControls}
data-chat-composer-controls="footer"
data-chat-composer-footer-controls="true"
className="relative flex min-w-0 flex-1 items-center justify-end gap-0.5"
className={cn(
"relative flex min-w-0 flex-1 items-center justify-end gap-0.5",
dictationTakesFooter && "hidden",
)}
>
{composerControls}
</div>
Expand Down Expand Up @@ -3637,6 +3644,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
onPreviousPendingQuestion={onPreviousActivePendingUserInputQuestion}
onInterrupt={handleInterruptPrimaryActionStable}
compactBeforeSendTokens={props.resumeCompactionTokens}
hideCompactToggle={dictationTakesFooter}
keepFullHistory={props.keepFullHistory}
onToggleKeepFullHistory={props.onToggleKeepFullHistory}
>
Expand All @@ -3656,11 +3664,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
/>
<ComposerSpeechButton
state={speechInput.state}
progress={null}
shortcutLabel={dictationShortcutLabel}
disabled={dictationDisabled}
onStart={() => void speechInput.start()}
onCancel={() => speechInput.cancel()}
/>
</>
) : null}
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/components/chat/ComposerFooterControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ export const ComposerFooterPrimaryActions = memo(function ComposerFooterPrimaryA
onPreviousPendingQuestion: () => void;
onInterrupt: () => void;
compactBeforeSendTokens: number | null;
hideCompactToggle: boolean;
keepFullHistory: boolean;
onToggleKeepFullHistory: () => void;
}) {
Expand Down Expand Up @@ -245,6 +246,7 @@ export const ComposerFooterPrimaryActions = memo(function ComposerFooterPrimaryA
onPreviousPendingQuestion={props.onPreviousPendingQuestion}
onInterrupt={props.onInterrupt}
compactBeforeSendTokens={props.compactBeforeSendTokens}
hideCompactToggle={props.hideCompactToggle}
keepFullHistory={props.keepFullHistory}
onToggleKeepFullHistory={props.onToggleKeepFullHistory}
>
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/components/chat/ComposerPrimaryActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ interface ComposerPrimaryActionsProps {
onPreviousPendingQuestion: () => void;
onInterrupt: () => void;
compactBeforeSendTokens?: number | null;
hideCompactToggle?: boolean;
keepFullHistory?: boolean;
onToggleKeepFullHistory?: () => void;
}
Expand Down Expand Up @@ -118,6 +119,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({
onPreviousPendingQuestion,
onInterrupt,
compactBeforeSendTokens = null,
hideCompactToggle = false,
keepFullHistory = false,
onToggleKeepFullHistory,
}: ComposerPrimaryActionsProps) {
Expand Down Expand Up @@ -356,7 +358,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({
<TooltipPopup>{submitTooltip}</TooltipPopup>
</Tooltip>
);
if (compactTokens === null) {
if (compactTokens === null || hideCompactToggle) {
return withStopButton(
<>
{children}
Expand Down
Loading
Loading