feat:初步修改布局
This commit is contained in:
parent
dbef018fd1
commit
1fd5405f42
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"window.title": "${activeEditorShort}${separator}${separator}deer-flow/frontend"
|
|
||||||
}
|
|
||||||
|
|
@ -87,7 +87,7 @@ body {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
background-color: var(--bg-primary);
|
/* background-color: var(--bg-primary); */
|
||||||
transition:
|
transition:
|
||||||
background-color var(--transition-normal),
|
background-color var(--transition-normal),
|
||||||
color var(--transition-normal);
|
color var(--transition-normal);
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,18 @@
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
import { type PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
import { type PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { ArtifactTrigger } from "@/components/workspace/artifacts";
|
import { ArtifactTrigger } from "@/components/workspace/artifacts";
|
||||||
import {
|
import {
|
||||||
ChatBox,
|
ChatBox,
|
||||||
useSpecificChatMode,
|
useSpecificChatMode,
|
||||||
useThreadChat,
|
useThreadChat,
|
||||||
} from "@/components/workspace/chats";
|
} from "@/components/workspace/chats";
|
||||||
|
import { DevTodoList } from "@/components/workspace/dev-todo-list";
|
||||||
import { InputBox } from "@/components/workspace/input-box";
|
import { InputBox } from "@/components/workspace/input-box";
|
||||||
import { MessageList } from "@/components/workspace/messages";
|
import { MessageList } from "@/components/workspace/messages";
|
||||||
import { ThreadContext } from "@/components/workspace/messages/context";
|
import { ThreadContext } from "@/components/workspace/messages/context";
|
||||||
import { ThreadTitle } from "@/components/workspace/thread-title";
|
import { ThreadTitle } from "@/components/workspace/thread-title";
|
||||||
import { TodoList } from "@/components/workspace/todo-list";
|
|
||||||
import { Welcome } from "@/components/workspace/welcome";
|
import { Welcome } from "@/components/workspace/welcome";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { useNotification } from "@/core/notification/hooks";
|
import { useNotification } from "@/core/notification/hooks";
|
||||||
|
|
@ -72,19 +73,38 @@ export default function ChatPage() {
|
||||||
return (
|
return (
|
||||||
<ThreadContext.Provider value={{ thread, isMock }}>
|
<ThreadContext.Provider value={{ thread, isMock }}>
|
||||||
<ChatBox threadId={threadId}>
|
<ChatBox threadId={threadId}>
|
||||||
<div className="relative flex size-full min-h-0 justify-between">
|
<div className="relative flex size-full min-h-0 px-[20px] bg-background justify-between">
|
||||||
<header
|
<header
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute top-0 right-0 left-0 z-30 flex h-12 shrink-0 items-center px-4",
|
"absolute top-0 right-0 left-0 z-30 grid grid-cols-3 h-14 px-[20px] py-[20px] shrink-0 items-center rounded-t-[20px]",
|
||||||
isNewThread
|
isNewThread
|
||||||
? "bg-background/0 backdrop-blur-none"
|
? "bg-background/0 backdrop-blur-none"
|
||||||
: "bg-background/80 shadow-xs backdrop-blur",
|
: "bg-background/80 shadow-xs backdrop-blur",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center text-sm font-medium">
|
{/* 返回查看结果左箭头 */}
|
||||||
|
<div className=" w-full items-center h-[18px] text-sm font-medium">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.5 10H13.25H15.6875H16.5M3.5 10L7.5625 6M3.5 10L7.5625 14" stroke="#666666" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className=" w-full text-center items-center h-[18px] text-sm font-medium">
|
||||||
<ThreadTitle threadId={threadId} thread={thread} />
|
<ThreadTitle threadId={threadId} thread={thread} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex justify-end items-center gap-2">
|
||||||
|
<DevTodoList
|
||||||
|
className="bg-white"
|
||||||
|
todos={thread.values.todos ?? []}
|
||||||
|
hidden={
|
||||||
|
!thread.values.todos || thread.values.todos.length === 0
|
||||||
|
}
|
||||||
|
trigger={
|
||||||
|
<Button size="sm" variant="ghost" className="text-sm font-medium h-[18px]">
|
||||||
|
Todo
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<ArtifactTrigger />
|
<ArtifactTrigger />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -96,27 +116,18 @@ export default function ChatPage() {
|
||||||
thread={thread}
|
thread={thread}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute right-0 bottom-0 left-0 z-30 flex justify-center px-4">
|
</main>
|
||||||
|
</div>
|
||||||
|
<div className="fixed right-0 bottom-3 left-0 z-30 flex justify-center px-4 pointer-events-none">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative w-full",
|
"relative w-full pointer-events-auto",
|
||||||
isNewThread && "-translate-y-[calc(50vh-96px)]",
|
isNewThread && "-translate-y-[calc(50vh-96px)]",
|
||||||
isNewThread
|
isNewThread
|
||||||
? "max-w-(--container-width-sm)"
|
? "max-w-(--container-width-sm)"
|
||||||
: "max-w-(--container-width-md)",
|
: "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
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<InputBox
|
<InputBox
|
||||||
className={cn("bg-background/5 w-full -translate-y-4")}
|
className={cn("bg-background/5 w-full -translate-y-4")}
|
||||||
isNewThread={isNewThread}
|
isNewThread={isNewThread}
|
||||||
|
|
@ -139,8 +150,6 @@ export default function ChatPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</ChatBox>
|
</ChatBox>
|
||||||
</ThreadContext.Provider>
|
</ThreadContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ function SidebarProvider({
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
|
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full m-auto rounded-t-[20px] overflow-hidden",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -207,6 +207,7 @@ function Sidebar({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
// !
|
||||||
className="group peer text-sidebar-foreground hidden md:block"
|
className="group peer text-sidebar-foreground hidden md:block"
|
||||||
data-state={state}
|
data-state={state}
|
||||||
data-collapsible={state === "collapsed" ? collapsible : ""}
|
data-collapsible={state === "collapsed" ? collapsible : ""}
|
||||||
|
|
@ -309,7 +310,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
||||||
<main
|
<main
|
||||||
data-slot="sidebar-inset"
|
data-slot="sidebar-inset"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background relative flex w-full flex-1 flex-col",
|
"relative flex w-full flex-1 flex-col",
|
||||||
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
|
||||||
defaultLayout={{ chat: 100, artifacts: 0 }}
|
defaultLayout={{ chat: 100, artifacts: 0 }}
|
||||||
groupRef={layoutRef}
|
groupRef={layoutRef}
|
||||||
>
|
>
|
||||||
<ResizablePanel className="relative" defaultSize={100} id="chat">
|
<ResizablePanel className="relative overflow-hidden rounded-t-[20px] " defaultSize={100} id="chat">
|
||||||
{children}
|
{children}
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle
|
<ResizableHandle
|
||||||
|
|
@ -111,14 +111,14 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
|
||||||
/>
|
/>
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
className={cn(
|
className={cn(
|
||||||
"transition-all duration-300 ease-in-out",
|
"transition-all duration-300 ease-in-out ml-[20px]",
|
||||||
!artifactsOpen && "opacity-0",
|
!artifactsOpen && "opacity-0",
|
||||||
)}
|
)}
|
||||||
id="artifacts"
|
id="artifacts"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-full p-4 transition-transform duration-300 ease-in-out",
|
"h-full p-4 transition-transform duration-300 ease-in-out bg-background rounded-t-[20px]",
|
||||||
artifactPanelOpen ? "translate-x-0" : "translate-x-full",
|
artifactPanelOpen ? "translate-x-0" : "translate-x-full",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ListTodoIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import type { Todo } from "@/core/todos";
|
||||||
|
|
||||||
|
import {
|
||||||
|
QueueItem,
|
||||||
|
QueueItemContent,
|
||||||
|
QueueItemIndicator,
|
||||||
|
QueueList,
|
||||||
|
} from "../ai-elements/queue";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "../ui/dropdown-menu";
|
||||||
|
|
||||||
|
export function DevTodoList({
|
||||||
|
className,
|
||||||
|
todos,
|
||||||
|
trigger,
|
||||||
|
hidden,
|
||||||
|
}: {
|
||||||
|
className?: string;
|
||||||
|
todos: Todo[];
|
||||||
|
trigger: React.ReactNode;
|
||||||
|
hidden: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger className={hidden ? "hidden" : ""} asChild>{trigger}</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className={className} align="start" side="top">
|
||||||
|
<QueueList className="w-64">
|
||||||
|
{todos.map((todo, i) => (
|
||||||
|
<QueueItem key={i + (todo.content ?? "")}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<QueueItemIndicator
|
||||||
|
className={
|
||||||
|
todo.status === "in_progress" ? "bg-primary/70" : ""
|
||||||
|
}
|
||||||
|
completed={todo.status === "completed"}
|
||||||
|
/>
|
||||||
|
<QueueItemContent
|
||||||
|
className={
|
||||||
|
todo.status === "in_progress" ? "text-primary/70" : ""
|
||||||
|
}
|
||||||
|
completed={todo.status === "completed"}
|
||||||
|
>
|
||||||
|
{todo.content}
|
||||||
|
</QueueItemContent>
|
||||||
|
</div>
|
||||||
|
</QueueItem>
|
||||||
|
))}
|
||||||
|
</QueueList>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ListTodoIcon };
|
||||||
|
|
@ -19,7 +19,7 @@ export function FlipDisplay({
|
||||||
initial={{ y: 8, opacity: 0 }}
|
initial={{ y: 8, opacity: 0 }}
|
||||||
animate={{ y: 2, opacity: 1 }}
|
animate={{ y: 2, opacity: 1 }}
|
||||||
exit={{ y: -8, opacity: 0 }}
|
exit={{ y: -8, opacity: 0 }}
|
||||||
transition={{ duration: 0.25, ease: [0.4, 0, 0.2, 1] }}
|
// transition={{ duration: 0.25, ease: [0.4, 0, 0.2, 1] }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ export function InputBox({
|
||||||
const { thread, isMock } = useThread();
|
const { thread, isMock } = useThread();
|
||||||
const { textInput } = usePromptInputController();
|
const { textInput } = usePromptInputController();
|
||||||
const promptRootRef = useRef<HTMLDivElement | null>(null);
|
const promptRootRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
|
|
||||||
const [followups, setFollowups] = useState<string[]>([]);
|
const [followups, setFollowups] = useState<string[]>([]);
|
||||||
const [followupsHidden, setFollowupsHidden] = useState(false);
|
const [followupsHidden, setFollowupsHidden] = useState(false);
|
||||||
|
|
@ -158,6 +159,22 @@ export function InputBox({
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isFocused) return;
|
||||||
|
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
||||||
|
setIsFocused(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
}, [isFocused]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -372,10 +389,11 @@ export function InputBox({
|
||||||
}, [context.model_name, disabled, isMock, status, thread.messages, threadId]);
|
}, [context.model_name, disabled, isMock, status, thread.messages, threadId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={promptRootRef} className="relative">
|
<div ref={(el) => { promptRootRef.current = el; containerRef.current = el; }} className="relative">
|
||||||
<PromptInput
|
<PromptInput
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background/85 rounded-2xl backdrop-blur-sm transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-2xl",
|
"bg-background/85 rounded-2xl backdrop-blur-sm transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-2xl",
|
||||||
|
!isFocused && "h-12",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
@ -394,8 +412,9 @@ export function InputBox({
|
||||||
<PromptInputAttachments>
|
<PromptInputAttachments>
|
||||||
{(attachment) => <PromptInputAttachment data={attachment} />}
|
{(attachment) => <PromptInputAttachment data={attachment} />}
|
||||||
</PromptInputAttachments>
|
</PromptInputAttachments>
|
||||||
<PromptInputBody className="absolute top-0 right-0 left-0 z-3">
|
<PromptInputBody className={cn("absolute top-0 right-0 left-0 z-3", !isFocused && "opacity-0 pointer-events-none")}>
|
||||||
<PromptInputTextarea
|
<PromptInputTextarea
|
||||||
|
ref={textareaRef}
|
||||||
className={cn("size-full")}
|
className={cn("size-full")}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={t.inputBox.placeholder}
|
placeholder={t.inputBox.placeholder}
|
||||||
|
|
@ -403,17 +422,17 @@ export function InputBox({
|
||||||
defaultValue={initialValue}
|
defaultValue={initialValue}
|
||||||
/>
|
/>
|
||||||
</PromptInputBody>
|
</PromptInputBody>
|
||||||
<PromptInputFooter className="flex">
|
{!isFocused && (
|
||||||
<PromptInputTools>
|
<div
|
||||||
{/* TODO: Add more connectors here
|
className="absolute inset-0 z-1 cursor-text"
|
||||||
<PromptInputActionMenu>
|
onClick={() => {
|
||||||
<PromptInputActionMenuTrigger className="px-2!" />
|
setIsFocused(true);
|
||||||
<PromptInputActionMenuContent>
|
textareaRef.current?.focus();
|
||||||
<PromptInputActionAddAttachments
|
}}
|
||||||
label={t.inputBox.addAttachments}
|
|
||||||
/>
|
/>
|
||||||
</PromptInputActionMenuContent>
|
)}
|
||||||
</PromptInputActionMenu> */}
|
<PromptInputFooter className={cn("flex", !isFocused && "hidden")}>
|
||||||
|
<PromptInputTools>
|
||||||
<AddAttachmentsButton className="px-2!" />
|
<AddAttachmentsButton className="px-2!" />
|
||||||
<PromptInputActionMenu>
|
<PromptInputActionMenu>
|
||||||
<ModeHoverGuide
|
<ModeHoverGuide
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,6 @@ export function MessageList({
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{thread.isLoading && <StreamingIndicator className="my-4" />}
|
{thread.isLoading && <StreamingIndicator className="my-4" />}
|
||||||
<div style={{ height: `${paddingBottom}px` }} />
|
|
||||||
</ConversationContent>
|
</ConversationContent>
|
||||||
</Conversation>
|
</Conversation>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--radius: 0.625rem;
|
--radius: 0.625rem;
|
||||||
--background: oklch(0.9855 0.0098 87.47);
|
--background: #F9F8FA;
|
||||||
--foreground: oklch(0.145 0 0);
|
--foreground: oklch(0.145 0 0);
|
||||||
--card: oklch(1 0.0098 87.47);
|
--card: oklch(1 0.0098 87.47);
|
||||||
--card-foreground: oklch(0.145 0 0);
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
|
@ -297,7 +297,7 @@
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply text-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-md {
|
.container-md {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue