mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-20 04:44:46 +08:00
refactor: refine folder structure and rename
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import type { ChatStatus } from "ai";
|
||||
import { LightbulbIcon, LightbulbOffIcon } from "lucide-react";
|
||||
import { useCallback, type ComponentProps } from "react";
|
||||
|
||||
import {
|
||||
PromptInput,
|
||||
PromptInputBody,
|
||||
PromptInputButton,
|
||||
PromptInputFooter,
|
||||
PromptInputSubmit,
|
||||
PromptInputTextarea,
|
||||
@@ -11,6 +13,8 @@ import {
|
||||
} from "@/components/ai-elements/prompt-input";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Tooltip } from "./tooltip";
|
||||
|
||||
export function InputBox({
|
||||
className,
|
||||
autoFocus,
|
||||
@@ -57,7 +61,13 @@ export function InputBox({
|
||||
/>
|
||||
</PromptInputBody>
|
||||
<PromptInputFooter className="flex">
|
||||
<div></div>
|
||||
<div>
|
||||
<Tooltip content="">
|
||||
<PromptInputButton>
|
||||
<LightbulbOffIcon className="size-4" />
|
||||
</PromptInputButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<PromptInputSubmit
|
||||
className="rounded-full"
|
||||
|
||||
@@ -21,14 +21,13 @@ import {
|
||||
ChainOfThoughtStep,
|
||||
} from "@/components/ai-elements/chain-of-thought";
|
||||
import { MessageResponse } from "@/components/ai-elements/message";
|
||||
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
||||
import { extractTitleFromMarkdown } from "@/core/utils/markdown";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import {
|
||||
extractReasoningContentFromMessage,
|
||||
findToolCallResult,
|
||||
} from "./utils";
|
||||
} from "@/core/messages/utils";
|
||||
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
||||
import { extractTitleFromMarkdown } from "@/core/utils/markdown";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function MessageGroup({
|
||||
className,
|
||||
|
||||
@@ -6,11 +6,15 @@ import {
|
||||
MessageContent as AIElementMessageContent,
|
||||
MessageResponse as AIElementMessageResponse,
|
||||
} from "@/components/ai-elements/message";
|
||||
import {
|
||||
extractContentFromMessage,
|
||||
hasReasoning,
|
||||
hasToolCalls,
|
||||
} from "@/core/messages/utils";
|
||||
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { MessageGroup } from "./message-group";
|
||||
import { extractContentFromMessage, hasReasoning, hasToolCalls } from "./utils";
|
||||
|
||||
export function MessageListItem({
|
||||
className,
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
ConversationContent,
|
||||
ConversationScrollButton,
|
||||
} from "@/components/ai-elements/conversation";
|
||||
import type { MessageThreadState } from "@/core/thread";
|
||||
import { groupMessages, hasContent } from "@/core/messages/utils";
|
||||
import type { AgentThreadState } from "@/core/threads";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { StreamingIndicator } from "../streaming-indicator";
|
||||
@@ -13,14 +14,13 @@ import { StreamingIndicator } from "../streaming-indicator";
|
||||
import { MessageGroup } from "./message-group";
|
||||
import { MessageListItem } from "./message-list-item";
|
||||
import { MessageListSkeleton } from "./skeleton";
|
||||
import { groupMessages, hasContent } from "./utils";
|
||||
|
||||
export function MessageList({
|
||||
className,
|
||||
thread,
|
||||
}: {
|
||||
className?: string;
|
||||
thread: UseStream<MessageThreadState>;
|
||||
thread: UseStream<AgentThreadState>;
|
||||
}) {
|
||||
if (thread.isThreadLoading) {
|
||||
return <MessageListSkeleton />;
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
import type { Message } from "@langchain/langgraph-sdk";
|
||||
|
||||
export function groupMessages<T>(
|
||||
messages: Message[],
|
||||
mapper: (
|
||||
groupedMessages: Message[],
|
||||
groupIndex: number,
|
||||
isLastGroup: boolean,
|
||||
) => T,
|
||||
isLoading = false,
|
||||
): T[] {
|
||||
if (messages.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const resultsOfGroups: T[] = [];
|
||||
let currentGroup: Message[] = [];
|
||||
const lastMessage = messages[messages.length - 1]!;
|
||||
const yieldCurrentGroup = () => {
|
||||
if (currentGroup.length > 0) {
|
||||
const resultOfGroup = mapper(
|
||||
currentGroup,
|
||||
resultsOfGroups.length,
|
||||
currentGroup.includes(lastMessage),
|
||||
);
|
||||
if (resultOfGroup !== undefined && resultOfGroup !== null) {
|
||||
resultsOfGroups.push(resultOfGroup);
|
||||
}
|
||||
currentGroup = [];
|
||||
}
|
||||
};
|
||||
let messageIndex = 0;
|
||||
for (const message of messages) {
|
||||
if (message.type === "human") {
|
||||
// Human messages are always shown as a individual group
|
||||
yieldCurrentGroup();
|
||||
currentGroup.push(message);
|
||||
yieldCurrentGroup();
|
||||
} else if (message.type === "tool") {
|
||||
// Tool messages are always shown with the assistant messages that contains the tool calls
|
||||
currentGroup.push(message);
|
||||
} else if (message.type === "ai") {
|
||||
if (
|
||||
hasToolCalls(message) ||
|
||||
(extractTextFromMessage(message) === "" &&
|
||||
extractReasoningContentFromMessage(message) !== "" &&
|
||||
messageIndex === messages.length - 1 &&
|
||||
isLoading)
|
||||
) {
|
||||
// Assistant messages without any content are folded into the previous group
|
||||
// Normally, these are tool calls (with or without thinking)
|
||||
currentGroup.push(message);
|
||||
} else {
|
||||
// Assistant messages with content (text or images) are shown as a group if they have content
|
||||
// No matter whether it has tool calls or not
|
||||
yieldCurrentGroup();
|
||||
currentGroup.push(message);
|
||||
}
|
||||
}
|
||||
messageIndex++;
|
||||
}
|
||||
yieldCurrentGroup();
|
||||
return resultsOfGroups;
|
||||
}
|
||||
|
||||
export function extractTextFromMessage(message: Message) {
|
||||
if (typeof message.content === "string") {
|
||||
return message.content.trim();
|
||||
}
|
||||
if (Array.isArray(message.content)) {
|
||||
return message.content
|
||||
.map((content) => (content.type === "text" ? content.text : ""))
|
||||
.join("\n")
|
||||
.trim();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function extractContentFromMessage(message: Message) {
|
||||
if (typeof message.content === "string") {
|
||||
return message.content.trim();
|
||||
}
|
||||
if (Array.isArray(message.content)) {
|
||||
return message.content
|
||||
.map((content) => {
|
||||
switch (content.type) {
|
||||
case "text":
|
||||
return content.text;
|
||||
case "image_url":
|
||||
const imageURL = extractURLFromImageURLContent(content.image_url);
|
||||
return ``;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
})
|
||||
.join("\n")
|
||||
.trim();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function extractReasoningContentFromMessage(message: Message) {
|
||||
if (message.type !== "ai" || !message.additional_kwargs) {
|
||||
return null;
|
||||
}
|
||||
if ("reasoning_content" in message.additional_kwargs) {
|
||||
return message.additional_kwargs.reasoning_content as string | null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function extractURLFromImageURLContent(
|
||||
content:
|
||||
| string
|
||||
| {
|
||||
url: string;
|
||||
},
|
||||
) {
|
||||
if (typeof content === "string") {
|
||||
return content;
|
||||
}
|
||||
return content.url;
|
||||
}
|
||||
|
||||
export function hasContent(message: Message) {
|
||||
if (typeof message.content === "string") {
|
||||
return message.content.trim().length > 0;
|
||||
}
|
||||
if (Array.isArray(message.content)) {
|
||||
return message.content.length > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasReasoning(message: Message) {
|
||||
return (
|
||||
message.type === "ai" &&
|
||||
typeof message.additional_kwargs?.reasoning_content === "string"
|
||||
);
|
||||
}
|
||||
|
||||
export function hasToolCalls(message: Message) {
|
||||
return (
|
||||
message.type === "ai" && message.tool_calls && message.tool_calls.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function findToolCallResult(toolCallId: string, messages: Message[]) {
|
||||
for (const message of messages) {
|
||||
if (message.type === "tool" && message.tool_call_id === toolCallId) {
|
||||
const content = extractTextFromMessage(message);
|
||||
if (content) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { useDeleteThread, useThreads } from "@/core/api";
|
||||
import { pathOfThread, titleOfThread } from "@/core/thread/utils";
|
||||
import { pathOfThread, titleOfThread } from "@/core/threads/utils";
|
||||
|
||||
export function RecentChatList() {
|
||||
const router = useRouter();
|
||||
|
||||
Reference in New Issue
Block a user