mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
refactor: update navigation and chat initialization logic
- Changed navigation from researcher to new-chat in EditorPage. - Updated NewChatPage to support lazy thread creation on first message. - Adjusted breadcrumb to not display thread ID for new-chat sections. - Improved user experience by modifying error messages and loading states. - Enhanced styling in model-selector component for better visibility.
This commit is contained in:
parent
85ddaedd9e
commit
bf22156664
5 changed files with 53 additions and 35 deletions
|
|
@ -323,7 +323,7 @@ export default function EditorPage() {
|
|||
if (hasUnsavedChanges) {
|
||||
setShowUnsavedDialog(true);
|
||||
} else {
|
||||
router.push(`/dashboard/${searchSpaceId}/researcher`);
|
||||
router.push(`/dashboard/${searchSpaceId}/new-chat`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -333,12 +333,12 @@ export default function EditorPage() {
|
|||
setGlobalHasUnsavedChanges(false);
|
||||
setHasUnsavedChanges(false);
|
||||
|
||||
// If there's a pending navigation (from sidebar), use that; otherwise go back to researcher
|
||||
// If there's a pending navigation (from sidebar), use that; otherwise go back to chat
|
||||
if (pendingNavigation) {
|
||||
router.push(pendingNavigation);
|
||||
setPendingNavigation(null);
|
||||
} else {
|
||||
router.push(`/dashboard/${searchSpaceId}/researcher`);
|
||||
router.push(`/dashboard/${searchSpaceId}/new-chat`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -379,7 +379,7 @@ export default function EditorPage() {
|
|||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button
|
||||
onClick={() => router.push(`/dashboard/${searchSpaceId}/researcher`)}
|
||||
onClick={() => router.push(`/dashboard/${searchSpaceId}/new-chat`)}
|
||||
variant="outline"
|
||||
className="gap-2"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
useExternalStoreRuntime,
|
||||
} from "@assistant-ui/react";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
|
|
@ -126,7 +126,6 @@ interface ThinkingStepData {
|
|||
|
||||
export default function NewChatPage() {
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const [isInitializing, setIsInitializing] = useState(true);
|
||||
const [threadId, setThreadId] = useState<number | null>(null);
|
||||
const [messages, setMessages] = useState<ThreadMessageLike[]>([]);
|
||||
|
|
@ -168,6 +167,7 @@ export default function NewChatPage() {
|
|||
}, [params.chat_id]);
|
||||
|
||||
// Initialize thread and load messages
|
||||
// For new chats (no urlChatId), we use lazy creation - thread is created on first message
|
||||
const initializeThread = useCallback(async () => {
|
||||
setIsInitializing(true);
|
||||
|
||||
|
|
@ -206,22 +206,20 @@ export default function NewChatPage() {
|
|||
setMessageDocumentsMap(restoredDocsMap);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create new thread
|
||||
const newThread = await createThread(searchSpaceId, "New Chat");
|
||||
setThreadId(newThread.id);
|
||||
router.replace(`/dashboard/${searchSpaceId}/new-chat/${newThread.id}`);
|
||||
}
|
||||
// For new chats (urlChatId === 0), don't create thread yet
|
||||
// Thread will be created lazily when user sends first message
|
||||
// This improves UX (instant load) and avoids orphan threads
|
||||
} catch (error) {
|
||||
console.error("[NewChatPage] Failed to initialize thread:", error);
|
||||
// Keep threadId as null - don't use Date.now() as it creates an invalid ID
|
||||
// that will cause 404 errors on subsequent API calls
|
||||
setThreadId(null);
|
||||
toast.error("Failed to initialize chat. Please try again.");
|
||||
toast.error("Failed to load chat. Please try again.");
|
||||
} finally {
|
||||
setIsInitializing(false);
|
||||
}
|
||||
}, [urlChatId, searchSpaceId, router, setMessageDocumentsMap]);
|
||||
}, [urlChatId, setMessageDocumentsMap]);
|
||||
|
||||
// Initialize on mount
|
||||
useEffect(() => {
|
||||
|
|
@ -240,8 +238,6 @@ export default function NewChatPage() {
|
|||
// Handle new message from user
|
||||
const onNew = useCallback(
|
||||
async (message: AppendMessage) => {
|
||||
if (!threadId) return;
|
||||
|
||||
// Extract user query text from content parts
|
||||
let userQuery = "";
|
||||
for (const part of message.content) {
|
||||
|
|
@ -273,6 +269,27 @@ export default function NewChatPage() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Lazy thread creation: create thread on first message if it doesn't exist
|
||||
let currentThreadId = threadId;
|
||||
if (!currentThreadId) {
|
||||
try {
|
||||
const newThread = await createThread(searchSpaceId, "New Chat");
|
||||
currentThreadId = newThread.id;
|
||||
setThreadId(currentThreadId);
|
||||
// Update URL silently using browser API (not router.replace) to avoid
|
||||
// interrupting the ongoing fetch/streaming with React navigation
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
`/dashboard/${searchSpaceId}/new-chat/${currentThreadId}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("[NewChatPage] Failed to create thread:", error);
|
||||
toast.error("Failed to start chat. Please try again.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add user message to state
|
||||
const userMsgId = `msg-user-${Date.now()}`;
|
||||
const userMessage: ThreadMessageLike = {
|
||||
|
|
@ -311,7 +328,7 @@ export default function NewChatPage() {
|
|||
},
|
||||
]
|
||||
: message.content;
|
||||
appendMessage(threadId, {
|
||||
appendMessage(currentThreadId, {
|
||||
role: "user",
|
||||
content: persistContent,
|
||||
}).catch((err) => console.error("Failed to persist user message:", err));
|
||||
|
|
@ -468,7 +485,7 @@ export default function NewChatPage() {
|
|||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chat_id: threadId,
|
||||
chat_id: currentThreadId,
|
||||
user_query: userQuery.trim(),
|
||||
search_space_id: searchSpaceId,
|
||||
messages: messageHistory,
|
||||
|
|
@ -601,7 +618,7 @@ export default function NewChatPage() {
|
|||
// Persist assistant message (with thinking steps for restoration on refresh)
|
||||
const finalContent = buildContentForPersistence();
|
||||
if (contentParts.length > 0) {
|
||||
appendMessage(threadId, {
|
||||
appendMessage(currentThreadId, {
|
||||
role: "assistant",
|
||||
content: finalContent,
|
||||
}).catch((err) => console.error("Failed to persist assistant message:", err));
|
||||
|
|
@ -678,7 +695,7 @@ export default function NewChatPage() {
|
|||
},
|
||||
});
|
||||
|
||||
// Show loading state
|
||||
// Show loading state only when loading an existing thread
|
||||
if (isInitializing) {
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-64px)] items-center justify-center">
|
||||
|
|
@ -687,11 +704,12 @@ export default function NewChatPage() {
|
|||
);
|
||||
}
|
||||
|
||||
// Show error state if thread initialization failed
|
||||
if (!threadId) {
|
||||
// Show error state only if we tried to load an existing thread but failed
|
||||
// For new chats (urlChatId === 0), threadId being null is expected (lazy creation)
|
||||
if (!threadId && urlChatId > 0) {
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-64px)] flex-col items-center justify-center gap-4">
|
||||
<div className="text-destructive">Failed to initialize chat</div>
|
||||
<div className="text-destructive">Failed to load chat</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ const getTimeBasedGreeting = (userEmail?: string): string => {
|
|||
: null;
|
||||
|
||||
// Array of greeting variations for each time period
|
||||
const morningGreetings = ["Good morning", "Rise and shine", "Morning", "Hey there"];
|
||||
const morningGreetings = ["Good morning", "Fresh start today", "Morning", "Hey there"];
|
||||
|
||||
const afternoonGreetings = ["Good afternoon", "Afternoon", "Hey there", "Hi there"];
|
||||
|
||||
|
|
@ -359,7 +359,7 @@ const getTimeBasedGreeting = (userEmail?: string): string => {
|
|||
|
||||
const nightGreetings = ["Good night", "Evening", "Hey there", "Winding down"];
|
||||
|
||||
const lateNightGreetings = ["Still up", "Night owl mode", "The night is young", "Hi there"];
|
||||
const lateNightGreetings = ["Still up", "Night owl mode", "Up past bedtime", "Hi there"];
|
||||
|
||||
// Select a random greeting based on time
|
||||
let greeting: string;
|
||||
|
|
@ -995,7 +995,7 @@ const UserMessage: FC = () => {
|
|||
>
|
||||
<UserMessageAttachments />
|
||||
|
||||
<div className="aui-user-message-content-wrapper relative col-start-2 min-w-0">
|
||||
<div className="aui-user-message-content-wrapper col-start-2 min-w-0">
|
||||
{/* Display mentioned documents as chips */}
|
||||
{mentionedDocs && mentionedDocs.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5 mb-2 justify-end">
|
||||
|
|
@ -1011,11 +1011,14 @@ const UserMessage: FC = () => {
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
|
||||
<MessagePrimitive.Parts />
|
||||
</div>
|
||||
<div className="aui-user-action-bar-wrapper -translate-x-full -translate-y-full absolute top-full left-0 pr-2">
|
||||
<UserActionBar />
|
||||
{/* Message bubble with action bar positioned relative to it */}
|
||||
<div className="relative">
|
||||
<div className="aui-user-message-content wrap-break-word rounded-2xl bg-muted px-4 py-2.5 text-foreground">
|
||||
<MessagePrimitive.Parts />
|
||||
</div>
|
||||
<div className="aui-user-action-bar-wrapper absolute top-1/2 right-full -translate-y-1/2 pr-1">
|
||||
<UserActionBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -165,14 +165,11 @@ export function DashboardBreadcrumb() {
|
|||
}
|
||||
|
||||
// Handle new-chat sub-sections (thread IDs)
|
||||
// Don't show thread ID in breadcrumb - users identify chats by content, not by ID
|
||||
if (section === "new-chat") {
|
||||
breadcrumbs.push({
|
||||
label: t("chat") || "Chat",
|
||||
href: `/dashboard/${segments[1]}/new-chat`,
|
||||
});
|
||||
if (subSection) {
|
||||
breadcrumbs.push({ label: `Thread ${subSection}` });
|
||||
}
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
|
|||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className={cn(
|
||||
"h-9 gap-2 px-3 rounded-xl border border-border/30 bg-background/50 backdrop-blur-sm",
|
||||
"h-9 gap-2 px-3 rounded-xl border border-border/80 bg-background/50 backdrop-blur-sm",
|
||||
"hover:bg-muted/80 hover:border-border/30 transition-all duration-200",
|
||||
"text-sm font-medium text-foreground",
|
||||
"focus-visible:ring-0 focus-visible:ring-offset-0",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue