优化聊天框输入聚焦逻辑,增强网络错误处理
This commit is contained in:
@@ -113,6 +113,7 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
const [conversationId, setConversationId] = useState<string | undefined>(undefined);
|
const [conversationId, setConversationId] = useState<string | undefined>(undefined);
|
||||||
const abortRef = useRef<AbortController | null>(null);
|
const abortRef = useRef<AbortController | null>(null);
|
||||||
const bottomRef = useRef<HTMLDivElement>(null);
|
const bottomRef = useRef<HTMLDivElement>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const canSend = useMemo(() => input.trim().length > 0 && !isStreaming, [input, isStreaming]);
|
const canSend = useMemo(() => input.trim().length > 0 && !isStreaming, [input, isStreaming]);
|
||||||
@@ -122,6 +123,14 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
}, [messages, isStreaming]);
|
}, [messages, isStreaming]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}, 0);
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
const handleSend = async () => {
|
const handleSend = async () => {
|
||||||
const prompt = input.trim();
|
const prompt = input.trim();
|
||||||
if (!prompt || isStreaming) return;
|
if (!prompt || isStreaming) return;
|
||||||
@@ -443,6 +452,7 @@ export const GlobalChatbox: React.FC<Props> = ({ open, onClose }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
|
inputRef={inputRef}
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
|
|||||||
@@ -97,4 +97,18 @@ describe("streamCopilotChat", () => {
|
|||||||
{ type: "error", message: "Login expired. Please sign in again.", detail: undefined },
|
{ type: "error", message: "Login expired. Please sign in again.", detail: undefined },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("emits network error when fetch throws", async () => {
|
||||||
|
apiFetch.mockRejectedValue(new TypeError("Failed to fetch"));
|
||||||
|
|
||||||
|
const events: Array<{ type: string; message?: string; detail?: string }> = [];
|
||||||
|
await streamCopilotChat({
|
||||||
|
message: "hi",
|
||||||
|
onEvent: (event) => events.push(event),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(events).toEqual([
|
||||||
|
{ type: "error", message: "network request failed", detail: "Failed to fetch" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+12
-1
@@ -38,7 +38,9 @@ export const streamCopilotChat = async ({
|
|||||||
signal,
|
signal,
|
||||||
onEvent,
|
onEvent,
|
||||||
}: StreamOptions) => {
|
}: StreamOptions) => {
|
||||||
const response = await apiFetch(`${config.BACKEND_URL}/api/v1/copilot/chat/stream`, {
|
let response: Response;
|
||||||
|
try {
|
||||||
|
response = await apiFetch(`${config.BACKEND_URL}/api/v1/copilot/chat/stream`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
signal,
|
signal,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -51,6 +53,15 @@ export const streamCopilotChat = async ({
|
|||||||
}),
|
}),
|
||||||
skipAuthRedirect: true,
|
skipAuthRedirect: true,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const detail = error instanceof Error ? error.message : String(error);
|
||||||
|
onEvent({
|
||||||
|
type: "error",
|
||||||
|
message: "network request failed",
|
||||||
|
detail,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok || !response.body) {
|
if (!response.ok || !response.body) {
|
||||||
const detail = await response.text();
|
const detail = await response.text();
|
||||||
|
|||||||
Reference in New Issue
Block a user