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;
|
||||
line-height: 1.6;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--bg-primary);
|
||||
/* background-color: var(--bg-primary); */
|
||||
transition:
|
||||
background-color var(--transition-normal),
|
||||
color var(--transition-normal);
|
||||
|
|
|
|||
|
|
@ -3,17 +3,18 @@
|
|||
import { useCallback } from "react";
|
||||
|
||||
import { type PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArtifactTrigger } from "@/components/workspace/artifacts";
|
||||
import {
|
||||
ChatBox,
|
||||
useSpecificChatMode,
|
||||
useThreadChat,
|
||||
} from "@/components/workspace/chats";
|
||||
import { DevTodoList } from "@/components/workspace/dev-todo-list";
|
||||
import { InputBox } from "@/components/workspace/input-box";
|
||||
import { MessageList } from "@/components/workspace/messages";
|
||||
import { ThreadContext } from "@/components/workspace/messages/context";
|
||||
import { ThreadTitle } from "@/components/workspace/thread-title";
|
||||
import { TodoList } from "@/components/workspace/todo-list";
|
||||
import { Welcome } from "@/components/workspace/welcome";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { useNotification } from "@/core/notification/hooks";
|
||||
|
|
@ -72,19 +73,38 @@ export default function ChatPage() {
|
|||
return (
|
||||
<ThreadContext.Provider value={{ thread, isMock }}>
|
||||
<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
|
||||
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
|
||||
? "bg-background/0 backdrop-blur-none"
|
||||
: "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} />
|
||||
</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 />
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -96,51 +116,40 @@ export default function ChatPage() {
|
|||
thread={thread}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute right-0 bottom-0 left-0 z-30 flex justify-center px-4">
|
||||
<div
|
||||
className={cn(
|
||||
"relative w-full",
|
||||
isNewThread && "-translate-y-[calc(50vh-96px)]",
|
||||
isNewThread
|
||||
? "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
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<InputBox
|
||||
className={cn("bg-background/5 w-full -translate-y-4")}
|
||||
isNewThread={isNewThread}
|
||||
threadId={threadId}
|
||||
autoFocus={isNewThread}
|
||||
status={thread.isLoading ? "streaming" : "ready"}
|
||||
context={settings.context}
|
||||
extraHeader={
|
||||
isNewThread && <Welcome mode={settings.context.mode} />
|
||||
}
|
||||
disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"}
|
||||
onContextChange={(context) => setSettings("context", context)}
|
||||
onSubmit={handleSubmit}
|
||||
onStop={handleStop}
|
||||
/>
|
||||
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" && (
|
||||
<div className="text-muted-foreground/67 w-full translate-y-12 text-center text-xs">
|
||||
{t.common.notAvailableInDemoMode}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<div className="fixed right-0 bottom-3 left-0 z-30 flex justify-center px-4 pointer-events-none">
|
||||
<div
|
||||
className={cn(
|
||||
"relative w-full pointer-events-auto",
|
||||
isNewThread && "-translate-y-[calc(50vh-96px)]",
|
||||
isNewThread
|
||||
? "max-w-(--container-width-sm)"
|
||||
: "max-w-(--container-width-md)",
|
||||
)}
|
||||
>
|
||||
<InputBox
|
||||
className={cn("bg-background/5 w-full -translate-y-4")}
|
||||
isNewThread={isNewThread}
|
||||
threadId={threadId}
|
||||
autoFocus={isNewThread}
|
||||
status={thread.isLoading ? "streaming" : "ready"}
|
||||
context={settings.context}
|
||||
extraHeader={
|
||||
isNewThread && <Welcome mode={settings.context.mode} />
|
||||
}
|
||||
disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"}
|
||||
onContextChange={(context) => setSettings("context", context)}
|
||||
onSubmit={handleSubmit}
|
||||
onStop={handleStop}
|
||||
/>
|
||||
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" && (
|
||||
<div className="text-muted-foreground/67 w-full translate-y-12 text-center text-xs">
|
||||
{t.common.notAvailableInDemoMode}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ChatBox>
|
||||
</ThreadContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ function SidebarProvider({
|
|||
} as React.CSSProperties
|
||||
}
|
||||
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,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -207,6 +207,7 @@ function Sidebar({
|
|||
|
||||
return (
|
||||
<div
|
||||
// !
|
||||
className="group peer text-sidebar-foreground hidden md:block"
|
||||
data-state={state}
|
||||
data-collapsible={state === "collapsed" ? collapsible : ""}
|
||||
|
|
@ -309,7 +310,7 @@ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
|||
<main
|
||||
data-slot="sidebar-inset"
|
||||
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",
|
||||
className,
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
|
|||
defaultLayout={{ chat: 100, artifacts: 0 }}
|
||||
groupRef={layoutRef}
|
||||
>
|
||||
<ResizablePanel className="relative" defaultSize={100} id="chat">
|
||||
<ResizablePanel className="relative overflow-hidden rounded-t-[20px] " defaultSize={100} id="chat">
|
||||
{children}
|
||||
</ResizablePanel>
|
||||
<ResizableHandle
|
||||
|
|
@ -111,14 +111,14 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
|
|||
/>
|
||||
<ResizablePanel
|
||||
className={cn(
|
||||
"transition-all duration-300 ease-in-out",
|
||||
"transition-all duration-300 ease-in-out ml-[20px]",
|
||||
!artifactsOpen && "opacity-0",
|
||||
)}
|
||||
id="artifacts"
|
||||
>
|
||||
<div
|
||||
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",
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
animate={{ y: 2, opacity: 1 }}
|
||||
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}
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ export function InputBox({
|
|||
const { thread, isMock } = useThread();
|
||||
const { textInput } = usePromptInputController();
|
||||
const promptRootRef = useRef<HTMLDivElement | null>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const [followups, setFollowups] = useState<string[]>([]);
|
||||
const [followupsHidden, setFollowupsHidden] = useState(false);
|
||||
|
|
@ -158,6 +159,22 @@ export function InputBox({
|
|||
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(() => {
|
||||
if (models.length === 0) {
|
||||
return;
|
||||
|
|
@ -372,10 +389,11 @@ export function InputBox({
|
|||
}, [context.model_name, disabled, isMock, status, thread.messages, threadId]);
|
||||
|
||||
return (
|
||||
<div ref={promptRootRef} className="relative">
|
||||
<div ref={(el) => { promptRootRef.current = el; containerRef.current = el; }} className="relative">
|
||||
<PromptInput
|
||||
className={cn(
|
||||
"bg-background/85 rounded-2xl backdrop-blur-sm transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-2xl",
|
||||
!isFocused && "h-12",
|
||||
className,
|
||||
)}
|
||||
disabled={disabled}
|
||||
|
|
@ -394,8 +412,9 @@ export function InputBox({
|
|||
<PromptInputAttachments>
|
||||
{(attachment) => <PromptInputAttachment data={attachment} />}
|
||||
</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
|
||||
ref={textareaRef}
|
||||
className={cn("size-full")}
|
||||
disabled={disabled}
|
||||
placeholder={t.inputBox.placeholder}
|
||||
|
|
@ -403,221 +422,144 @@ export function InputBox({
|
|||
defaultValue={initialValue}
|
||||
/>
|
||||
</PromptInputBody>
|
||||
<PromptInputFooter className="flex">
|
||||
{!isFocused && (
|
||||
<div
|
||||
className="absolute inset-0 z-1 cursor-text"
|
||||
onClick={() => {
|
||||
setIsFocused(true);
|
||||
textareaRef.current?.focus();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<PromptInputFooter className={cn("flex", !isFocused && "hidden")}>
|
||||
<PromptInputTools>
|
||||
{/* TODO: Add more connectors here
|
||||
<PromptInputActionMenu>
|
||||
<PromptInputActionMenuTrigger className="px-2!" />
|
||||
<PromptInputActionMenuContent>
|
||||
<PromptInputActionAddAttachments
|
||||
label={t.inputBox.addAttachments}
|
||||
/>
|
||||
</PromptInputActionMenuContent>
|
||||
</PromptInputActionMenu> */}
|
||||
<AddAttachmentsButton className="px-2!" />
|
||||
<PromptInputActionMenu>
|
||||
<ModeHoverGuide
|
||||
mode={
|
||||
context.mode === "flash" ||
|
||||
context.mode === "thinking" ||
|
||||
context.mode === "pro" ||
|
||||
context.mode === "ultra"
|
||||
? context.mode
|
||||
: "flash"
|
||||
}
|
||||
>
|
||||
<PromptInputActionMenuTrigger className="gap-1! px-2!">
|
||||
<div>
|
||||
{context.mode === "flash" && <ZapIcon className="size-3" />}
|
||||
{context.mode === "thinking" && (
|
||||
<LightbulbIcon className="size-3" />
|
||||
)}
|
||||
{context.mode === "pro" && (
|
||||
<GraduationCapIcon className="size-3" />
|
||||
)}
|
||||
{context.mode === "ultra" && (
|
||||
<RocketIcon className="size-3 text-[#dabb5e]" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"text-xs font-normal",
|
||||
context.mode === "ultra" ? "golden-text" : "",
|
||||
)}
|
||||
>
|
||||
{(context.mode === "flash" && t.inputBox.flashMode) ||
|
||||
(context.mode === "thinking" && t.inputBox.reasoningMode) ||
|
||||
(context.mode === "pro" && t.inputBox.proMode) ||
|
||||
(context.mode === "ultra" && t.inputBox.ultraMode)}
|
||||
</div>
|
||||
</PromptInputActionMenuTrigger>
|
||||
</ModeHoverGuide>
|
||||
<PromptInputActionMenuContent className="w-80">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
||||
{t.inputBox.mode}
|
||||
</DropdownMenuLabel>
|
||||
<PromptInputActionMenu>
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.mode === "flash"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleModeSelect("flash")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
<ZapIcon
|
||||
className={cn(
|
||||
"mr-2 size-4",
|
||||
context.mode === "flash" &&
|
||||
"text-accent-foreground",
|
||||
)}
|
||||
/>
|
||||
{t.inputBox.flashMode}
|
||||
</div>
|
||||
<div className="pl-7 text-xs">
|
||||
{t.inputBox.flashModeDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.mode === "flash" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
{supportThinking && (
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.mode === "thinking"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleModeSelect("thinking")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
<LightbulbIcon
|
||||
className={cn(
|
||||
"mr-2 size-4",
|
||||
context.mode === "thinking" &&
|
||||
"text-accent-foreground",
|
||||
)}
|
||||
/>
|
||||
{t.inputBox.reasoningMode}
|
||||
</div>
|
||||
<div className="pl-7 text-xs">
|
||||
{t.inputBox.reasoningModeDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.mode === "thinking" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
)}
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.mode === "pro"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleModeSelect("pro")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
<GraduationCapIcon
|
||||
className={cn(
|
||||
"mr-2 size-4",
|
||||
context.mode === "pro" && "text-accent-foreground",
|
||||
)}
|
||||
/>
|
||||
{t.inputBox.proMode}
|
||||
</div>
|
||||
<div className="pl-7 text-xs">
|
||||
{t.inputBox.proModeDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.mode === "pro" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.mode === "ultra"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleModeSelect("ultra")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
<RocketIcon
|
||||
className={cn(
|
||||
"mr-2 size-4",
|
||||
context.mode === "ultra" && "text-[#dabb5e]",
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
context.mode === "ultra" && "golden-text",
|
||||
)}
|
||||
>
|
||||
{t.inputBox.ultraMode}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pl-7 text-xs">
|
||||
{t.inputBox.ultraModeDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.mode === "ultra" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
</PromptInputActionMenu>
|
||||
</DropdownMenuGroup>
|
||||
</PromptInputActionMenuContent>
|
||||
</PromptInputActionMenu>
|
||||
{supportReasoningEffort && context.mode !== "flash" && (
|
||||
<AddAttachmentsButton className="px-2!" />
|
||||
<PromptInputActionMenu>
|
||||
<PromptInputActionMenuTrigger className="gap-1! px-2!">
|
||||
<div className="text-xs font-normal">
|
||||
{t.inputBox.reasoningEffort}:
|
||||
{context.reasoning_effort === "minimal" && " " + t.inputBox.reasoningEffortMinimal}
|
||||
{context.reasoning_effort === "low" && " " + t.inputBox.reasoningEffortLow}
|
||||
{context.reasoning_effort === "medium" && " " + t.inputBox.reasoningEffortMedium}
|
||||
{context.reasoning_effort === "high" && " " + t.inputBox.reasoningEffortHigh}
|
||||
</div>
|
||||
</PromptInputActionMenuTrigger>
|
||||
<PromptInputActionMenuContent className="w-70">
|
||||
<ModeHoverGuide
|
||||
mode={
|
||||
context.mode === "flash" ||
|
||||
context.mode === "thinking" ||
|
||||
context.mode === "pro" ||
|
||||
context.mode === "ultra"
|
||||
? context.mode
|
||||
: "flash"
|
||||
}
|
||||
>
|
||||
<PromptInputActionMenuTrigger className="gap-1! px-2!">
|
||||
<div>
|
||||
{context.mode === "flash" && <ZapIcon className="size-3" />}
|
||||
{context.mode === "thinking" && (
|
||||
<LightbulbIcon className="size-3" />
|
||||
)}
|
||||
{context.mode === "pro" && (
|
||||
<GraduationCapIcon className="size-3" />
|
||||
)}
|
||||
{context.mode === "ultra" && (
|
||||
<RocketIcon className="size-3 text-[#dabb5e]" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"text-xs font-normal",
|
||||
context.mode === "ultra" ? "golden-text" : "",
|
||||
)}
|
||||
>
|
||||
{(context.mode === "flash" && t.inputBox.flashMode) ||
|
||||
(context.mode === "thinking" && t.inputBox.reasoningMode) ||
|
||||
(context.mode === "pro" && t.inputBox.proMode) ||
|
||||
(context.mode === "ultra" && t.inputBox.ultraMode)}
|
||||
</div>
|
||||
</PromptInputActionMenuTrigger>
|
||||
</ModeHoverGuide>
|
||||
<PromptInputActionMenuContent className="w-80">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
||||
{t.inputBox.reasoningEffort}
|
||||
{t.inputBox.mode}
|
||||
</DropdownMenuLabel>
|
||||
<PromptInputActionMenu>
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.reasoning_effort === "minimal"
|
||||
context.mode === "flash"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleReasoningEffortSelect("minimal")}
|
||||
onSelect={() => handleModeSelect("flash")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
{t.inputBox.reasoningEffortMinimal}
|
||||
<ZapIcon
|
||||
className={cn(
|
||||
"mr-2 size-4",
|
||||
context.mode === "flash" &&
|
||||
"text-accent-foreground",
|
||||
)}
|
||||
/>
|
||||
{t.inputBox.flashMode}
|
||||
</div>
|
||||
<div className="pl-2 text-xs">
|
||||
{t.inputBox.reasoningEffortMinimalDescription}
|
||||
<div className="pl-7 text-xs">
|
||||
{t.inputBox.flashModeDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.reasoning_effort === "minimal" ? (
|
||||
{context.mode === "flash" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
{supportThinking && (
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.mode === "thinking"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleModeSelect("thinking")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
<LightbulbIcon
|
||||
className={cn(
|
||||
"mr-2 size-4",
|
||||
context.mode === "thinking" &&
|
||||
"text-accent-foreground",
|
||||
)}
|
||||
/>
|
||||
{t.inputBox.reasoningMode}
|
||||
</div>
|
||||
<div className="pl-7 text-xs">
|
||||
{t.inputBox.reasoningModeDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.mode === "thinking" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
)}
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.mode === "pro"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleModeSelect("pro")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
<GraduationCapIcon
|
||||
className={cn(
|
||||
"mr-2 size-4",
|
||||
context.mode === "pro" && "text-accent-foreground",
|
||||
)}
|
||||
/>
|
||||
{t.inputBox.proMode}
|
||||
</div>
|
||||
<div className="pl-7 text-xs">
|
||||
{t.inputBox.proModeDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.mode === "pro" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
|
|
@ -625,65 +567,33 @@ export function InputBox({
|
|||
</PromptInputActionMenuItem>
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.reasoning_effort === "low"
|
||||
context.mode === "ultra"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleReasoningEffortSelect("low")}
|
||||
onSelect={() => handleModeSelect("ultra")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
{t.inputBox.reasoningEffortLow}
|
||||
<RocketIcon
|
||||
className={cn(
|
||||
"mr-2 size-4",
|
||||
context.mode === "ultra" && "text-[#dabb5e]",
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
context.mode === "ultra" && "golden-text",
|
||||
)}
|
||||
>
|
||||
{t.inputBox.ultraMode}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pl-2 text-xs">
|
||||
{t.inputBox.reasoningEffortLowDescription}
|
||||
<div className="pl-7 text-xs">
|
||||
{t.inputBox.ultraModeDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.reasoning_effort === "low" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.reasoning_effort === "medium" || !context.reasoning_effort
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleReasoningEffortSelect("medium")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
{t.inputBox.reasoningEffortMedium}
|
||||
</div>
|
||||
<div className="pl-2 text-xs">
|
||||
{t.inputBox.reasoningEffortMediumDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.reasoning_effort === "medium" || !context.reasoning_effort ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.reasoning_effort === "high"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleReasoningEffortSelect("high")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
{t.inputBox.reasoningEffortHigh}
|
||||
</div>
|
||||
<div className="pl-2 text-xs">
|
||||
{t.inputBox.reasoningEffortHighDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.reasoning_effort === "high" ? (
|
||||
{context.mode === "ultra" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
|
|
@ -693,56 +603,165 @@ export function InputBox({
|
|||
</DropdownMenuGroup>
|
||||
</PromptInputActionMenuContent>
|
||||
</PromptInputActionMenu>
|
||||
)}
|
||||
</PromptInputTools>
|
||||
<PromptInputTools>
|
||||
<ModelSelector
|
||||
open={modelDialogOpen}
|
||||
onOpenChange={setModelDialogOpen}
|
||||
>
|
||||
<ModelSelectorTrigger asChild>
|
||||
<PromptInputButton>
|
||||
<ModelSelectorName className="text-xs font-normal">
|
||||
{selectedModel?.display_name}
|
||||
</ModelSelectorName>
|
||||
</PromptInputButton>
|
||||
</ModelSelectorTrigger>
|
||||
<ModelSelectorContent>
|
||||
<ModelSelectorInput placeholder={t.inputBox.searchModels} />
|
||||
<ModelSelectorList>
|
||||
{models.map((m) => (
|
||||
<ModelSelectorItem
|
||||
key={m.name}
|
||||
value={m.name}
|
||||
onSelect={() => handleModelSelect(m.name)}
|
||||
>
|
||||
<ModelSelectorName>{m.display_name}</ModelSelectorName>
|
||||
{m.name === context.model_name ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</ModelSelectorItem>
|
||||
))}
|
||||
</ModelSelectorList>
|
||||
</ModelSelectorContent>
|
||||
</ModelSelector>
|
||||
<PromptInputSubmit
|
||||
className="rounded-full"
|
||||
disabled={disabled}
|
||||
variant="outline"
|
||||
status={status}
|
||||
/>
|
||||
</PromptInputTools>
|
||||
</PromptInputFooter>
|
||||
{isNewThread && searchParams.get("mode") !== "skill" && (
|
||||
<div className="absolute right-0 -bottom-20 left-0 z-0 flex items-center justify-center">
|
||||
<SuggestionList />
|
||||
</div>
|
||||
)}
|
||||
{!isNewThread && (
|
||||
<div className="bg-background absolute right-0 -bottom-[17px] left-0 z-0 h-4"></div>
|
||||
)}
|
||||
{supportReasoningEffort && context.mode !== "flash" && (
|
||||
<PromptInputActionMenu>
|
||||
<PromptInputActionMenuTrigger className="gap-1! px-2!">
|
||||
<div className="text-xs font-normal">
|
||||
{t.inputBox.reasoningEffort}:
|
||||
{context.reasoning_effort === "minimal" && " " + t.inputBox.reasoningEffortMinimal}
|
||||
{context.reasoning_effort === "low" && " " + t.inputBox.reasoningEffortLow}
|
||||
{context.reasoning_effort === "medium" && " " + t.inputBox.reasoningEffortMedium}
|
||||
{context.reasoning_effort === "high" && " " + t.inputBox.reasoningEffortHigh}
|
||||
</div>
|
||||
</PromptInputActionMenuTrigger>
|
||||
<PromptInputActionMenuContent className="w-70">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
||||
{t.inputBox.reasoningEffort}
|
||||
</DropdownMenuLabel>
|
||||
<PromptInputActionMenu>
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.reasoning_effort === "minimal"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleReasoningEffortSelect("minimal")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
{t.inputBox.reasoningEffortMinimal}
|
||||
</div>
|
||||
<div className="pl-2 text-xs">
|
||||
{t.inputBox.reasoningEffortMinimalDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.reasoning_effort === "minimal" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.reasoning_effort === "low"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleReasoningEffortSelect("low")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
{t.inputBox.reasoningEffortLow}
|
||||
</div>
|
||||
<div className="pl-2 text-xs">
|
||||
{t.inputBox.reasoningEffortLowDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.reasoning_effort === "low" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.reasoning_effort === "medium" || !context.reasoning_effort
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleReasoningEffortSelect("medium")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
{t.inputBox.reasoningEffortMedium}
|
||||
</div>
|
||||
<div className="pl-2 text-xs">
|
||||
{t.inputBox.reasoningEffortMediumDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.reasoning_effort === "medium" || !context.reasoning_effort ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
<PromptInputActionMenuItem
|
||||
className={cn(
|
||||
context.reasoning_effort === "high"
|
||||
? "text-accent-foreground"
|
||||
: "text-muted-foreground/65",
|
||||
)}
|
||||
onSelect={() => handleReasoningEffortSelect("high")}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-1 font-bold">
|
||||
{t.inputBox.reasoningEffortHigh}
|
||||
</div>
|
||||
<div className="pl-2 text-xs">
|
||||
{t.inputBox.reasoningEffortHighDescription}
|
||||
</div>
|
||||
</div>
|
||||
{context.reasoning_effort === "high" ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</PromptInputActionMenuItem>
|
||||
</PromptInputActionMenu>
|
||||
</DropdownMenuGroup>
|
||||
</PromptInputActionMenuContent>
|
||||
</PromptInputActionMenu>
|
||||
)}
|
||||
</PromptInputTools>
|
||||
<PromptInputTools>
|
||||
<ModelSelector
|
||||
open={modelDialogOpen}
|
||||
onOpenChange={setModelDialogOpen}
|
||||
>
|
||||
<ModelSelectorTrigger asChild>
|
||||
<PromptInputButton>
|
||||
<ModelSelectorName className="text-xs font-normal">
|
||||
{selectedModel?.display_name}
|
||||
</ModelSelectorName>
|
||||
</PromptInputButton>
|
||||
</ModelSelectorTrigger>
|
||||
<ModelSelectorContent>
|
||||
<ModelSelectorInput placeholder={t.inputBox.searchModels} />
|
||||
<ModelSelectorList>
|
||||
{models.map((m) => (
|
||||
<ModelSelectorItem
|
||||
key={m.name}
|
||||
value={m.name}
|
||||
onSelect={() => handleModelSelect(m.name)}
|
||||
>
|
||||
<ModelSelectorName>{m.display_name}</ModelSelectorName>
|
||||
{m.name === context.model_name ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
) : (
|
||||
<div className="ml-auto size-4" />
|
||||
)}
|
||||
</ModelSelectorItem>
|
||||
))}
|
||||
</ModelSelectorList>
|
||||
</ModelSelectorContent>
|
||||
</ModelSelector>
|
||||
<PromptInputSubmit
|
||||
className="rounded-full"
|
||||
disabled={disabled}
|
||||
variant="outline"
|
||||
status={status}
|
||||
/>
|
||||
</PromptInputTools>
|
||||
</PromptInputFooter>
|
||||
{isNewThread && searchParams.get("mode") !== "skill" && (
|
||||
<div className="absolute right-0 -bottom-20 left-0 z-0 flex items-center justify-center">
|
||||
<SuggestionList />
|
||||
</div>
|
||||
)}
|
||||
{!isNewThread && (
|
||||
<div className="bg-background absolute right-0 -bottom-[17px] left-0 z-0 h-4"></div>
|
||||
)}
|
||||
</PromptInput>
|
||||
|
||||
{!disabled &&
|
||||
|
|
|
|||
|
|
@ -199,7 +199,6 @@ export function MessageList({
|
|||
);
|
||||
})}
|
||||
{thread.isLoading && <StreamingIndicator className="my-4" />}
|
||||
<div style={{ height: `${paddingBottom}px` }} />
|
||||
</ConversationContent>
|
||||
</Conversation>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@
|
|||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(0.9855 0.0098 87.47);
|
||||
--background: #F9F8FA;
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0.0098 87.47);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
|
|
@ -297,7 +297,7 @@
|
|||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
@apply text-foreground;
|
||||
}
|
||||
|
||||
.container-md {
|
||||
|
|
|
|||
Loading…
Reference in New Issue