mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-22 21:54:45 +08:00
feat: support dynamic loading models
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
PromptInputTextarea,
|
PromptInputTextarea,
|
||||||
type PromptInputMessage,
|
type PromptInputMessage,
|
||||||
} from "@/components/ai-elements/prompt-input";
|
} from "@/components/ai-elements/prompt-input";
|
||||||
|
import { useModels } from "@/core/models/hooks";
|
||||||
import type { AgentThreadContext } from "@/core/threads";
|
import type { AgentThreadContext } from "@/core/threads";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -26,15 +27,6 @@ import {
|
|||||||
|
|
||||||
import { Tooltip } from "./tooltip";
|
import { Tooltip } from "./tooltip";
|
||||||
|
|
||||||
const AVAILABLE_MODELS = [
|
|
||||||
{ name: "deepseek-v3.2", displayName: "DeepSeek v3.2", provider: "deepseek" },
|
|
||||||
{
|
|
||||||
name: "doubao-seed-1.8",
|
|
||||||
displayName: "Doubao Seed 1.8",
|
|
||||||
provider: "doubao",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function InputBox({
|
export function InputBox({
|
||||||
className,
|
className,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
@@ -54,19 +46,22 @@ export function InputBox({
|
|||||||
onStop?: () => void;
|
onStop?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [modelDialogOpen, setModelDialogOpen] = useState(false);
|
const [modelDialogOpen, setModelDialogOpen] = useState(false);
|
||||||
|
const { models } = useModels();
|
||||||
const selectedModel = useMemo(
|
const selectedModel = useMemo(
|
||||||
() => AVAILABLE_MODELS.find((m) => m.name === context.model_name),
|
() => models.find((m) => m.name === context.model_name),
|
||||||
[context.model_name],
|
[context.model_name, models],
|
||||||
);
|
);
|
||||||
const handleModelSelect = useCallback(
|
const handleModelSelect = useCallback(
|
||||||
(model_name: string) => {
|
(model_name: string) => {
|
||||||
|
const supports_thinking = selectedModel?.supports_thinking ?? false;
|
||||||
onContextChange?.({
|
onContextChange?.({
|
||||||
...context,
|
...context,
|
||||||
model_name,
|
model_name,
|
||||||
|
thinking_enabled: supports_thinking && context.thinking_enabled,
|
||||||
});
|
});
|
||||||
setModelDialogOpen(false);
|
setModelDialogOpen(false);
|
||||||
},
|
},
|
||||||
[onContextChange, context],
|
[selectedModel?.supports_thinking, onContextChange, context],
|
||||||
);
|
);
|
||||||
const handleThinkingToggle = useCallback(() => {
|
const handleThinkingToggle = useCallback(() => {
|
||||||
onContextChange?.({
|
onContextChange?.({
|
||||||
@@ -90,7 +85,7 @@ export function InputBox({
|
|||||||
return (
|
return (
|
||||||
<PromptInput
|
<PromptInput
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background/50 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",
|
||||||
"h-48 translate-y-14 overflow-hidden",
|
"h-48 translate-y-14 overflow-hidden",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -123,6 +118,7 @@ export function InputBox({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{selectedModel?.supports_thinking && (
|
||||||
<PromptInputButton onClick={handleThinkingToggle}>
|
<PromptInputButton onClick={handleThinkingToggle}>
|
||||||
{context.thinking_enabled ? (
|
{context.thinking_enabled ? (
|
||||||
<LightbulbIcon className="text-primary size-4" />
|
<LightbulbIcon className="text-primary size-4" />
|
||||||
@@ -130,6 +126,7 @@ export function InputBox({
|
|||||||
<LightbulbOffIcon className="size-4" />
|
<LightbulbOffIcon className="size-4" />
|
||||||
)}
|
)}
|
||||||
</PromptInputButton>
|
</PromptInputButton>
|
||||||
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -140,20 +137,20 @@ export function InputBox({
|
|||||||
<ModelSelectorTrigger asChild>
|
<ModelSelectorTrigger asChild>
|
||||||
<PromptInputButton>
|
<PromptInputButton>
|
||||||
<ModelSelectorName className="text-xs font-normal">
|
<ModelSelectorName className="text-xs font-normal">
|
||||||
{selectedModel?.displayName}
|
{selectedModel?.display_name}
|
||||||
</ModelSelectorName>
|
</ModelSelectorName>
|
||||||
</PromptInputButton>
|
</PromptInputButton>
|
||||||
</ModelSelectorTrigger>
|
</ModelSelectorTrigger>
|
||||||
<ModelSelectorContent>
|
<ModelSelectorContent>
|
||||||
<ModelSelectorInput placeholder="Search models..." />
|
<ModelSelectorInput placeholder="Search models..." />
|
||||||
<ModelSelectorList>
|
<ModelSelectorList>
|
||||||
{AVAILABLE_MODELS.map((m) => (
|
{models.map((m) => (
|
||||||
<ModelSelectorItem
|
<ModelSelectorItem
|
||||||
key={m.name}
|
key={m.name}
|
||||||
value={m.name}
|
value={m.name}
|
||||||
onSelect={() => handleModelSelect(m.name)}
|
onSelect={() => handleModelSelect(m.name)}
|
||||||
>
|
>
|
||||||
<ModelSelectorName>{m.displayName}</ModelSelectorName>
|
<ModelSelectorName>{m.display_name}</ModelSelectorName>
|
||||||
{m.name === context.model_name ? (
|
{m.name === context.model_name ? (
|
||||||
<CheckIcon className="ml-auto size-4" />
|
<CheckIcon className="ml-auto size-4" />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
9
frontend/src/core/models/api.ts
Normal file
9
frontend/src/core/models/api.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { getBackendBaseURL } from "../config";
|
||||||
|
|
||||||
|
import type { Model } from "./types";
|
||||||
|
|
||||||
|
export async function loadModels() {
|
||||||
|
const res = fetch(`${getBackendBaseURL()}/api/models`);
|
||||||
|
const { models } = (await (await res).json()) as { models: Model[] };
|
||||||
|
return models;
|
||||||
|
}
|
||||||
11
frontend/src/core/models/hooks.ts
Normal file
11
frontend/src/core/models/hooks.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { loadModels } from "./api";
|
||||||
|
|
||||||
|
export function useModels() {
|
||||||
|
const { data, isLoading, error } = useQuery({
|
||||||
|
queryKey: ["models"],
|
||||||
|
queryFn: () => loadModels(),
|
||||||
|
});
|
||||||
|
return { models: data ?? [], isLoading, error };
|
||||||
|
}
|
||||||
2
frontend/src/core/models/index.ts
Normal file
2
frontend/src/core/models/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./api";
|
||||||
|
export * from "./types";
|
||||||
7
frontend/src/core/models/types.ts
Normal file
7
frontend/src/core/models/types.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export interface Model {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
display_name: string;
|
||||||
|
description?: string | null;
|
||||||
|
supports_thinking?: boolean;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user