mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-20 15:11:09 +00:00
fix(ui): avoid follow-up suggestion overlap (#1777)
* fix(ui): avoid follow-up suggestion overlap * fix(ui): address followup review feedback --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
@@ -109,6 +109,7 @@ export function InputBox({
|
||||
threadId,
|
||||
initialValue,
|
||||
onContextChange,
|
||||
onFollowupsVisibilityChange,
|
||||
onSubmit,
|
||||
onStop,
|
||||
...props
|
||||
@@ -136,6 +137,7 @@ export function InputBox({
|
||||
reasoning_effort?: "minimal" | "low" | "medium" | "high";
|
||||
},
|
||||
) => void;
|
||||
onFollowupsVisibilityChange?: (visible: boolean) => void;
|
||||
onSubmit?: (message: PromptInputMessage) => void;
|
||||
onStop?: () => void;
|
||||
}) {
|
||||
@@ -186,6 +188,8 @@ export function InputBox({
|
||||
return models.find((m) => m.name === context.model_name) ?? models[0];
|
||||
}, [context.model_name, models]);
|
||||
|
||||
const resolvedModelName = selectedModel?.name;
|
||||
|
||||
const supportThinking = useMemo(
|
||||
() => selectedModel?.supports_thinking ?? false,
|
||||
[selectedModel],
|
||||
@@ -253,9 +257,33 @@ export function InputBox({
|
||||
setFollowups([]);
|
||||
setFollowupsHidden(false);
|
||||
setFollowupsLoading(false);
|
||||
|
||||
// Guard against submitting before the initial model auto-selection
|
||||
// effect has flushed thread settings to storage/state.
|
||||
if (resolvedModelName && context.model_name !== resolvedModelName) {
|
||||
onContextChange?.({
|
||||
...context,
|
||||
model_name: resolvedModelName,
|
||||
mode: getResolvedMode(
|
||||
context.mode,
|
||||
selectedModel?.supports_thinking ?? false,
|
||||
),
|
||||
});
|
||||
setTimeout(() => onSubmit?.(message), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
onSubmit?.(message);
|
||||
},
|
||||
[onSubmit, onStop, status],
|
||||
[
|
||||
context,
|
||||
onContextChange,
|
||||
onSubmit,
|
||||
onStop,
|
||||
resolvedModelName,
|
||||
selectedModel?.supports_thinking,
|
||||
status,
|
||||
],
|
||||
);
|
||||
|
||||
const requestFormSubmit = useCallback(() => {
|
||||
@@ -309,6 +337,26 @@ export function InputBox({
|
||||
setTimeout(() => requestFormSubmit(), 0);
|
||||
}, [pendingSuggestion, requestFormSubmit, textInput]);
|
||||
|
||||
const showFollowups =
|
||||
!disabled &&
|
||||
!isNewThread &&
|
||||
!followupsHidden &&
|
||||
(followupsLoading || followups.length > 0);
|
||||
|
||||
const followupsVisibilityChangeRef = useRef(onFollowupsVisibilityChange);
|
||||
|
||||
useEffect(() => {
|
||||
followupsVisibilityChangeRef.current = onFollowupsVisibilityChange;
|
||||
}, [onFollowupsVisibilityChange]);
|
||||
|
||||
useEffect(() => {
|
||||
followupsVisibilityChangeRef.current?.(showFollowups);
|
||||
}, [showFollowups]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => followupsVisibilityChangeRef.current?.(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const streaming = status === "streaming";
|
||||
const wasStreaming = wasStreamingRef.current;
|
||||
@@ -769,40 +817,37 @@ export function InputBox({
|
||||
)}
|
||||
</PromptInput>
|
||||
|
||||
{!disabled &&
|
||||
!isNewThread &&
|
||||
!followupsHidden &&
|
||||
(followupsLoading || followups.length > 0) && (
|
||||
<div className="absolute -top-20 right-0 left-0 z-20 flex items-center justify-center">
|
||||
<div className="flex items-center gap-2">
|
||||
{followupsLoading ? (
|
||||
<div className="text-muted-foreground bg-background/80 rounded-full border px-4 py-2 text-xs backdrop-blur-sm">
|
||||
{t.inputBox.followupLoading}
|
||||
</div>
|
||||
) : (
|
||||
<Suggestions className="min-h-16 w-fit items-start">
|
||||
{followups.map((s) => (
|
||||
<Suggestion
|
||||
key={s}
|
||||
suggestion={s}
|
||||
onClick={() => handleFollowupClick(s)}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
aria-label={t.common.close}
|
||||
className="text-muted-foreground cursor-pointer rounded-full px-3 text-xs font-normal"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={() => setFollowupsHidden(true)}
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
</Button>
|
||||
</Suggestions>
|
||||
)}
|
||||
</div>
|
||||
{showFollowups && (
|
||||
<div className="absolute -top-20 right-0 left-0 z-20 flex items-center justify-center">
|
||||
<div className="flex items-center gap-2">
|
||||
{followupsLoading ? (
|
||||
<div className="text-muted-foreground bg-background/80 rounded-full border px-4 py-2 text-xs backdrop-blur-sm">
|
||||
{t.inputBox.followupLoading}
|
||||
</div>
|
||||
) : (
|
||||
<Suggestions className="min-h-16 w-fit items-start">
|
||||
{followups.map((s) => (
|
||||
<Suggestion
|
||||
key={s}
|
||||
suggestion={s}
|
||||
onClick={() => handleFollowupClick(s)}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
aria-label={t.common.close}
|
||||
className="text-muted-foreground cursor-pointer rounded-full px-3 text-xs font-normal"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={() => setFollowupsHidden(true)}
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
</Button>
|
||||
</Suggestions>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
|
||||
<DialogContent>
|
||||
|
||||
@@ -29,11 +29,14 @@ import { MessageListItem } from "./message-list-item";
|
||||
import { MessageListSkeleton } from "./skeleton";
|
||||
import { SubtaskCard } from "./subtask-card";
|
||||
|
||||
export const MESSAGE_LIST_DEFAULT_PADDING_BOTTOM = 160;
|
||||
export const MESSAGE_LIST_FOLLOWUPS_EXTRA_PADDING_BOTTOM = 80;
|
||||
|
||||
export function MessageList({
|
||||
className,
|
||||
threadId,
|
||||
thread,
|
||||
paddingBottom = 160,
|
||||
paddingBottom = MESSAGE_LIST_DEFAULT_PADDING_BOTTOM,
|
||||
}: {
|
||||
className?: string;
|
||||
threadId: string;
|
||||
|
||||
Reference in New Issue
Block a user