mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-05 07:02:13 +08:00
refactor: move biz logic to core
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { type HumanMessage } from "@langchain/core/messages";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import type { PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
||||
import { BreadcrumbItem } from "@/components/ui/breadcrumb";
|
||||
import { InputBox } from "@/components/workspace/input-box";
|
||||
import { MessageList } from "@/components/workspace/message-list/message-list";
|
||||
@@ -17,13 +14,12 @@ import {
|
||||
} from "@/components/workspace/workspace-container";
|
||||
import { useLocalSettings } from "@/core/settings";
|
||||
import { type AgentThread } from "@/core/threads";
|
||||
import { useThreadStream } from "@/core/threads/hooks";
|
||||
import { titleOfThread } from "@/core/threads/utils";
|
||||
import { useSubmitThread, useThreadStream } from "@/core/threads/hooks";
|
||||
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
||||
import { uuid } from "@/core/utils/uuid";
|
||||
|
||||
export default function ChatPage() {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
||||
const isNewThread = useMemo(
|
||||
() => threadIdFromPath === "new",
|
||||
@@ -43,40 +39,15 @@ export default function ChatPage() {
|
||||
isNewThread,
|
||||
threadId,
|
||||
});
|
||||
const handleSubmit = useCallback(
|
||||
async (message: PromptInputMessage) => {
|
||||
const text = message.text.trim();
|
||||
if (isNewThread) {
|
||||
router.replace(`/workspace/chats/${threadId}`);
|
||||
}
|
||||
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"] });
|
||||
const handleSubmit = useSubmitThread({
|
||||
isNewThread,
|
||||
threadId,
|
||||
thread,
|
||||
threadContext,
|
||||
afterSubmit() {
|
||||
router.push(pathOfThread(threadId!));
|
||||
},
|
||||
[isNewThread, queryClient, router, thread, threadContext, threadId],
|
||||
);
|
||||
});
|
||||
const handleStop = useCallback(async () => {
|
||||
await thread.stop();
|
||||
}, [thread]);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Message as AIElementMessage,
|
||||
MessageContent as AIElementMessageContent,
|
||||
MessageResponse as AIElementMessageResponse,
|
||||
MessageToolbar,
|
||||
} from "@/components/ai-elements/message";
|
||||
import {
|
||||
extractContentFromMessage,
|
||||
@@ -15,6 +16,7 @@ import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { MessageGroup } from "./message-group";
|
||||
import { CopyButton } from "../copy-button";
|
||||
|
||||
export function MessageListItem({
|
||||
className,
|
||||
@@ -38,6 +40,17 @@ export function MessageListItem({
|
||||
messagesInGroup={messagesInGroup}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<MessageToolbar
|
||||
className={cn(
|
||||
message.type === "human" && "justify-end",
|
||||
message.type === "human" ? "-bottom-9" : "-bottom-8",
|
||||
"absolute right-0 left-0 z-20 opacity-0 transition-opacity delay-200 duration-300 group-hover/conversation-message:opacity-100",
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-1">
|
||||
<CopyButton clipboardData={extractContentFromMessage(message)} />
|
||||
</div>
|
||||
</MessageToolbar>
|
||||
</AIElementMessage>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function MessageList({
|
||||
<ConversationContent className="mx-auto w-full max-w-(--container-width-md)">
|
||||
{groupMessages(
|
||||
thread.messages,
|
||||
(groupedMessages, groupIndex, isLastGroup) => {
|
||||
(groupedMessages) => {
|
||||
if (groupedMessages[0] && hasContent(groupedMessages[0])) {
|
||||
const message = groupedMessages[0];
|
||||
return (
|
||||
|
||||
@@ -57,7 +57,7 @@ export function RecentChatList() {
|
||||
<SidebarMenu>
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
{threads.map((thread) => {
|
||||
const isActive = pathOfThread(thread, false) === pathname;
|
||||
const isActive = pathOfThread(thread.thread_id) === pathname;
|
||||
return (
|
||||
<SidebarMenuItem
|
||||
key={thread.thread_id}
|
||||
@@ -67,7 +67,7 @@ export function RecentChatList() {
|
||||
<div>
|
||||
<Link
|
||||
className="text-muted-foreground block w-full whitespace-nowrap group-hover/side-menu-item:overflow-hidden"
|
||||
href={pathOfThread(thread)}
|
||||
href={pathOfThread(thread.thread_id)}
|
||||
>
|
||||
{titleOfThread(thread)}
|
||||
</Link>
|
||||
|
||||
@@ -22,16 +22,37 @@ export function useThreadStream({
|
||||
threadId: string | null | undefined;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
return useStream<AgentThreadState>({
|
||||
const thread = useStream<AgentThreadState>({
|
||||
client: getAPIClient(),
|
||||
assistantId: "lead_agent",
|
||||
threadId: isNewThread ? undefined : threadId,
|
||||
reconnectOnMount: true,
|
||||
fetchStateHistory: true,
|
||||
onFinish() {
|
||||
void queryClient.invalidateQueries({ queryKey: ["threads", "search"] });
|
||||
onFinish(state) {
|
||||
// void queryClient.invalidateQueries({ queryKey: ["threads", "search"] });
|
||||
queryClient.setQueriesData(
|
||||
{
|
||||
queryKey: ["threads", "search"],
|
||||
exact: false,
|
||||
},
|
||||
(oldData: Array<AgentThread>) => {
|
||||
return oldData.map((t) => {
|
||||
if (t.thread_id === threadId) {
|
||||
return {
|
||||
...t,
|
||||
values: {
|
||||
...t.values,
|
||||
title: state.values.title,
|
||||
},
|
||||
};
|
||||
}
|
||||
return t;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
return thread;
|
||||
}
|
||||
|
||||
export function useSubmitThread({
|
||||
@@ -39,43 +60,47 @@ export function useSubmitThread({
|
||||
thread,
|
||||
threadContext,
|
||||
isNewThread,
|
||||
message,
|
||||
afterSubmit,
|
||||
}: {
|
||||
isNewThread: boolean;
|
||||
threadId: string;
|
||||
threadId: string | null | undefined;
|
||||
thread: UseStream<AgentThreadState>;
|
||||
threadContext: AgentThreadContext;
|
||||
message: PromptInputMessage;
|
||||
threadContext: Omit<AgentThreadContext, "thread_id">;
|
||||
afterSubmit?: () => void;
|
||||
}) {
|
||||
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,
|
||||
const callback = useCallback(
|
||||
async (message: PromptInputMessage) => {
|
||||
const text = message.text.trim();
|
||||
await thread.submit(
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
type: "human",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as HumanMessage[],
|
||||
},
|
||||
},
|
||||
);
|
||||
void queryClient.invalidateQueries({ queryKey: ["threads", "search"] });
|
||||
}, [queryClient, thread, threadContext, threadId, isNewThread, text]);
|
||||
{
|
||||
threadId: isNewThread ? threadId! : undefined,
|
||||
streamSubgraphs: true,
|
||||
streamResumable: true,
|
||||
context: {
|
||||
...threadContext,
|
||||
thread_id: threadId,
|
||||
},
|
||||
},
|
||||
);
|
||||
void queryClient.invalidateQueries({ queryKey: ["threads", "search"] });
|
||||
afterSubmit?.();
|
||||
},
|
||||
[thread, isNewThread, threadId, threadContext, queryClient, afterSubmit],
|
||||
);
|
||||
return callback;
|
||||
}
|
||||
|
||||
@@ -86,12 +111,11 @@ export function useThreads(
|
||||
sortOrder: "desc",
|
||||
},
|
||||
) {
|
||||
const langGraphClient = getAPIClient();
|
||||
const apiClient = getAPIClient();
|
||||
return useQuery<AgentThread[]>({
|
||||
queryKey: ["threads", "search", params],
|
||||
queryFn: async () => {
|
||||
const response =
|
||||
await langGraphClient.threads.search<AgentThreadState>(params);
|
||||
const response = await apiClient.threads.search<AgentThreadState>(params);
|
||||
return response as AgentThread[];
|
||||
},
|
||||
});
|
||||
@@ -99,10 +123,10 @@ export function useThreads(
|
||||
|
||||
export function useDeleteThread() {
|
||||
const queryClient = useQueryClient();
|
||||
const langGraphClient = getAPIClient();
|
||||
const apiClient = getAPIClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ threadId }: { threadId: string }) => {
|
||||
await langGraphClient.threads.delete(threadId);
|
||||
await apiClient.threads.delete(threadId);
|
||||
},
|
||||
onSuccess(_, { threadId }) {
|
||||
queryClient.setQueriesData(
|
||||
|
||||
Reference in New Issue
Block a user