fix(prompt-input): 提供可选上下文入口并避免历史路由切换期异常

This commit is contained in:
董宏凯 committed 2026-09-01 14:51:21 +08:00
1 parent 519e4c0c01
commit 9455160101
2 files changed
+21 -11

No files matched your search

@@ -60,7 +60,6 @@ import {
createContext, createContext,
type FormEvent, type FormEvent,
type FormEventHandler, type FormEventHandler,
Fragment,
type HTMLAttributes, type HTMLAttributes,
type KeyboardEventHandler, type KeyboardEventHandler,
type PropsWithChildren, type PropsWithChildren,
@@ -124,8 +123,10 @@ export const usePromptInputController = () => {
return ctx; return ctx;
}; };
// Optional variants (do NOT throw). Useful for dual-mode components. // Optional variants (do NOT throw). Useful for dual-mode components and for
const useOptionalPromptInputController = () => // Fast Refresh transitions where a page can briefly retain the previous
// provider context instance.
export const useOptionalPromptInputController = () =>
useContext(PromptInputController); useContext(PromptInputController);
export const useProviderAttachments = () => { export const useProviderAttachments = () => {
@@ -406,9 +407,9 @@ export function PromptInputAttachments({
{...props} {...props}
> >
{attachments.files.map((file) => ( {attachments.files.map((file) => (
<Fragment key={file.id}> <div key={file.id} className="max-w-60">
<div className="max-w-60">{children(file)}</div> {children(file)}
</Fragment> </div>
))} ))}
</div> </div>
); );
@@ -1,7 +1,7 @@
import { useParams, useSearchParams } from "next/navigation"; import { useParams, useSearchParams } from "next/navigation";
import { useEffect, useMemo, useRef } from "react"; 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"; import { useI18n } from "@/core/i18n/hooks";
/** /**
@@ -11,7 +11,11 @@ export function useSpecificChatMode() {
const { t } = useI18n(); const { t } = useI18n();
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
const searchParams = useSearchParams(); 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(() => { const inputInitialValue = useMemo(() => {
if (threadIdFromPath !== "new" || searchParams.get("mode") !== "skill") { if (threadIdFromPath !== "new" || searchParams.get("mode") !== "skill") {
return undefined; return undefined;
@@ -19,8 +23,10 @@ export function useSpecificChatMode() {
return t.inputBox.createSkillPrompt; return t.inputBox.createSkillPrompt;
}, [threadIdFromPath, searchParams, t.inputBox.createSkillPrompt]); }, [threadIdFromPath, searchParams, t.inputBox.createSkillPrompt]);
const lastInitialValueRef = useRef<string | undefined>(undefined); const lastInitialValueRef = useRef<string | undefined>(undefined);
const setInputRef = useRef(promptInputController.textInput.setInput); const setInputRef = useRef<((value: string) => void) | undefined>(
setInputRef.current = promptInputController.textInput.setInput; promptInputController?.textInput.setInput,
);
setInputRef.current = promptInputController?.textInput.setInput;
useEffect(() => { useEffect(() => {
if ( if (
inputInitialValue && inputInitialValue &&
@@ -28,7 +34,10 @@ export function useSpecificChatMode() {
) { ) {
lastInitialValueRef.current = inputInitialValue; lastInitialValueRef.current = inputInitialValue;
setTimeout(() => { setTimeout(() => {
setInputRef.current(inputInitialValue); const setInput = setInputRef.current;
if (!setInput) return;
setInput(inputInitialValue);
const textarea = document.querySelector("textarea"); const textarea = document.querySelector("textarea");
if (textarea) { if (textarea) {
textarea.focus(); textarea.focus();