保持new路由下的空白引导页
This commit is contained in:
parent
4119fdcba7
commit
e2fdfa75d7
|
|
@ -80,20 +80,19 @@ export default function ChatPage() {
|
|||
}, 100);
|
||||
}
|
||||
}, [inputInitialValue]);
|
||||
const isNewThread = useMemo(
|
||||
() => {
|
||||
// UI mode depends only on route: /workspace/chats/new is always "new page" mode.
|
||||
const isNewThread = useMemo(() => threadIdFromPath === "new", [threadIdFromPath]);
|
||||
|
||||
// Submission strategy is controlled by `isnew` query param only.
|
||||
// - isnew=false: reuse existing thread
|
||||
// - otherwise: create/start a new session
|
||||
const createNewSession = useMemo(() => {
|
||||
if (threadIdFromPath !== "new") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const queryThreadId = searchParams.get("thread_id")?.trim();
|
||||
const queryIsNew = searchParams.get("isnew")?.trim().toLowerCase();
|
||||
const shouldReuseExisting = queryIsNew === "false" && !!queryThreadId;
|
||||
|
||||
return !shouldReuseExisting;
|
||||
},
|
||||
[threadIdFromPath, searchParams],
|
||||
);
|
||||
return searchParams.get("isnew")?.trim().toLowerCase() !== "false";
|
||||
}, [threadIdFromPath, searchParams]);
|
||||
|
||||
const uploadTarget = useMemo(() => {
|
||||
const target = searchParams.get("upload_target")?.trim().toLowerCase();
|
||||
|
|
@ -110,11 +109,21 @@ export default function ChatPage() {
|
|||
}
|
||||
}, [threadIdFromPath, searchParams]);
|
||||
|
||||
// Runtime strategy for /new page:
|
||||
// - UI remains new-page mode
|
||||
// - if isnew=false, execute against existing thread_id without creating a new one
|
||||
const reuseExistingThread = useMemo(
|
||||
() => threadIdFromPath === "new" && !createNewSession && !!threadId,
|
||||
[threadIdFromPath, createNewSession, threadId],
|
||||
);
|
||||
|
||||
const { showNotification } = useNotification();
|
||||
const [finalState, setFinalState] = useState<AgentThreadState | null>(null);
|
||||
const thread = useThreadStream({
|
||||
isNewThread,
|
||||
// Keep UI in new-page mode, but runtime may reuse existing thread
|
||||
isNewThread: reuseExistingThread ? false : isNewThread,
|
||||
threadId,
|
||||
fetchStateHistory: true,
|
||||
onFinish: (state) => {
|
||||
setFinalState(state);
|
||||
if (document.hidden || !document.hasFocus()) {
|
||||
|
|
@ -150,13 +159,16 @@ export default function ChatPage() {
|
|||
return result;
|
||||
}, [thread, isNewThread]);
|
||||
|
||||
const [hasSubmitted, setHasSubmitted] = useState(false);
|
||||
const suppressExistingThreadPrefetchUi = reuseExistingThread && !hasSubmitted;
|
||||
|
||||
useEffect(() => {
|
||||
const pageTitle = isNewThread
|
||||
? t.pages.newChat
|
||||
: thread.values?.title && thread.values.title !== "Untitled"
|
||||
? thread.values.title
|
||||
: t.pages.untitled;
|
||||
if (thread.isThreadLoading) {
|
||||
if (thread.isThreadLoading && !suppressExistingThreadPrefetchUi) {
|
||||
document.title = `Loading... - ${t.pages.appName}`;
|
||||
} else {
|
||||
document.title = `${pageTitle} - ${t.pages.appName}`;
|
||||
|
|
@ -168,6 +180,7 @@ export default function ChatPage() {
|
|||
t.pages.appName,
|
||||
thread.values.title,
|
||||
thread.isThreadLoading,
|
||||
suppressExistingThreadPrefetchUi,
|
||||
]);
|
||||
|
||||
const [autoSelectFirstArtifact, setAutoSelectFirstArtifact] = useState(true);
|
||||
|
|
@ -198,8 +211,9 @@ export default function ChatPage() {
|
|||
|
||||
const [todoListCollapsed, setTodoListCollapsed] = useState(true);
|
||||
|
||||
const handleSubmit = useSubmitThread({
|
||||
const submitThread = useSubmitThread({
|
||||
isNewThread,
|
||||
createNewSession,
|
||||
threadId,
|
||||
thread,
|
||||
uploadTarget,
|
||||
|
|
@ -214,6 +228,13 @@ export default function ChatPage() {
|
|||
router.push(pathOfThread(threadId!));
|
||||
},
|
||||
});
|
||||
const handleSubmit = useCallback(
|
||||
(message: Parameters<typeof submitThread>[0]) => {
|
||||
setHasSubmitted(true);
|
||||
void submitThread(message);
|
||||
},
|
||||
[submitThread],
|
||||
);
|
||||
const handleStop = useCallback(async () => {
|
||||
await thread.stop();
|
||||
}, [thread]);
|
||||
|
|
@ -268,8 +289,11 @@ export default function ChatPage() {
|
|||
className={cn("size-full", !isNewThread && "pt-10")}
|
||||
threadId={threadId}
|
||||
thread={thread}
|
||||
suppressThreadLoading={suppressExistingThreadPrefetchUi}
|
||||
messagesOverride={
|
||||
!thread.isLoading && finalState?.messages
|
||||
suppressExistingThreadPrefetchUi
|
||||
? []
|
||||
: !thread.isLoading && finalState?.messages
|
||||
? (finalState.messages as Message[])
|
||||
: undefined
|
||||
}
|
||||
|
|
@ -306,7 +330,13 @@ export default function ChatPage() {
|
|||
className={cn("bg-background/5 w-full -translate-y-4")}
|
||||
isNewThread={isNewThread}
|
||||
autoFocus={isNewThread}
|
||||
status={thread.isLoading ? "streaming" : "ready"}
|
||||
status={
|
||||
suppressExistingThreadPrefetchUi
|
||||
? "ready"
|
||||
: thread.isLoading
|
||||
? "streaming"
|
||||
: "ready"
|
||||
}
|
||||
context={settings.context}
|
||||
extraHeader={
|
||||
isNewThread && <Welcome mode={settings.context.mode} />
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export function MessageList({
|
|||
threadId,
|
||||
thread,
|
||||
messagesOverride,
|
||||
suppressThreadLoading = false,
|
||||
paddingBottom = 160,
|
||||
}: {
|
||||
className?: string;
|
||||
|
|
@ -42,13 +43,14 @@ export function MessageList({
|
|||
thread: UseStream<AgentThreadState>;
|
||||
/** When set (e.g. from onFinish), use instead of thread.messages so SSE end shows complete state. */
|
||||
messagesOverride?: Message[];
|
||||
suppressThreadLoading?: boolean;
|
||||
paddingBottom?: number;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading);
|
||||
const updateSubtask = useUpdateSubtask();
|
||||
const messages = messagesOverride ?? thread.messages;
|
||||
if (thread.isThreadLoading) {
|
||||
if (thread.isThreadLoading && !suppressThreadLoading) {
|
||||
return <MessageListSkeleton />;
|
||||
}
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -21,10 +21,12 @@ import type {
|
|||
export function useThreadStream({
|
||||
threadId,
|
||||
isNewThread,
|
||||
fetchStateHistory = true,
|
||||
onFinish,
|
||||
}: {
|
||||
isNewThread: boolean;
|
||||
threadId: string | null | undefined;
|
||||
fetchStateHistory?: boolean;
|
||||
onFinish?: (state: AgentThreadState) => void;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
|
|
@ -34,7 +36,7 @@ export function useThreadStream({
|
|||
assistantId: "lead_agent",
|
||||
threadId: isNewThread ? undefined : threadId,
|
||||
reconnectOnMount: true,
|
||||
fetchStateHistory: true,
|
||||
fetchStateHistory,
|
||||
onCustomEvent(event: unknown) {
|
||||
console.info(event);
|
||||
if (
|
||||
|
|
@ -84,10 +86,12 @@ export function useSubmitThread({
|
|||
thread,
|
||||
threadContext,
|
||||
isNewThread,
|
||||
createNewSession,
|
||||
uploadTarget,
|
||||
afterSubmit,
|
||||
}: {
|
||||
isNewThread: boolean;
|
||||
createNewSession: boolean;
|
||||
threadId: string | null | undefined;
|
||||
thread: UseStream<AgentThreadState>;
|
||||
threadContext: Omit<AgentThreadContext, "thread_id">;
|
||||
|
|
@ -95,10 +99,21 @@ export function useSubmitThread({
|
|||
afterSubmit?: () => void;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiClient = getAPIClient();
|
||||
const callback = useCallback(
|
||||
async (message: PromptInputMessage) => {
|
||||
const text = message.text.trim();
|
||||
|
||||
// For "new session" semantics, ensure the target thread id starts fresh.
|
||||
// If the same id already exists, delete it first and let submit recreate it.
|
||||
if (createNewSession && threadId) {
|
||||
try {
|
||||
await apiClient.threads.delete(threadId);
|
||||
} catch {
|
||||
// Ignore delete errors (e.g. thread does not exist yet)
|
||||
}
|
||||
}
|
||||
|
||||
// Upload files first if any
|
||||
if (message.files && message.files.length > 0) {
|
||||
try {
|
||||
|
|
@ -154,7 +169,7 @@ export function useSubmitThread({
|
|||
] as HumanMessage[],
|
||||
},
|
||||
{
|
||||
threadId: isNewThread ? threadId! : undefined,
|
||||
threadId: createNewSession ? threadId! : undefined,
|
||||
streamSubgraphs: true,
|
||||
streamResumable: true,
|
||||
streamMode: ["values", "messages-tuple", "custom"],
|
||||
|
|
@ -170,7 +185,17 @@ export function useSubmitThread({
|
|||
void queryClient.invalidateQueries({ queryKey: ["threads", "search"] });
|
||||
afterSubmit?.();
|
||||
},
|
||||
[thread, isNewThread, threadId, threadContext, uploadTarget, queryClient, afterSubmit],
|
||||
[
|
||||
thread,
|
||||
isNewThread,
|
||||
createNewSession,
|
||||
threadId,
|
||||
threadContext,
|
||||
uploadTarget,
|
||||
queryClient,
|
||||
apiClient,
|
||||
afterSubmit,
|
||||
],
|
||||
);
|
||||
return callback;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue