mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-19 04:14:46 +08:00
feat: put all options into '+'
This commit is contained in:
@@ -30,12 +30,7 @@ export default async function RootLayout({
|
|||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
>
|
>
|
||||||
<body>
|
<body>
|
||||||
<ThemeProvider
|
<ThemeProvider attribute="class" enableSystem disableTransitionOnChange>
|
||||||
attribute="class"
|
|
||||||
defaultTheme="dark"
|
|
||||||
enableSystem
|
|
||||||
disableTransitionOnChange
|
|
||||||
>
|
|
||||||
<I18nProvider initialLocale={locale}>{children}</I18nProvider>
|
<I18nProvider initialLocale={locale}>{children}</I18nProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -147,21 +147,12 @@ export default function ChatPage() {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative w-full",
|
"relative w-full",
|
||||||
isNewThread && "-translate-y-[calc(50vh-120px)]",
|
isNewThread && "-translate-y-[calc(50vh-160px)]",
|
||||||
isNewThread
|
isNewThread
|
||||||
? "max-w-(--container-width-sm)"
|
? "max-w-(--container-width-sm)"
|
||||||
: "max-w-(--container-width-md)",
|
: "max-w-(--container-width-md)",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isNewThread && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"absolute right-0 bottom-[136px] left-0 flex",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Welcome />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="absolute -top-4 right-0 left-0 z-0">
|
<div className="absolute -top-4 right-0 left-0 z-0">
|
||||||
<div className="absolute right-0 bottom-0 left-0">
|
<div className="absolute right-0 bottom-0 left-0">
|
||||||
<TodoList
|
<TodoList
|
||||||
@@ -183,6 +174,7 @@ export default function ChatPage() {
|
|||||||
autoFocus={isNewThread}
|
autoFocus={isNewThread}
|
||||||
status={thread.isLoading ? "streaming" : "ready"}
|
status={thread.isLoading ? "streaming" : "ready"}
|
||||||
context={settings.context}
|
context={settings.context}
|
||||||
|
extraHeader={isNewThread && <Welcome />}
|
||||||
onContextChange={(context) =>
|
onContextChange={(context) =>
|
||||||
setSettings("context", context)
|
setSettings("context", context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
PaperclipIcon,
|
PaperclipIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
SquareIcon,
|
SquareIcon,
|
||||||
|
UploadIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
@@ -422,7 +423,7 @@ export const PromptInputActionAddAttachments = ({
|
|||||||
attachments.openFileDialog();
|
attachments.openFileDialog();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ImageIcon className="mr-2 size-4" /> {label}
|
<PaperclipIcon className="mr-2 size-4" /> {label}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,36 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { ChatStatus } from "ai";
|
import type { ChatStatus } from "ai";
|
||||||
import { CheckIcon, LightbulbIcon, ListTodoIcon } from "lucide-react";
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
GraduationCapIcon,
|
||||||
|
LightbulbIcon,
|
||||||
|
ZapIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import { useCallback, useMemo, useState, type ComponentProps } from "react";
|
import { useCallback, useMemo, useState, type ComponentProps } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PromptInput,
|
PromptInput,
|
||||||
|
PromptInputActionAddAttachments,
|
||||||
|
PromptInputActionMenu,
|
||||||
|
PromptInputActionMenuContent,
|
||||||
|
PromptInputActionMenuItem,
|
||||||
|
PromptInputActionMenuTrigger,
|
||||||
|
PromptInputAttachment,
|
||||||
|
PromptInputAttachments,
|
||||||
PromptInputBody,
|
PromptInputBody,
|
||||||
PromptInputButton,
|
PromptInputButton,
|
||||||
PromptInputFooter,
|
PromptInputFooter,
|
||||||
PromptInputSubmit,
|
PromptInputSubmit,
|
||||||
PromptInputTextarea,
|
PromptInputTextarea,
|
||||||
|
PromptInputTools,
|
||||||
type PromptInputMessage,
|
type PromptInputMessage,
|
||||||
} from "@/components/ai-elements/prompt-input";
|
} from "@/components/ai-elements/prompt-input";
|
||||||
|
import {
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { useModels } from "@/core/models/hooks";
|
import { useModels } from "@/core/models/hooks";
|
||||||
import type { AgentThreadContext } from "@/core/threads";
|
import type { AgentThreadContext } from "@/core/threads";
|
||||||
@@ -28,13 +46,12 @@ import {
|
|||||||
ModelSelectorTrigger,
|
ModelSelectorTrigger,
|
||||||
} from "../ai-elements/model-selector";
|
} from "../ai-elements/model-selector";
|
||||||
|
|
||||||
import { Tooltip } from "./tooltip";
|
|
||||||
|
|
||||||
export function InputBox({
|
export function InputBox({
|
||||||
className,
|
className,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
status = "ready",
|
status = "ready",
|
||||||
context,
|
context,
|
||||||
|
extraHeader,
|
||||||
onContextChange,
|
onContextChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onStop,
|
onStop,
|
||||||
@@ -55,6 +72,19 @@ export function InputBox({
|
|||||||
() => models.find((m) => m.name === context.model_name),
|
() => models.find((m) => m.name === context.model_name),
|
||||||
[context.model_name, models],
|
[context.model_name, models],
|
||||||
);
|
);
|
||||||
|
const supportThinking = useMemo(
|
||||||
|
() => selectedModel?.supports_thinking ?? false,
|
||||||
|
[selectedModel],
|
||||||
|
);
|
||||||
|
const mode = useMemo(() => {
|
||||||
|
if (context.is_plan_mode) {
|
||||||
|
return "pro";
|
||||||
|
}
|
||||||
|
if (context.thinking_enabled) {
|
||||||
|
return "thinking";
|
||||||
|
}
|
||||||
|
return "flash";
|
||||||
|
}, [context.thinking_enabled, context.is_plan_mode]);
|
||||||
const handleModelSelect = useCallback(
|
const handleModelSelect = useCallback(
|
||||||
(model_name: string) => {
|
(model_name: string) => {
|
||||||
const supports_thinking = selectedModel?.supports_thinking ?? false;
|
const supports_thinking = selectedModel?.supports_thinking ?? false;
|
||||||
@@ -67,18 +97,30 @@ export function InputBox({
|
|||||||
},
|
},
|
||||||
[selectedModel?.supports_thinking, onContextChange, context],
|
[selectedModel?.supports_thinking, onContextChange, context],
|
||||||
);
|
);
|
||||||
const handleThinkingToggle = useCallback(() => {
|
const handleModeSelect = useCallback(
|
||||||
onContextChange?.({
|
(mode: "flash" | "thinking" | "pro") => {
|
||||||
...context,
|
if (mode === "flash") {
|
||||||
thinking_enabled: !context.thinking_enabled,
|
onContextChange?.({
|
||||||
});
|
...context,
|
||||||
}, [onContextChange, context]);
|
thinking_enabled: false,
|
||||||
const handlePlanModeToggle = useCallback(() => {
|
is_plan_mode: false,
|
||||||
onContextChange?.({
|
});
|
||||||
...context,
|
} else if (mode === "thinking") {
|
||||||
is_plan_mode: !context.is_plan_mode,
|
onContextChange?.({
|
||||||
});
|
...context,
|
||||||
}, [onContextChange, context]);
|
thinking_enabled: true,
|
||||||
|
is_plan_mode: false,
|
||||||
|
});
|
||||||
|
} else if (mode === "pro") {
|
||||||
|
onContextChange?.({
|
||||||
|
...context,
|
||||||
|
thinking_enabled: true,
|
||||||
|
is_plan_mode: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onContextChange, context],
|
||||||
|
);
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
async (message: PromptInputMessage) => {
|
async (message: PromptInputMessage) => {
|
||||||
if (status === "streaming") {
|
if (status === "streaming") {
|
||||||
@@ -103,6 +145,16 @@ export function InputBox({
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
{extraHeader && (
|
||||||
|
<div className="absolute top-0 right-0 left-0 z-10">
|
||||||
|
<div className="absolute right-0 bottom-0 left-0 flex items-center justify-center">
|
||||||
|
{extraHeader}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<PromptInputAttachments>
|
||||||
|
{(attachment) => <PromptInputAttachment data={attachment} />}
|
||||||
|
</PromptInputAttachments>
|
||||||
<PromptInputBody className="absolute top-0 right-0 left-0 z-3">
|
<PromptInputBody className="absolute top-0 right-0 left-0 z-3">
|
||||||
<PromptInputTextarea
|
<PromptInputTextarea
|
||||||
className={cn("size-full")}
|
className={cn("size-full")}
|
||||||
@@ -111,91 +163,111 @@ export function InputBox({
|
|||||||
/>
|
/>
|
||||||
</PromptInputBody>
|
</PromptInputBody>
|
||||||
<PromptInputFooter className="flex">
|
<PromptInputFooter className="flex">
|
||||||
<div className="flex items-center">
|
<PromptInputTools>
|
||||||
<Tooltip
|
<PromptInputActionMenu>
|
||||||
content={
|
<PromptInputActionMenuTrigger />
|
||||||
context.thinking_enabled ? (
|
<PromptInputActionMenuContent className="w-80">
|
||||||
<div className="tex-sm flex flex-col gap-1">
|
<PromptInputActionAddAttachments
|
||||||
<div>{t.inputBox.thinkingEnabled}</div>
|
label={t.inputBox.addAttachments}
|
||||||
<div className="opacity-50">
|
/>
|
||||||
{t.inputBox.clickToDisableThinking}
|
<DropdownMenuSeparator />
|
||||||
</div>
|
<DropdownMenuGroup>
|
||||||
</div>
|
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
||||||
) : (
|
{t.inputBox.mode}
|
||||||
<div className="tex-sm flex flex-col gap-1">
|
</DropdownMenuLabel>
|
||||||
<div>{t.inputBox.thinkingDisabled}</div>
|
<PromptInputActionMenu>
|
||||||
<div className="opacity-50">
|
<PromptInputActionMenuItem
|
||||||
{t.inputBox.clickToEnableThinking}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{selectedModel?.supports_thinking && (
|
|
||||||
<PromptInputButton onClick={handleThinkingToggle}>
|
|
||||||
<>
|
|
||||||
{context.thinking_enabled ? (
|
|
||||||
<LightbulbIcon className="text-primary size-4" />
|
|
||||||
) : (
|
|
||||||
<LightbulbIcon className="size-4" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-xs font-normal",
|
mode === "flash"
|
||||||
context.thinking_enabled
|
? "text-accent-foreground"
|
||||||
? "text-primary"
|
: "text-muted-foreground/65",
|
||||||
: "text-muted-foreground",
|
|
||||||
)}
|
)}
|
||||||
|
onSelect={() => handleModeSelect("flash")}
|
||||||
>
|
>
|
||||||
{t.inputBox.thinking}
|
<div className="flex flex-col gap-2">
|
||||||
</span>
|
<div className="flex items-center gap-1 font-bold">
|
||||||
</>
|
<ZapIcon
|
||||||
</PromptInputButton>
|
className={cn(
|
||||||
)}
|
"mr-2 size-4",
|
||||||
</Tooltip>
|
mode === "flash" && "text-accent-foreground",
|
||||||
<Tooltip
|
)}
|
||||||
content={
|
/>
|
||||||
context.is_plan_mode ? (
|
{t.inputBox.flashMode}
|
||||||
<div className="tex-sm flex flex-col gap-1">
|
</div>
|
||||||
<div>{t.inputBox.planMode}</div>
|
<div className="pl-7 text-xs">
|
||||||
<div className="opacity-50">
|
{t.inputBox.flashModeDescription}
|
||||||
{t.inputBox.clickToDisablePlanMode}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{mode === "flash" ? (
|
||||||
) : (
|
<CheckIcon className="ml-auto size-4" />
|
||||||
<div className="tex-sm flex flex-col gap-1">
|
) : (
|
||||||
<div>{t.inputBox.planMode}</div>
|
<div className="ml-auto size-4" />
|
||||||
<div className="opacity-50">
|
)}
|
||||||
{t.inputBox.clickToEnablePlanMode}
|
</PromptInputActionMenuItem>
|
||||||
</div>
|
{supportThinking && (
|
||||||
</div>
|
<PromptInputActionMenuItem
|
||||||
)
|
className={cn(
|
||||||
}
|
mode === "thinking"
|
||||||
>
|
? "text-accent-foreground"
|
||||||
{selectedModel?.supports_thinking && (
|
: "text-muted-foreground/65",
|
||||||
<PromptInputButton onClick={handlePlanModeToggle}>
|
)}
|
||||||
<>
|
onSelect={() => handleModeSelect("thinking")}
|
||||||
{context.is_plan_mode ? (
|
>
|
||||||
<ListTodoIcon className="text-primary size-4" />
|
<div className="flex flex-col gap-2">
|
||||||
) : (
|
<div className="flex items-center gap-1 font-bold">
|
||||||
<ListTodoIcon className="size-4" />
|
<LightbulbIcon
|
||||||
|
className={cn(
|
||||||
|
"mr-2 size-4",
|
||||||
|
mode === "thinking" && "text-accent-foreground",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{t.inputBox.reasoningMode}
|
||||||
|
</div>
|
||||||
|
<div className="pl-7 text-xs">
|
||||||
|
{t.inputBox.reasoningModeDescription}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{mode === "thinking" ? (
|
||||||
|
<CheckIcon className="ml-auto size-4" />
|
||||||
|
) : (
|
||||||
|
<div className="ml-auto size-4" />
|
||||||
|
)}
|
||||||
|
</PromptInputActionMenuItem>
|
||||||
)}
|
)}
|
||||||
<span
|
<PromptInputActionMenuItem
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-xs font-normal",
|
mode === "pro"
|
||||||
context.is_plan_mode
|
? "text-accent-foreground"
|
||||||
? "text-primary"
|
: "text-muted-foreground/65",
|
||||||
: "text-muted-foreground",
|
|
||||||
)}
|
)}
|
||||||
|
onSelect={() => handleModeSelect("pro")}
|
||||||
>
|
>
|
||||||
{t.inputBox.planMode}
|
<div className="flex flex-col gap-2">
|
||||||
</span>
|
<div className="flex items-center gap-1 font-bold">
|
||||||
</>
|
<GraduationCapIcon
|
||||||
</PromptInputButton>
|
className={cn(
|
||||||
)}
|
"mr-2 size-4",
|
||||||
</Tooltip>
|
mode === "pro" && "text-accent-foreground",
|
||||||
</div>
|
)}
|
||||||
<div className="flex items-center gap-2">
|
/>
|
||||||
|
{t.inputBox.proMode}
|
||||||
|
</div>
|
||||||
|
<div className="pl-7 text-xs">
|
||||||
|
{t.inputBox.proModeDescription}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{mode === "pro" ? (
|
||||||
|
<CheckIcon className="ml-auto size-4" />
|
||||||
|
) : (
|
||||||
|
<div className="ml-auto size-4" />
|
||||||
|
)}
|
||||||
|
</PromptInputActionMenuItem>
|
||||||
|
</PromptInputActionMenu>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
</PromptInputActionMenuContent>
|
||||||
|
</PromptInputActionMenu>
|
||||||
|
</PromptInputTools>
|
||||||
|
<PromptInputTools>
|
||||||
<ModelSelector
|
<ModelSelector
|
||||||
open={modelDialogOpen}
|
open={modelDialogOpen}
|
||||||
onOpenChange={setModelDialogOpen}
|
onOpenChange={setModelDialogOpen}
|
||||||
@@ -232,7 +304,7 @@ export function InputBox({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
status={status}
|
status={status}
|
||||||
/>
|
/>
|
||||||
</div>
|
</PromptInputTools>
|
||||||
</PromptInputFooter>
|
</PromptInputFooter>
|
||||||
</PromptInput>
|
</PromptInput>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -39,16 +39,16 @@ export const enUS: Translations = {
|
|||||||
// Input Box
|
// Input Box
|
||||||
inputBox: {
|
inputBox: {
|
||||||
placeholder: "How can I assist you today?",
|
placeholder: "How can I assist you today?",
|
||||||
thinking: "Thinking",
|
addAttachments: "Add attachments",
|
||||||
thinkingEnabled: "Thinking is enabled",
|
mode: "Mode",
|
||||||
thinkingDisabled: "Thinking is disabled",
|
flashMode: "Flash",
|
||||||
clickToDisableThinking: "Click to disable thinking",
|
flashModeDescription: "Fast and efficient",
|
||||||
clickToEnableThinking: "Click to enable thinking",
|
reasoningMode: "Reasoning",
|
||||||
planMode: "Plan mode",
|
reasoningModeDescription:
|
||||||
planModeEnabled: "Plan mode is enabled",
|
"Reasoning before action, balance between time and accuracy",
|
||||||
planModeDisabled: "Plan mode is disabled",
|
proMode: "Pro",
|
||||||
clickToDisablePlanMode: "Click to disable plan mode",
|
proModeDescription:
|
||||||
clickToEnablePlanMode: "Click to enable plan mode",
|
"Reasoning, planning and executing, get more accurate results, may take more time",
|
||||||
searchModels: "Search models...",
|
searchModels: "Search models...",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -36,16 +36,14 @@ export interface Translations {
|
|||||||
// Input Box
|
// Input Box
|
||||||
inputBox: {
|
inputBox: {
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
thinking: string;
|
addAttachments: string;
|
||||||
thinkingEnabled: string;
|
mode: string;
|
||||||
thinkingDisabled: string;
|
flashMode: string;
|
||||||
clickToDisableThinking: string;
|
flashModeDescription: string;
|
||||||
clickToEnableThinking: string;
|
reasoningMode: string;
|
||||||
planMode: string;
|
reasoningModeDescription: string;
|
||||||
planModeEnabled: string;
|
proMode: string;
|
||||||
planModeDisabled: string;
|
proModeDescription: string;
|
||||||
clickToDisablePlanMode: string;
|
|
||||||
clickToEnablePlanMode: string;
|
|
||||||
searchModels: string;
|
searchModels: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -39,16 +39,14 @@ export const zhCN: Translations = {
|
|||||||
// Input Box
|
// Input Box
|
||||||
inputBox: {
|
inputBox: {
|
||||||
placeholder: "今天我能为你做些什么?",
|
placeholder: "今天我能为你做些什么?",
|
||||||
thinking: "思考",
|
addAttachments: "添加附件",
|
||||||
thinkingEnabled: "思考功能已启用",
|
mode: "模式",
|
||||||
thinkingDisabled: "思考功能已禁用",
|
flashMode: "闪速",
|
||||||
clickToDisableThinking: "点击禁用思考功能",
|
flashModeDescription: "快速且高效的完成任务,但可能不够精准",
|
||||||
clickToEnableThinking: "点击启用思考功能",
|
reasoningMode: "思考",
|
||||||
planMode: "To-do 模式",
|
reasoningModeDescription: "思考后再行动,在时间与准确性之间取得平衡",
|
||||||
planModeEnabled: "To-do 模式已启用",
|
proMode: "专业",
|
||||||
planModeDisabled: "To-do 模式已禁用",
|
proModeDescription: "思考、计划再执行,获得更精准的结果,可能需要更多时间",
|
||||||
clickToDisablePlanMode: "点击禁用 To-do 模式",
|
|
||||||
clickToEnablePlanMode: "点击启用 To-do 模式",
|
|
||||||
searchModels: "搜索模型...",
|
searchModels: "搜索模型...",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user