feat:初步修改布局

This commit is contained in:
SuperManTouX 2026-03-13 20:13:38 +08:00
parent dbef018fd1
commit 1fd5405f42
17 changed files with 447 additions and 361 deletions

View File

@ -1,3 +0,0 @@
{
"window.title": "${activeEditorShort}${separator}${separator}deer-flow/frontend"
}

View File

@ -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);

View File

@ -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,51 +116,40 @@ 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">
<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> </main>
</div> </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> </ChatBox>
</ThreadContext.Provider> </ThreadContext.Provider>
); );

View File

@ -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,
)} )}

View File

@ -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",
)} )}
> >

View File

@ -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 };

View File

@ -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>

View File

@ -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,221 +422,144 @@ export function InputBox({
defaultValue={initialValue} defaultValue={initialValue}
/> />
</PromptInputBody> </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> <PromptInputTools>
{/* TODO: Add more connectors here <AddAttachmentsButton className="px-2!" />
<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" && (
<PromptInputActionMenu> <PromptInputActionMenu>
<PromptInputActionMenuTrigger className="gap-1! px-2!"> <ModeHoverGuide
<div className="text-xs font-normal"> mode={
{t.inputBox.reasoningEffort}: context.mode === "flash" ||
{context.reasoning_effort === "minimal" && " " + t.inputBox.reasoningEffortMinimal} context.mode === "thinking" ||
{context.reasoning_effort === "low" && " " + t.inputBox.reasoningEffortLow} context.mode === "pro" ||
{context.reasoning_effort === "medium" && " " + t.inputBox.reasoningEffortMedium} context.mode === "ultra"
{context.reasoning_effort === "high" && " " + t.inputBox.reasoningEffortHigh} ? context.mode
</div> : "flash"
</PromptInputActionMenuTrigger> }
<PromptInputActionMenuContent className="w-70"> >
<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> <DropdownMenuGroup>
<DropdownMenuLabel className="text-muted-foreground text-xs"> <DropdownMenuLabel className="text-muted-foreground text-xs">
{t.inputBox.reasoningEffort} {t.inputBox.mode}
</DropdownMenuLabel> </DropdownMenuLabel>
<PromptInputActionMenu> <PromptInputActionMenu>
<PromptInputActionMenuItem <PromptInputActionMenuItem
className={cn( className={cn(
context.reasoning_effort === "minimal" context.mode === "flash"
? "text-accent-foreground" ? "text-accent-foreground"
: "text-muted-foreground/65", : "text-muted-foreground/65",
)} )}
onSelect={() => handleReasoningEffortSelect("minimal")} onSelect={() => handleModeSelect("flash")}
> >
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center gap-1 font-bold"> <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>
<div className="pl-2 text-xs"> <div className="pl-7 text-xs">
{t.inputBox.reasoningEffortMinimalDescription} {t.inputBox.flashModeDescription}
</div> </div>
</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" /> <CheckIcon className="ml-auto size-4" />
) : ( ) : (
<div className="ml-auto size-4" /> <div className="ml-auto size-4" />
@ -625,65 +567,33 @@ export function InputBox({
</PromptInputActionMenuItem> </PromptInputActionMenuItem>
<PromptInputActionMenuItem <PromptInputActionMenuItem
className={cn( className={cn(
context.reasoning_effort === "low" context.mode === "ultra"
? "text-accent-foreground" ? "text-accent-foreground"
: "text-muted-foreground/65", : "text-muted-foreground/65",
)} )}
onSelect={() => handleReasoningEffortSelect("low")} onSelect={() => handleModeSelect("ultra")}
> >
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center gap-1 font-bold"> <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>
<div className="pl-2 text-xs"> <div className="pl-7 text-xs">
{t.inputBox.reasoningEffortLowDescription} {t.inputBox.ultraModeDescription}
</div> </div>
</div> </div>
{context.reasoning_effort === "low" ? ( {context.mode === "ultra" ? (
<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" /> <CheckIcon className="ml-auto size-4" />
) : ( ) : (
<div className="ml-auto size-4" /> <div className="ml-auto size-4" />
@ -693,56 +603,165 @@ export function InputBox({
</DropdownMenuGroup> </DropdownMenuGroup>
</PromptInputActionMenuContent> </PromptInputActionMenuContent>
</PromptInputActionMenu> </PromptInputActionMenu>
)} {supportReasoningEffort && context.mode !== "flash" && (
</PromptInputTools> <PromptInputActionMenu>
<PromptInputTools> <PromptInputActionMenuTrigger className="gap-1! px-2!">
<ModelSelector <div className="text-xs font-normal">
open={modelDialogOpen} {t.inputBox.reasoningEffort}:
onOpenChange={setModelDialogOpen} {context.reasoning_effort === "minimal" && " " + t.inputBox.reasoningEffortMinimal}
> {context.reasoning_effort === "low" && " " + t.inputBox.reasoningEffortLow}
<ModelSelectorTrigger asChild> {context.reasoning_effort === "medium" && " " + t.inputBox.reasoningEffortMedium}
<PromptInputButton> {context.reasoning_effort === "high" && " " + t.inputBox.reasoningEffortHigh}
<ModelSelectorName className="text-xs font-normal"> </div>
{selectedModel?.display_name} </PromptInputActionMenuTrigger>
</ModelSelectorName> <PromptInputActionMenuContent className="w-70">
</PromptInputButton> <DropdownMenuGroup>
</ModelSelectorTrigger> <DropdownMenuLabel className="text-muted-foreground text-xs">
<ModelSelectorContent> {t.inputBox.reasoningEffort}
<ModelSelectorInput placeholder={t.inputBox.searchModels} /> </DropdownMenuLabel>
<ModelSelectorList> <PromptInputActionMenu>
{models.map((m) => ( <PromptInputActionMenuItem
<ModelSelectorItem className={cn(
key={m.name} context.reasoning_effort === "minimal"
value={m.name} ? "text-accent-foreground"
onSelect={() => handleModelSelect(m.name)} : "text-muted-foreground/65",
> )}
<ModelSelectorName>{m.display_name}</ModelSelectorName> onSelect={() => handleReasoningEffortSelect("minimal")}
{m.name === context.model_name ? ( >
<CheckIcon className="ml-auto size-4" /> <div className="flex flex-col gap-2">
) : ( <div className="flex items-center gap-1 font-bold">
<div className="ml-auto size-4" /> {t.inputBox.reasoningEffortMinimal}
)} </div>
</ModelSelectorItem> <div className="pl-2 text-xs">
))} {t.inputBox.reasoningEffortMinimalDescription}
</ModelSelectorList> </div>
</ModelSelectorContent> </div>
</ModelSelector> {context.reasoning_effort === "minimal" ? (
<PromptInputSubmit <CheckIcon className="ml-auto size-4" />
className="rounded-full" ) : (
disabled={disabled} <div className="ml-auto size-4" />
variant="outline" )}
status={status} </PromptInputActionMenuItem>
/> <PromptInputActionMenuItem
</PromptInputTools> className={cn(
</PromptInputFooter> context.reasoning_effort === "low"
{isNewThread && searchParams.get("mode") !== "skill" && ( ? "text-accent-foreground"
<div className="absolute right-0 -bottom-20 left-0 z-0 flex items-center justify-center"> : "text-muted-foreground/65",
<SuggestionList /> )}
</div> onSelect={() => handleReasoningEffortSelect("low")}
)} >
{!isNewThread && ( <div className="flex flex-col gap-2">
<div className="bg-background absolute right-0 -bottom-[17px] left-0 z-0 h-4"></div> <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> </PromptInput>
{!disabled && {!disabled &&

View File

@ -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>
); );

View File

@ -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 {

0
scripts/cleanup-containers.sh Executable file → Normal file
View File

0
scripts/docker.sh Executable file → Normal file
View File

0
skills/public/find-skills/scripts/install-skill.sh Executable file → Normal file
View File

0
skills/public/skill-creator/scripts/init_skill.py Executable file → Normal file
View File

0
skills/public/skill-creator/scripts/package_skill.py Executable file → Normal file
View File

0
skills/public/skill-creator/scripts/quick_validate.py Executable file → Normal file
View File

View File