feat: rename 'model' to 'model_name'

This commit is contained in:
Henry Li
2026-01-16 14:03:34 +08:00
parent 1f03fb3749
commit faf80bb429
10 changed files with 147 additions and 89 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,2 +1 @@
export * from "./client"; export * from "./api-client";
export * from "./hooks";

View File

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

View 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);
},
);
},
});
}

View File

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