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