mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-10 16:22:38 +02:00
feat: migrated to surfsense deep agent
This commit is contained in:
parent
b14283e300
commit
4a0c3e368a
90 changed files with 5337 additions and 6029 deletions
|
|
@ -10,11 +10,12 @@ import { useParams, useRouter } from "next/navigation";
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Thread } from "@/components/assistant-ui/thread";
|
||||
import { ChatHeader } from "@/components/new-chat/chat-header";
|
||||
import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking";
|
||||
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
|
||||
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
|
||||
import { LinkPreviewToolUI } from "@/components/tool-ui/link-preview";
|
||||
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
|
||||
import { ScrapeWebpageToolUI } from "@/components/tool-ui/scrape-webpage";
|
||||
import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking";
|
||||
import { getBearerToken } from "@/lib/auth-utils";
|
||||
import { createAttachmentAdapter, extractAttachmentContent } from "@/lib/chat/attachment-adapter";
|
||||
import {
|
||||
|
|
@ -34,21 +35,21 @@ import {
|
|||
*/
|
||||
function extractThinkingSteps(content: unknown): ThinkingStep[] {
|
||||
if (!Array.isArray(content)) return [];
|
||||
|
||||
|
||||
const thinkingPart = content.find(
|
||||
(part: unknown) =>
|
||||
typeof part === "object" &&
|
||||
part !== null &&
|
||||
"type" in part &&
|
||||
(part: unknown) =>
|
||||
typeof part === "object" &&
|
||||
part !== null &&
|
||||
"type" in part &&
|
||||
(part as { type: string }).type === "thinking-steps"
|
||||
) as { type: "thinking-steps"; steps: ThinkingStep[] } | undefined;
|
||||
|
||||
|
||||
return thinkingPart?.steps || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert backend message to assistant-ui ThreadMessageLike format
|
||||
* Filters out 'thinking-steps' part as it's handled separately
|
||||
* Filters out 'thinking-steps' part as it's handled separately via messageThinkingSteps
|
||||
*/
|
||||
function convertToThreadMessage(msg: MessageRecord): ThreadMessageLike {
|
||||
let content: ThreadMessageLike["content"];
|
||||
|
|
@ -58,15 +59,18 @@ function convertToThreadMessage(msg: MessageRecord): ThreadMessageLike {
|
|||
} else if (Array.isArray(msg.content)) {
|
||||
// Filter out thinking-steps part - it's handled separately via messageThinkingSteps
|
||||
const filteredContent = msg.content.filter(
|
||||
(part: unknown) =>
|
||||
!(typeof part === "object" &&
|
||||
part !== null &&
|
||||
"type" in part &&
|
||||
(part as { type: string }).type === "thinking-steps")
|
||||
(part: unknown) =>
|
||||
!(
|
||||
typeof part === "object" &&
|
||||
part !== null &&
|
||||
"type" in part &&
|
||||
(part as { type: string }).type === "thinking-steps"
|
||||
)
|
||||
);
|
||||
content = filteredContent.length > 0
|
||||
? (filteredContent as ThreadMessageLike["content"])
|
||||
: [{ type: "text", text: "" }];
|
||||
content =
|
||||
filteredContent.length > 0
|
||||
? (filteredContent as ThreadMessageLike["content"])
|
||||
: [{ type: "text", text: "" }];
|
||||
} else {
|
||||
content = [{ type: "text", text: String(msg.content) }];
|
||||
}
|
||||
|
|
@ -82,7 +86,12 @@ function convertToThreadMessage(msg: MessageRecord): ThreadMessageLike {
|
|||
/**
|
||||
* Tools that should render custom UI in the chat.
|
||||
*/
|
||||
const TOOLS_WITH_UI = new Set(["generate_podcast", "link_preview", "display_image", "scrape_webpage"]);
|
||||
const TOOLS_WITH_UI = new Set([
|
||||
"generate_podcast",
|
||||
"link_preview",
|
||||
"display_image",
|
||||
"scrape_webpage",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Type for thinking step data from the backend
|
||||
|
|
@ -101,10 +110,11 @@ export default function NewChatPage() {
|
|||
const [threadId, setThreadId] = useState<number | null>(null);
|
||||
const [messages, setMessages] = useState<ThreadMessageLike[]>([]);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
// Store thinking steps per message ID
|
||||
const [messageThinkingSteps, setMessageThinkingSteps] = useState<
|
||||
Map<string, ThinkingStep[]>
|
||||
>(new Map());
|
||||
// Store thinking steps per message ID - kept separate from content to avoid
|
||||
// "unsupported part type" errors from assistant-ui
|
||||
const [messageThinkingSteps, setMessageThinkingSteps] = useState<Map<string, ThinkingStep[]>>(
|
||||
new Map()
|
||||
);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
// Create the attachment adapter for file processing
|
||||
|
|
@ -141,7 +151,7 @@ export default function NewChatPage() {
|
|||
if (response.messages && response.messages.length > 0) {
|
||||
const loadedMessages = response.messages.map(convertToThreadMessage);
|
||||
setMessages(loadedMessages);
|
||||
|
||||
|
||||
// Extract and restore thinking steps from persisted messages
|
||||
const restoredThinkingSteps = new Map<string, ThinkingStep[]>();
|
||||
for (const msg of response.messages) {
|
||||
|
|
@ -247,10 +257,10 @@ export default function NewChatPage() {
|
|||
// Prepare assistant message
|
||||
const assistantMsgId = `msg-assistant-${Date.now()}`;
|
||||
const currentThinkingSteps = new Map<string, ThinkingStepData>();
|
||||
|
||||
|
||||
// Ordered content parts to preserve inline tool call positions
|
||||
// Each part is either a text segment or a tool call
|
||||
type ContentPart =
|
||||
type ContentPart =
|
||||
| { type: "text"; text: string }
|
||||
| {
|
||||
type: "tool-call";
|
||||
|
|
@ -260,13 +270,13 @@ export default function NewChatPage() {
|
|||
result?: unknown;
|
||||
};
|
||||
const contentParts: ContentPart[] = [];
|
||||
|
||||
|
||||
// Track the current text segment index (for appending text deltas)
|
||||
let currentTextPartIndex = -1;
|
||||
|
||||
|
||||
// Map to track tool call indices for updating results
|
||||
const toolCallIndices = new Map<string, number>();
|
||||
|
||||
|
||||
// Helper to get or create the current text part for appending text
|
||||
const appendText = (delta: string) => {
|
||||
if (currentTextPartIndex >= 0 && contentParts[currentTextPartIndex]?.type === "text") {
|
||||
|
|
@ -278,7 +288,7 @@ export default function NewChatPage() {
|
|||
currentTextPartIndex = contentParts.length - 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Helper to add a tool call (this "breaks" the current text segment)
|
||||
const addToolCall = (toolCallId: string, toolName: string, args: Record<string, unknown>) => {
|
||||
if (TOOLS_WITH_UI.has(toolName)) {
|
||||
|
|
@ -293,9 +303,12 @@ export default function NewChatPage() {
|
|||
currentTextPartIndex = -1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Helper to update a tool call's args or result
|
||||
const updateToolCall = (toolCallId: string, update: { args?: Record<string, unknown>; result?: unknown }) => {
|
||||
const updateToolCall = (
|
||||
toolCallId: string,
|
||||
update: { args?: Record<string, unknown>; result?: unknown }
|
||||
) => {
|
||||
const index = toolCallIndices.get(toolCallId);
|
||||
if (index !== undefined && contentParts[index]?.type === "tool-call") {
|
||||
const tc = contentParts[index] as ContentPart & { type: "tool-call" };
|
||||
|
|
@ -304,7 +317,7 @@ export default function NewChatPage() {
|
|||
}
|
||||
};
|
||||
|
||||
// Helper to build content for UI (without thinking-steps)
|
||||
// Helper to build content for UI (without thinking-steps to avoid assistant-ui errors)
|
||||
const buildContentForUI = (): ThreadMessageLike["content"] => {
|
||||
// Filter to only include text parts with content and tool-calls with UI
|
||||
const filtered = contentParts.filter((part) => {
|
||||
|
|
@ -317,10 +330,10 @@ export default function NewChatPage() {
|
|||
: [{ type: "text", text: "" }];
|
||||
};
|
||||
|
||||
// Helper to build content for persistence (includes thinking-steps)
|
||||
// Helper to build content for persistence (includes thinking-steps for restoration)
|
||||
const buildContentForPersistence = (): unknown[] => {
|
||||
const parts: unknown[] = [];
|
||||
|
||||
|
||||
// Include thinking steps for persistence
|
||||
if (currentThinkingSteps.size > 0) {
|
||||
parts.push({
|
||||
|
|
@ -328,7 +341,7 @@ export default function NewChatPage() {
|
|||
steps: Array.from(currentThinkingSteps.values()),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Add content parts (filtered)
|
||||
for (const part of contentParts) {
|
||||
if (part.type === "text" && part.text.length > 0) {
|
||||
|
|
@ -337,7 +350,7 @@ export default function NewChatPage() {
|
|||
parts.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return parts.length > 0 ? parts : [{ type: "text", text: "" }];
|
||||
};
|
||||
|
||||
|
|
@ -482,13 +495,12 @@ export default function NewChatPage() {
|
|||
const stepData = parsed.data as ThinkingStepData;
|
||||
if (stepData?.id) {
|
||||
currentThinkingSteps.set(stepData.id, stepData);
|
||||
// Update message-specific thinking steps
|
||||
// Update thinking steps state for rendering
|
||||
// The ThinkingStepsScrollHandler in Thread component
|
||||
// will handle auto-scrolling when this state changes
|
||||
setMessageThinkingSteps((prev) => {
|
||||
const newMap = new Map(prev);
|
||||
newMap.set(
|
||||
assistantMsgId,
|
||||
Array.from(currentThinkingSteps.values())
|
||||
);
|
||||
newMap.set(assistantMsgId, Array.from(currentThinkingSteps.values()));
|
||||
return newMap;
|
||||
});
|
||||
}
|
||||
|
|
@ -614,8 +626,11 @@ export default function NewChatPage() {
|
|||
<LinkPreviewToolUI />
|
||||
<DisplayImageToolUI />
|
||||
<ScrapeWebpageToolUI />
|
||||
<div className="h-[calc(100vh-64px)] max-h-[calc(100vh-64px)] overflow-hidden">
|
||||
<Thread messageThinkingSteps={messageThinkingSteps} />
|
||||
<div className="flex flex-col h-[calc(100vh-64px)] max-h-[calc(100vh-64px)] overflow-hidden">
|
||||
<ChatHeader searchSpaceId={searchSpaceId} />
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<Thread messageThinkingSteps={messageThinkingSteps} />
|
||||
</div>
|
||||
</div>
|
||||
</AssistantRuntimeProvider>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue