feat: rename 'model' to 'model_name'

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

View File

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

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">) {
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 };

View File

@@ -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({
>
<ModelSelectorTrigger asChild>
<PromptInputButton>
<ModelSelectorLogo
className="size-4"
provider={
selectedModel?.provider as ModelSelectorLogoProps["provider"]
}
/>
<ModelSelectorName>
<BoxIcon className="size-4" />
<ModelSelectorName className="font-light">
{selectedModel?.displayName}
</ModelSelectorName>
</PromptInputButton>
@@ -160,12 +158,8 @@ export function InputBox({
value={m.name}
onSelect={() => handleModelSelect(m.name)}
>
<ModelSelectorLogo
className="size-4"
provider={m.provider}
/>
<ModelSelectorName>{m.displayName}</ModelSelectorName>
{m.name === context.model ? (
{m.name === context.model_name ? (
<CheckIcon className="ml-auto size-4" />
) : (
<div className="ml-auto size-4" />

View File

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

View File

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

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 "./hooks";
export * from "./api-client";

View File

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

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> {
thread_id: string;
model: string;
model_name: string;
thinking_enabled: boolean;
}