From 9455160101ca0762f1769d0082f14733f2ed5744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Tue, 1 Sep 2026 14:51:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(prompt-input):=20=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E5=8F=AF=E9=80=89=E4=B8=8A=E4=B8=8B=E6=96=87=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E5=B9=B6=E9=81=BF=E5=85=8D=E5=8E=86=E5=8F=B2=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E6=9C=9F=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ai-elements/prompt-input.tsx | 35 ++++++++++--------- .../workspace/chats/use-chat-mode.ts | 21 +++++++---- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/frontend-web/src/components/ai-elements/prompt-input.tsx b/frontend-web/src/components/ai-elements/prompt-input.tsx index a796b775..58dc06ea 100644 --- a/frontend-web/src/components/ai-elements/prompt-input.tsx +++ b/frontend-web/src/components/ai-elements/prompt-input.tsx @@ -56,12 +56,11 @@ import { type ChangeEventHandler, Children, type ClipboardEventHandler, - type ComponentProps, - createContext, - type FormEvent, - type FormEventHandler, - Fragment, - type HTMLAttributes, + type ComponentProps, + createContext, + type FormEvent, + type FormEventHandler, + type HTMLAttributes, type KeyboardEventHandler, type PropsWithChildren, type ReactNode, @@ -124,9 +123,11 @@ export const usePromptInputController = () => { return ctx; }; -// Optional variants (do NOT throw). Useful for dual-mode components. -const useOptionalPromptInputController = () => - useContext(PromptInputController); +// Optional variants (do NOT throw). Useful for dual-mode components and for +// Fast Refresh transitions where a page can briefly retain the previous +// provider context instance. +export const useOptionalPromptInputController = () => + useContext(PromptInputController); export const useProviderAttachments = () => { const ctx = useContext(ProviderAttachmentsContext); @@ -403,14 +404,14 @@ export function PromptInputAttachments({ return (
- {attachments.files.map((file) => ( - -
{children(file)}
-
- ))} -
+ {...props} + > + {attachments.files.map((file) => ( +
+ {children(file)} +
+ ))} + ); } diff --git a/frontend-web/src/components/workspace/chats/use-chat-mode.ts b/frontend-web/src/components/workspace/chats/use-chat-mode.ts index 232fccd6..2f73fac4 100644 --- a/frontend-web/src/components/workspace/chats/use-chat-mode.ts +++ b/frontend-web/src/components/workspace/chats/use-chat-mode.ts @@ -1,7 +1,7 @@ import { useParams, useSearchParams } from "next/navigation"; import { useEffect, useMemo, useRef } from "react"; -import { usePromptInputController } from "@/components/ai-elements/prompt-input"; +import { useOptionalPromptInputController } from "@/components/ai-elements/prompt-input"; import { useI18n } from "@/core/i18n/hooks"; /** @@ -11,7 +11,11 @@ export function useSpecificChatMode() { const { t } = useI18n(); const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); const searchParams = useSearchParams(); - const promptInputController = usePromptInputController(); + // A regular history item can navigate here while Vite Fast Refresh is + // reconciling the previous page tree. In that short window the page may see + // a stale provider context. This hook only pre-fills the skill-mode draft, + // so it must not make an ordinary chat page fail to render. + const promptInputController = useOptionalPromptInputController(); const inputInitialValue = useMemo(() => { if (threadIdFromPath !== "new" || searchParams.get("mode") !== "skill") { return undefined; @@ -19,16 +23,21 @@ export function useSpecificChatMode() { return t.inputBox.createSkillPrompt; }, [threadIdFromPath, searchParams, t.inputBox.createSkillPrompt]); const lastInitialValueRef = useRef(undefined); - const setInputRef = useRef(promptInputController.textInput.setInput); - setInputRef.current = promptInputController.textInput.setInput; + const setInputRef = useRef<((value: string) => void) | undefined>( + promptInputController?.textInput.setInput, + ); + setInputRef.current = promptInputController?.textInput.setInput; useEffect(() => { if ( inputInitialValue && inputInitialValue !== lastInitialValueRef.current ) { lastInitialValueRef.current = inputInitialValue; - setTimeout(() => { - setInputRef.current(inputInitialValue); + setTimeout(() => { + const setInput = setInputRef.current; + if (!setInput) return; + + setInput(inputInitialValue); const textarea = document.querySelector("textarea"); if (textarea) { textarea.focus();