mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-19 12:24:46 +08:00
feat: rename 'model' to 'model_name'
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { type HumanMessage } from "@langchain/core/messages";
|
import { type HumanMessage } from "@langchain/core/messages";
|
||||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
@@ -16,14 +15,12 @@ import {
|
|||||||
WorkspaceHeader,
|
WorkspaceHeader,
|
||||||
WorkspaceFooter,
|
WorkspaceFooter,
|
||||||
} from "@/components/workspace/workspace-container";
|
} from "@/components/workspace/workspace-container";
|
||||||
import { getLangGraphClient } from "@/core/api";
|
|
||||||
import { useLocalSettings } from "@/core/settings";
|
import { useLocalSettings } from "@/core/settings";
|
||||||
import type { AgentThread, AgentThreadState } from "@/core/threads";
|
import { type AgentThread } from "@/core/threads";
|
||||||
|
import { useThreadStream } from "@/core/threads/hooks";
|
||||||
import { titleOfThread } from "@/core/threads/utils";
|
import { titleOfThread } from "@/core/threads/utils";
|
||||||
import { uuid } from "@/core/utils/uuid";
|
import { uuid } from "@/core/utils/uuid";
|
||||||
|
|
||||||
const langGraphClient = getLangGraphClient();
|
|
||||||
|
|
||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -42,15 +39,9 @@ export default function ChatPage() {
|
|||||||
setThreadId(uuid());
|
setThreadId(uuid());
|
||||||
}
|
}
|
||||||
}, [threadIdFromPath]);
|
}, [threadIdFromPath]);
|
||||||
const thread = useStream<AgentThreadState>({
|
const thread = useThreadStream({
|
||||||
client: langGraphClient,
|
isNewThread,
|
||||||
assistantId: "lead_agent",
|
threadId,
|
||||||
threadId: !isNewThread ? threadId : undefined,
|
|
||||||
reconnectOnMount: true,
|
|
||||||
fetchStateHistory: true,
|
|
||||||
onFinish() {
|
|
||||||
void queryClient.invalidateQueries({ queryKey: ["threads", "search"] });
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
async (message: PromptInputMessage) => {
|
async (message: PromptInputMessage) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
return (
|
return (
|
||||||
@@ -11,11 +11,11 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|||||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Input }
|
export { Input };
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import type { ChatStatus } from "ai";
|
import type { ChatStatus } from "ai";
|
||||||
import { CheckIcon, LightbulbIcon, LightbulbOffIcon } from "lucide-react";
|
import {
|
||||||
|
BoxIcon,
|
||||||
|
CheckIcon,
|
||||||
|
LightbulbIcon,
|
||||||
|
LightbulbOffIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import { useCallback, useMemo, useState, type ComponentProps } from "react";
|
import { useCallback, useMemo, useState, type ComponentProps } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -20,10 +25,8 @@ import {
|
|||||||
ModelSelectorInput,
|
ModelSelectorInput,
|
||||||
ModelSelectorItem,
|
ModelSelectorItem,
|
||||||
ModelSelectorList,
|
ModelSelectorList,
|
||||||
ModelSelectorLogo,
|
|
||||||
ModelSelectorName,
|
ModelSelectorName,
|
||||||
ModelSelectorTrigger,
|
ModelSelectorTrigger,
|
||||||
type ModelSelectorLogoProps,
|
|
||||||
} from "../ai-elements/model-selector";
|
} from "../ai-elements/model-selector";
|
||||||
|
|
||||||
import { Tooltip } from "./tooltip";
|
import { Tooltip } from "./tooltip";
|
||||||
@@ -56,14 +59,14 @@ export function InputBox({
|
|||||||
}) {
|
}) {
|
||||||
const [modelDialogOpen, setModelDialogOpen] = useState(false);
|
const [modelDialogOpen, setModelDialogOpen] = useState(false);
|
||||||
const selectedModel = useMemo(
|
const selectedModel = useMemo(
|
||||||
() => AVAILABLE_MODELS.find((m) => m.name === context.model),
|
() => AVAILABLE_MODELS.find((m) => m.name === context.model_name),
|
||||||
[context.model],
|
[context.model_name],
|
||||||
);
|
);
|
||||||
const handleModelSelect = useCallback(
|
const handleModelSelect = useCallback(
|
||||||
(model: string) => {
|
(model_name: string) => {
|
||||||
onContextChange?.({
|
onContextChange?.({
|
||||||
...context,
|
...context,
|
||||||
model,
|
model_name,
|
||||||
});
|
});
|
||||||
setModelDialogOpen(false);
|
setModelDialogOpen(false);
|
||||||
},
|
},
|
||||||
@@ -140,13 +143,8 @@ export function InputBox({
|
|||||||
>
|
>
|
||||||
<ModelSelectorTrigger asChild>
|
<ModelSelectorTrigger asChild>
|
||||||
<PromptInputButton>
|
<PromptInputButton>
|
||||||
<ModelSelectorLogo
|
<BoxIcon className="size-4" />
|
||||||
className="size-4"
|
<ModelSelectorName className="font-light">
|
||||||
provider={
|
|
||||||
selectedModel?.provider as ModelSelectorLogoProps["provider"]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ModelSelectorName>
|
|
||||||
{selectedModel?.displayName}
|
{selectedModel?.displayName}
|
||||||
</ModelSelectorName>
|
</ModelSelectorName>
|
||||||
</PromptInputButton>
|
</PromptInputButton>
|
||||||
@@ -160,12 +158,8 @@ export function InputBox({
|
|||||||
value={m.name}
|
value={m.name}
|
||||||
onSelect={() => handleModelSelect(m.name)}
|
onSelect={() => handleModelSelect(m.name)}
|
||||||
>
|
>
|
||||||
<ModelSelectorLogo
|
|
||||||
className="size-4"
|
|
||||||
provider={m.provider}
|
|
||||||
/>
|
|
||||||
<ModelSelectorName>{m.displayName}</ModelSelectorName>
|
<ModelSelectorName>{m.displayName}</ModelSelectorName>
|
||||||
{m.name === context.model ? (
|
{m.name === context.model_name ? (
|
||||||
<CheckIcon className="ml-auto size-4" />
|
<CheckIcon className="ml-auto size-4" />
|
||||||
) : (
|
) : (
|
||||||
<div className="ml-auto size-4" />
|
<div className="ml-auto size-4" />
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { useDeleteThread, useThreads } from "@/core/api";
|
import { useDeleteThread, useThreads } from "@/core/threads/hooks";
|
||||||
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
||||||
|
|
||||||
export function RecentChatList() {
|
export function RecentChatList() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { Client as LangGraphClient } from "@langchain/langgraph-sdk/client";
|
import { Client as LangGraphClient } from "@langchain/langgraph-sdk/client";
|
||||||
|
|
||||||
let _singleton: LangGraphClient | null = null;
|
let _singleton: LangGraphClient | null = null;
|
||||||
export function getLangGraphClient(): LangGraphClient {
|
export function getAPIClient(): LangGraphClient {
|
||||||
let url: URL | null = null;
|
let url: URL | null = null;
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
url = new URL("/api/langgraph", "http://localhost:3000");
|
url = new URL("/api/langgraph", "http://localhost:3000");
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import type { ThreadsClient } from "@langchain/langgraph-sdk/client";
|
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
import type { AgentThread, AgentThreadState } from "../threads";
|
|
||||||
|
|
||||||
import { getLangGraphClient } from "./client";
|
|
||||||
|
|
||||||
export function useThreads(
|
|
||||||
params: Parameters<ThreadsClient["search"]>[0] = {
|
|
||||||
limit: 50,
|
|
||||||
sortBy: "updated_at",
|
|
||||||
sortOrder: "desc",
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
const langGraphClient = getLangGraphClient();
|
|
||||||
return useQuery<AgentThread[]>({
|
|
||||||
queryKey: ["threads", "search", params],
|
|
||||||
queryFn: async () => {
|
|
||||||
const response =
|
|
||||||
await langGraphClient.threads.search<AgentThreadState>(params);
|
|
||||||
return response as AgentThread[];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDeleteThread() {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const langGraphClient = getLangGraphClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async ({ threadId }: { threadId: string }) => {
|
|
||||||
await langGraphClient.threads.delete(threadId);
|
|
||||||
},
|
|
||||||
onSuccess(_, { threadId }) {
|
|
||||||
queryClient.setQueriesData(
|
|
||||||
{
|
|
||||||
queryKey: ["threads", "search"],
|
|
||||||
exact: false,
|
|
||||||
},
|
|
||||||
(oldData: Array<AgentThread>) => {
|
|
||||||
return oldData.filter((t) => t.thread_id !== threadId);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
export * from "./client";
|
export * from "./api-client";
|
||||||
export * from "./hooks";
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { AgentThreadContext } from "../threads";
|
|||||||
|
|
||||||
export const DEFAULT_LOCAL_SETTINGS: LocalSettings = {
|
export const DEFAULT_LOCAL_SETTINGS: LocalSettings = {
|
||||||
context: {
|
context: {
|
||||||
model: "deepseek-v3.2",
|
model_name: "deepseek-v3.2",
|
||||||
thinking_enabled: true,
|
thinking_enabled: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
119
frontend/src/core/threads/hooks.ts
Normal file
119
frontend/src/core/threads/hooks.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import type { HumanMessage } from "@langchain/core/messages";
|
||||||
|
import type { ThreadsClient } from "@langchain/langgraph-sdk/client";
|
||||||
|
import { useStream, type UseStream } from "@langchain/langgraph-sdk/react";
|
||||||
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
import type { PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
||||||
|
|
||||||
|
import { getAPIClient } from "../api";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
AgentThread,
|
||||||
|
AgentThreadContext,
|
||||||
|
AgentThreadState,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export function useThreadStream({
|
||||||
|
threadId,
|
||||||
|
isNewThread,
|
||||||
|
}: {
|
||||||
|
isNewThread: boolean;
|
||||||
|
threadId: string | null | undefined;
|
||||||
|
}) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useStream<AgentThreadState>({
|
||||||
|
client: getAPIClient(),
|
||||||
|
assistantId: "lead_agent",
|
||||||
|
threadId: isNewThread ? undefined : threadId,
|
||||||
|
reconnectOnMount: true,
|
||||||
|
fetchStateHistory: true,
|
||||||
|
onFinish() {
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ["threads", "search"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSubmitThread({
|
||||||
|
threadId,
|
||||||
|
thread,
|
||||||
|
threadContext,
|
||||||
|
isNewThread,
|
||||||
|
message,
|
||||||
|
}: {
|
||||||
|
isNewThread: boolean;
|
||||||
|
threadId: string;
|
||||||
|
thread: UseStream<AgentThreadState>;
|
||||||
|
threadContext: AgentThreadContext;
|
||||||
|
message: PromptInputMessage;
|
||||||
|
}) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const text = message.text.trim();
|
||||||
|
const callback = useCallback(async () => {
|
||||||
|
await thread.submit(
|
||||||
|
{
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
type: "human",
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as HumanMessage[],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threadId: isNewThread ? threadId : undefined,
|
||||||
|
streamSubgraphs: true,
|
||||||
|
streamResumable: true,
|
||||||
|
context: {
|
||||||
|
...threadContext,
|
||||||
|
thread_id: threadId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ["threads", "search"] });
|
||||||
|
}, [queryClient, thread, threadContext, threadId, isNewThread, text]);
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useThreads(
|
||||||
|
params: Parameters<ThreadsClient["search"]>[0] = {
|
||||||
|
limit: 50,
|
||||||
|
sortBy: "updated_at",
|
||||||
|
sortOrder: "desc",
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const langGraphClient = getAPIClient();
|
||||||
|
return useQuery<AgentThread[]>({
|
||||||
|
queryKey: ["threads", "search", params],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response =
|
||||||
|
await langGraphClient.threads.search<AgentThreadState>(params);
|
||||||
|
return response as AgentThread[];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDeleteThread() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const langGraphClient = getAPIClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({ threadId }: { threadId: string }) => {
|
||||||
|
await langGraphClient.threads.delete(threadId);
|
||||||
|
},
|
||||||
|
onSuccess(_, { threadId }) {
|
||||||
|
queryClient.setQueriesData(
|
||||||
|
{
|
||||||
|
queryKey: ["threads", "search"],
|
||||||
|
exact: false,
|
||||||
|
},
|
||||||
|
(oldData: Array<AgentThread>) => {
|
||||||
|
return oldData.filter((t) => t.thread_id !== threadId);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -10,6 +10,6 @@ export interface AgentThread extends Thread<AgentThreadState> {}
|
|||||||
|
|
||||||
export interface AgentThreadContext extends Record<string, unknown> {
|
export interface AgentThreadContext extends Record<string, unknown> {
|
||||||
thread_id: string;
|
thread_id: string;
|
||||||
model: string;
|
model_name: string;
|
||||||
thinking_enabled: boolean;
|
thinking_enabled: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user