mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-11 19:23:41 +00:00
[codex] fix follow-up suggestions layout (#2836)
* fix follow-up suggestions layout * fix agent chat welcome layout transition --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
08ee7adeba
commit
dfa4eb0c1a
@ -2,7 +2,7 @@
|
||||
|
||||
import { BotIcon, PlusSquare } from "lucide-react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import type { PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@ -14,7 +14,6 @@ import { InputBox } from "@/components/workspace/input-box";
|
||||
import {
|
||||
MessageList,
|
||||
MESSAGE_LIST_DEFAULT_PADDING_BOTTOM,
|
||||
MESSAGE_LIST_FOLLOWUPS_EXTRA_PADDING_BOTTOM,
|
||||
} from "@/components/workspace/messages";
|
||||
import { ThreadContext } from "@/components/workspace/messages/context";
|
||||
import { ThreadTitle } from "@/components/workspace/thread-title";
|
||||
@ -34,7 +33,6 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
export default function AgentChatPage() {
|
||||
const { t } = useI18n();
|
||||
const [showFollowups, setShowFollowups] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const { agent_name } = useParams<{
|
||||
@ -45,6 +43,10 @@ export default function AgentChatPage() {
|
||||
|
||||
const { threadId, setThreadId, isNewThread, setIsNewThread, isMock } =
|
||||
useThreadChat();
|
||||
// `isNewThread` gates history/token-usage fetches until the backend creates
|
||||
// the thread. `isWelcomeMode` controls only the centered welcome layout, so
|
||||
// it can flip immediately on submit without triggering eager history loads.
|
||||
const [isWelcomeMode, setIsWelcomeMode] = useState(isNewThread);
|
||||
const [settings, setSettings] = useThreadSettings(threadId);
|
||||
const [localSettings, setLocalSettings] = useLocalSettings();
|
||||
const { tokenUsageEnabled } = useModels();
|
||||
@ -55,6 +57,11 @@ export default function AgentChatPage() {
|
||||
const backendTokenUsage = threadTokenUsageToTokenUsage(threadTokenUsage.data);
|
||||
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
useEffect(() => {
|
||||
setIsWelcomeMode(isNewThread);
|
||||
}, [isNewThread]);
|
||||
|
||||
const {
|
||||
thread,
|
||||
pendingUsageMessages,
|
||||
@ -66,6 +73,9 @@ export default function AgentChatPage() {
|
||||
threadId: isNewThread ? undefined : threadId,
|
||||
context: { ...settings.context, agent_name: agent_name },
|
||||
isMock,
|
||||
onSend: () => {
|
||||
setIsWelcomeMode(false);
|
||||
},
|
||||
onStart: (createdThreadId) => {
|
||||
setThreadId(createdThreadId);
|
||||
setIsNewThread(false);
|
||||
@ -105,13 +115,10 @@ export default function AgentChatPage() {
|
||||
await thread.stop();
|
||||
}, [thread]);
|
||||
|
||||
const messageListPaddingBottom = showFollowups
|
||||
? MESSAGE_LIST_DEFAULT_PADDING_BOTTOM +
|
||||
MESSAGE_LIST_FOLLOWUPS_EXTRA_PADDING_BOTTOM
|
||||
: undefined;
|
||||
const tokenUsageInlineMode = tokenUsageEnabled
|
||||
? localSettings.tokenUsage.inlineMode
|
||||
: "off";
|
||||
const hasTodos = (thread.values.todos?.length ?? 0) > 0;
|
||||
|
||||
return (
|
||||
<ThreadContext.Provider value={{ thread }}>
|
||||
@ -120,7 +127,7 @@ export default function AgentChatPage() {
|
||||
<header
|
||||
className={cn(
|
||||
"absolute top-0 right-0 left-0 z-30 flex h-12 shrink-0 items-center gap-2 px-4",
|
||||
isNewThread
|
||||
isWelcomeMode
|
||||
? "bg-background/0 backdrop-blur-none"
|
||||
: "bg-background/80 shadow-xs backdrop-blur",
|
||||
)}
|
||||
@ -165,12 +172,12 @@ export default function AgentChatPage() {
|
||||
</header>
|
||||
|
||||
<main className="flex min-h-0 max-w-full grow flex-col">
|
||||
<div className="flex size-full justify-center">
|
||||
<div className="flex min-h-0 flex-1 justify-center">
|
||||
<MessageList
|
||||
className={cn("size-full", !isNewThread && "pt-10")}
|
||||
className={cn("size-full", !isWelcomeMode && "pt-10")}
|
||||
threadId={threadId}
|
||||
thread={thread}
|
||||
paddingBottom={messageListPaddingBottom}
|
||||
paddingBottom={MESSAGE_LIST_DEFAULT_PADDING_BOTTOM}
|
||||
hasMoreHistory={hasMoreHistory}
|
||||
loadMoreHistory={loadMoreHistory}
|
||||
isHistoryLoading={isHistoryLoading}
|
||||
@ -178,33 +185,51 @@ export default function AgentChatPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute right-0 bottom-0 left-0 z-30 flex justify-center px-4">
|
||||
<div
|
||||
className={cn(
|
||||
"right-0 bottom-0 left-0 z-30 flex justify-center px-4",
|
||||
isWelcomeMode ? "absolute" : "relative shrink-0 pb-4",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"relative w-full",
|
||||
isNewThread && "-translate-y-[calc(50vh-96px)]",
|
||||
isNewThread
|
||||
isWelcomeMode && "-translate-y-[calc(50vh-96px)]",
|
||||
isWelcomeMode
|
||||
? "max-w-(--container-width-sm)"
|
||||
: "max-w-(--container-width-md)",
|
||||
)}
|
||||
>
|
||||
<div className="absolute -top-4 right-0 left-0 z-0">
|
||||
<div className="absolute right-0 bottom-0 left-0">
|
||||
<TodoList
|
||||
className="bg-background/5"
|
||||
todos={thread.values.todos ?? []}
|
||||
hidden={
|
||||
!thread.values.todos || thread.values.todos.length === 0
|
||||
}
|
||||
/>
|
||||
{hasTodos && (
|
||||
<div
|
||||
className={cn(
|
||||
"right-0 left-0 z-0",
|
||||
isWelcomeMode ? "absolute -top-4" : "relative",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"right-0 bottom-0 left-0",
|
||||
isWelcomeMode ? "absolute" : "relative",
|
||||
)}
|
||||
>
|
||||
<TodoList
|
||||
className="bg-background/5"
|
||||
todos={thread.values.todos ?? []}
|
||||
hidden={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<InputBox
|
||||
className={cn("bg-background/5 w-full -translate-y-4")}
|
||||
isWelcomeMode={isNewThread}
|
||||
className={cn(
|
||||
"bg-background/5 w-full",
|
||||
isWelcomeMode && "-translate-y-4",
|
||||
)}
|
||||
isWelcomeMode={isWelcomeMode}
|
||||
threadId={threadId}
|
||||
autoFocus={isNewThread}
|
||||
autoFocus={isWelcomeMode}
|
||||
status={
|
||||
thread.error
|
||||
? "error"
|
||||
@ -214,13 +239,12 @@ export default function AgentChatPage() {
|
||||
}
|
||||
context={settings.context}
|
||||
extraHeader={
|
||||
isNewThread && (
|
||||
isWelcomeMode && (
|
||||
<AgentWelcome agent={agent} agentName={agent_name} />
|
||||
)
|
||||
}
|
||||
disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"}
|
||||
onContextChange={(context) => setSettings("context", context)}
|
||||
onFollowupsVisibilityChange={setShowFollowups}
|
||||
onSubmit={handleSubmit}
|
||||
onStop={handleStop}
|
||||
/>
|
||||
|
||||
@ -14,7 +14,6 @@ import { InputBox } from "@/components/workspace/input-box";
|
||||
import {
|
||||
MessageList,
|
||||
MESSAGE_LIST_DEFAULT_PADDING_BOTTOM,
|
||||
MESSAGE_LIST_FOLLOWUPS_EXTRA_PADDING_BOTTOM,
|
||||
} from "@/components/workspace/messages";
|
||||
import { ThreadContext } from "@/components/workspace/messages/context";
|
||||
import { ThreadTitle } from "@/components/workspace/thread-title";
|
||||
@ -33,7 +32,6 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
export default function ChatPage() {
|
||||
const { t } = useI18n();
|
||||
const [showFollowups, setShowFollowups] = useState(false);
|
||||
const { threadId, setThreadId, isNewThread, setIsNewThread, isMock } =
|
||||
useThreadChat();
|
||||
// `isNewThread` tracks whether the backend has the thread yet — gates the
|
||||
@ -119,13 +117,10 @@ export default function ChatPage() {
|
||||
await thread.stop();
|
||||
}, [thread]);
|
||||
|
||||
const messageListPaddingBottom = showFollowups
|
||||
? MESSAGE_LIST_DEFAULT_PADDING_BOTTOM +
|
||||
MESSAGE_LIST_FOLLOWUPS_EXTRA_PADDING_BOTTOM
|
||||
: undefined;
|
||||
const tokenUsageInlineMode = tokenUsageEnabled
|
||||
? localSettings.tokenUsage.inlineMode
|
||||
: "off";
|
||||
const hasTodos = (thread.values.todos?.length ?? 0) > 0;
|
||||
|
||||
return (
|
||||
<ThreadContext.Provider value={{ thread, isMock }}>
|
||||
@ -159,19 +154,24 @@ export default function ChatPage() {
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex min-h-0 max-w-full grow flex-col">
|
||||
<div className="flex size-full justify-center">
|
||||
<div className="flex min-h-0 flex-1 justify-center">
|
||||
<MessageList
|
||||
className={cn("size-full", !isWelcomeMode && "pt-10")}
|
||||
threadId={threadId}
|
||||
thread={thread}
|
||||
paddingBottom={messageListPaddingBottom}
|
||||
paddingBottom={MESSAGE_LIST_DEFAULT_PADDING_BOTTOM}
|
||||
hasMoreHistory={hasMoreHistory}
|
||||
loadMoreHistory={loadMoreHistory}
|
||||
isHistoryLoading={isHistoryLoading}
|
||||
tokenUsageInlineMode={tokenUsageInlineMode}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute right-0 bottom-0 left-0 z-30 flex justify-center px-4">
|
||||
<div
|
||||
className={cn(
|
||||
"right-0 bottom-0 left-0 z-30 flex justify-center px-4",
|
||||
isWelcomeMode ? "absolute" : "relative shrink-0 pb-4",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"relative w-full",
|
||||
@ -181,20 +181,33 @@ export default function ChatPage() {
|
||||
: "max-w-(--container-width-md)",
|
||||
)}
|
||||
>
|
||||
<div className="absolute -top-4 right-0 left-0 z-0">
|
||||
<div className="absolute right-0 bottom-0 left-0">
|
||||
<TodoList
|
||||
className="bg-background/5"
|
||||
todos={thread.values.todos ?? []}
|
||||
hidden={
|
||||
!thread.values.todos || thread.values.todos.length === 0
|
||||
}
|
||||
/>
|
||||
{hasTodos && (
|
||||
<div
|
||||
className={cn(
|
||||
"right-0 left-0 z-0",
|
||||
isWelcomeMode ? "absolute -top-4" : "relative",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"right-0 bottom-0 left-0",
|
||||
isWelcomeMode ? "absolute" : "relative",
|
||||
)}
|
||||
>
|
||||
<TodoList
|
||||
className="bg-background/5"
|
||||
todos={thread.values.todos ?? []}
|
||||
hidden={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{mountedRef.current ? (
|
||||
<InputBox
|
||||
className={cn("bg-background/5 w-full -translate-y-4")}
|
||||
className={cn(
|
||||
"bg-background/5 w-full",
|
||||
isWelcomeMode && "-translate-y-4",
|
||||
)}
|
||||
isWelcomeMode={isWelcomeMode}
|
||||
threadId={threadId}
|
||||
autoFocus={isWelcomeMode}
|
||||
@ -216,7 +229,6 @@ export default function ChatPage() {
|
||||
onContextChange={(context) =>
|
||||
setSettings("context", context)
|
||||
}
|
||||
onFollowupsVisibilityChange={setShowFollowups}
|
||||
onSubmit={handleSubmit}
|
||||
onStop={handleStop}
|
||||
/>
|
||||
@ -224,7 +236,8 @@ export default function ChatPage() {
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"bg-background/5 h-32 w-full -translate-y-4 rounded-2xl",
|
||||
"bg-background/5 h-32 w-full rounded-2xl",
|
||||
isWelcomeMode && "-translate-y-4",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -110,7 +110,6 @@ export function InputBox({
|
||||
threadId,
|
||||
initialValue,
|
||||
onContextChange,
|
||||
onFollowupsVisibilityChange,
|
||||
onSubmit,
|
||||
onStop,
|
||||
...props
|
||||
@ -143,7 +142,6 @@ export function InputBox({
|
||||
reasoning_effort?: "minimal" | "low" | "medium" | "high";
|
||||
},
|
||||
) => void;
|
||||
onFollowupsVisibilityChange?: (visible: boolean) => void;
|
||||
onSubmit?: (message: PromptInputMessage) => void;
|
||||
onStop?: () => void;
|
||||
}) {
|
||||
@ -350,24 +348,10 @@ export function InputBox({
|
||||
!followupsHidden &&
|
||||
(followupsLoading || followups.length > 0);
|
||||
|
||||
const followupsVisibilityChangeRef = useRef(onFollowupsVisibilityChange);
|
||||
|
||||
useEffect(() => {
|
||||
followupsVisibilityChangeRef.current = onFollowupsVisibilityChange;
|
||||
}, [onFollowupsVisibilityChange]);
|
||||
|
||||
useEffect(() => {
|
||||
followupsVisibilityChangeRef.current?.(showFollowups);
|
||||
}, [showFollowups]);
|
||||
|
||||
useEffect(() => {
|
||||
messagesRef.current = thread.messages;
|
||||
}, [thread.messages]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => followupsVisibilityChangeRef.current?.(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const streaming = status === "streaming";
|
||||
const wasStreaming = wasStreamingRef.current;
|
||||
@ -442,26 +426,33 @@ export function InputBox({
|
||||
}, [context.model_name, disabled, isMock, status, threadId]);
|
||||
|
||||
return (
|
||||
<div ref={promptRootRef} className="relative flex flex-col gap-4">
|
||||
<div
|
||||
ref={promptRootRef}
|
||||
className={cn(
|
||||
"relative flex flex-col",
|
||||
isWelcomeMode ? "gap-4" : "gap-2",
|
||||
)}
|
||||
>
|
||||
{showFollowups && (
|
||||
<div className="flex items-center justify-center pb-2">
|
||||
<div className="flex items-center justify-center pb-1">
|
||||
<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">
|
||||
<div className="text-muted-foreground bg-background/80 rounded-full border px-4 py-1.5 text-xs backdrop-blur-sm">
|
||||
{t.inputBox.followupLoading}
|
||||
</div>
|
||||
) : (
|
||||
<Suggestions className="min-h-16 w-fit items-start">
|
||||
<Suggestions className="w-fit items-center">
|
||||
{followups.map((s) => (
|
||||
<Suggestion
|
||||
key={s}
|
||||
className="py-1.5"
|
||||
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"
|
||||
className="text-muted-foreground h-auto cursor-pointer rounded-full px-2.5 py-1.5 text-xs font-normal"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
type="button"
|
||||
|
||||
@ -44,8 +44,7 @@ import {
|
||||
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 const MESSAGE_LIST_DEFAULT_PADDING_BOTTOM = 24;
|
||||
|
||||
const LOAD_MORE_HISTORY_THROTTLE_MS = 1200;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user