From ac075477a0b10f52cf71516db2252691b05e1bfa Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 16 Jan 2026 14:03:34 +0800 Subject: [PATCH] feat: rename 'model' to 'model_name' --- .../app/workspace/chats/[thread_id]/page.tsx | 19 +-- frontend/src/components/ui/input.tsx | 10 +- .../src/components/workspace/input-box.tsx | 32 ++--- .../components/workspace/recent-chat-list.tsx | 2 +- .../src/core/api/{client.ts => api-client.ts} | 2 +- frontend/src/core/api/hooks.ts | 45 ------- frontend/src/core/api/index.ts | 3 +- frontend/src/core/settings/local.ts | 2 +- frontend/src/core/threads/hooks.ts | 119 ++++++++++++++++++ frontend/src/core/threads/types.ts | 2 +- 10 files changed, 147 insertions(+), 89 deletions(-) rename frontend/src/core/api/{client.ts => api-client.ts} (88%) delete mode 100644 frontend/src/core/api/hooks.ts create mode 100644 frontend/src/core/threads/hooks.ts diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 233b80b..2970ac4 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -1,7 +1,6 @@ "use client"; import { type HumanMessage } from "@langchain/core/messages"; -import { useStream } from "@langchain/langgraph-sdk/react"; import { useQueryClient } from "@tanstack/react-query"; import { useParams, useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -16,14 +15,12 @@ import { WorkspaceHeader, WorkspaceFooter, } from "@/components/workspace/workspace-container"; -import { getLangGraphClient } from "@/core/api"; 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 { uuid } from "@/core/utils/uuid"; -const langGraphClient = getLangGraphClient(); - export default function ChatPage() { const router = useRouter(); const queryClient = useQueryClient(); @@ -42,15 +39,9 @@ export default function ChatPage() { setThreadId(uuid()); } }, [threadIdFromPath]); - const thread = useStream({ - client: langGraphClient, - assistantId: "lead_agent", - threadId: !isNewThread ? threadId : undefined, - reconnectOnMount: true, - fetchStateHistory: true, - onFinish() { - void queryClient.invalidateQueries({ queryKey: ["threads", "search"] }); - }, + const thread = useThreadStream({ + isNewThread, + threadId, }); const handleSubmit = useCallback( async (message: PromptInputMessage) => { diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx index 8916905..f16c2c0 100644 --- a/frontend/src/components/ui/input.tsx +++ b/frontend/src/components/ui/input.tsx @@ -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">) { 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", "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", - className + className, )} {...props} /> - ) + ); } -export { Input } +export { Input }; diff --git a/frontend/src/components/workspace/input-box.tsx b/frontend/src/components/workspace/input-box.tsx index 34189e7..eddadba 100644 --- a/frontend/src/components/workspace/input-box.tsx +++ b/frontend/src/components/workspace/input-box.tsx @@ -1,5 +1,10 @@ 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 { @@ -20,10 +25,8 @@ import { ModelSelectorInput, ModelSelectorItem, ModelSelectorList, - ModelSelectorLogo, ModelSelectorName, ModelSelectorTrigger, - type ModelSelectorLogoProps, } from "../ai-elements/model-selector"; import { Tooltip } from "./tooltip"; @@ -56,14 +59,14 @@ export function InputBox({ }) { const [modelDialogOpen, setModelDialogOpen] = useState(false); const selectedModel = useMemo( - () => AVAILABLE_MODELS.find((m) => m.name === context.model), - [context.model], + () => AVAILABLE_MODELS.find((m) => m.name === context.model_name), + [context.model_name], ); const handleModelSelect = useCallback( - (model: string) => { + (model_name: string) => { onContextChange?.({ ...context, - model, + model_name, }); setModelDialogOpen(false); }, @@ -140,13 +143,8 @@ export function InputBox({ > - - + + {selectedModel?.displayName} @@ -160,12 +158,8 @@ export function InputBox({ value={m.name} onSelect={() => handleModelSelect(m.name)} > - {m.displayName} - {m.name === context.model ? ( + {m.name === context.model_name ? ( ) : (
diff --git a/frontend/src/components/workspace/recent-chat-list.tsx b/frontend/src/components/workspace/recent-chat-list.tsx index c69c002..d7ce2dd 100644 --- a/frontend/src/components/workspace/recent-chat-list.tsx +++ b/frontend/src/components/workspace/recent-chat-list.tsx @@ -20,7 +20,7 @@ import { SidebarMenuButton, SidebarMenuItem, } from "@/components/ui/sidebar"; -import { useDeleteThread, useThreads } from "@/core/api"; +import { useDeleteThread, useThreads } from "@/core/threads/hooks"; import { pathOfThread, titleOfThread } from "@/core/threads/utils"; export function RecentChatList() { diff --git a/frontend/src/core/api/client.ts b/frontend/src/core/api/api-client.ts similarity index 88% rename from frontend/src/core/api/client.ts rename to frontend/src/core/api/api-client.ts index da65b07..e8197a9 100644 --- a/frontend/src/core/api/client.ts +++ b/frontend/src/core/api/api-client.ts @@ -3,7 +3,7 @@ import { Client as LangGraphClient } from "@langchain/langgraph-sdk/client"; let _singleton: LangGraphClient | null = null; -export function getLangGraphClient(): LangGraphClient { +export function getAPIClient(): LangGraphClient { let url: URL | null = null; if (typeof window === "undefined") { url = new URL("/api/langgraph", "http://localhost:3000"); diff --git a/frontend/src/core/api/hooks.ts b/frontend/src/core/api/hooks.ts deleted file mode 100644 index a24f7be..0000000 --- a/frontend/src/core/api/hooks.ts +++ /dev/null @@ -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[0] = { - limit: 50, - sortBy: "updated_at", - sortOrder: "desc", - }, -) { - const langGraphClient = getLangGraphClient(); - return useQuery({ - queryKey: ["threads", "search", params], - queryFn: async () => { - const response = - await langGraphClient.threads.search(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) => { - return oldData.filter((t) => t.thread_id !== threadId); - }, - ); - }, - }); -} diff --git a/frontend/src/core/api/index.ts b/frontend/src/core/api/index.ts index 0f9f4fe..f63899b 100644 --- a/frontend/src/core/api/index.ts +++ b/frontend/src/core/api/index.ts @@ -1,2 +1 @@ -export * from "./client"; -export * from "./hooks"; +export * from "./api-client"; diff --git a/frontend/src/core/settings/local.ts b/frontend/src/core/settings/local.ts index 0afa962..419ba9d 100644 --- a/frontend/src/core/settings/local.ts +++ b/frontend/src/core/settings/local.ts @@ -2,7 +2,7 @@ import type { AgentThreadContext } from "../threads"; export const DEFAULT_LOCAL_SETTINGS: LocalSettings = { context: { - model: "deepseek-v3.2", + model_name: "deepseek-v3.2", thinking_enabled: true, }, }; diff --git a/frontend/src/core/threads/hooks.ts b/frontend/src/core/threads/hooks.ts new file mode 100644 index 0000000..f0c642a --- /dev/null +++ b/frontend/src/core/threads/hooks.ts @@ -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({ + 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; + 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[0] = { + limit: 50, + sortBy: "updated_at", + sortOrder: "desc", + }, +) { + const langGraphClient = getAPIClient(); + return useQuery({ + queryKey: ["threads", "search", params], + queryFn: async () => { + const response = + await langGraphClient.threads.search(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) => { + return oldData.filter((t) => t.thread_id !== threadId); + }, + ); + }, + }); +} diff --git a/frontend/src/core/threads/types.ts b/frontend/src/core/threads/types.ts index eb59a2a..a6ec392 100644 --- a/frontend/src/core/threads/types.ts +++ b/frontend/src/core/threads/types.ts @@ -10,6 +10,6 @@ export interface AgentThread extends Thread {} export interface AgentThreadContext extends Record { thread_id: string; - model: string; + model_name: string; thinking_enabled: boolean; }