mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Compare commits
7 Commits
ci/gate-e2
...
fix/copilo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95c6907ccd | ||
|
|
f4bc3c2012 | ||
|
|
f265ef8ac3 | ||
|
|
c79e6ff30a | ||
|
|
7db8bf161a | ||
|
|
84650d0f4d | ||
|
|
0467cb2e49 |
@@ -5,8 +5,12 @@ import { Button } from "@/components/atoms/Button/Button";
|
||||
import type { UserRateLimitResponse } from "@/app/api/__generated__/models/userRateLimitResponse";
|
||||
import { UsageBar } from "../../components/UsageBar";
|
||||
|
||||
/** Extend generated type with optional fields returned by the backend
|
||||
* but not yet present in the generated OpenAPI schema on this branch. */
|
||||
type RateLimitData = UserRateLimitResponse & { user_email?: string | null };
|
||||
|
||||
interface Props {
|
||||
data: UserRateLimitResponse;
|
||||
data: RateLimitData;
|
||||
onReset: (resetWeekly: boolean) => Promise<void>;
|
||||
/** Override the outer container classes (default: bordered card). */
|
||||
className?: string;
|
||||
|
||||
@@ -49,17 +49,23 @@ export function useRateLimitManager() {
|
||||
setRateLimitData(null);
|
||||
|
||||
try {
|
||||
// The backend accepts either user_id or email, but the generated type
|
||||
// only knows about user_id — cast to satisfy the compiler until the
|
||||
// OpenAPI spec on this branch is updated.
|
||||
const params = looksLikeEmail(trimmed)
|
||||
? { email: trimmed }
|
||||
? ({ email: trimmed } as unknown as { user_id: string })
|
||||
: { user_id: trimmed };
|
||||
const response = await getV2GetUserRateLimit(params);
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to fetch rate limit");
|
||||
}
|
||||
setRateLimitData(response.data);
|
||||
const data = response.data as typeof response.data & {
|
||||
user_email?: string | null;
|
||||
};
|
||||
setSelectedUser({
|
||||
user_id: response.data.user_id,
|
||||
user_email: response.data.user_email ?? response.data.user_id,
|
||||
user_id: data.user_id,
|
||||
user_email: data.user_email ?? data.user_id,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error fetching rate limit:", error);
|
||||
|
||||
@@ -95,7 +95,8 @@ export function useChatSession() {
|
||||
async function createSession() {
|
||||
if (sessionId) return sessionId;
|
||||
try {
|
||||
const response = await createSessionMutation({ data: null });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const response = await (createSessionMutation as any)({ data: null });
|
||||
if (response.status !== 200 || !response.data?.id) {
|
||||
const error = new Error("Failed to create session");
|
||||
Sentry.captureException(error, {
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
|
||||
const RECONNECT_BASE_DELAY_MS = 1_000;
|
||||
const RECONNECT_MAX_ATTEMPTS = 3;
|
||||
const STREAM_TIMEOUT_MS = 60_000;
|
||||
|
||||
/** Minimum time the page must have been hidden to trigger a wake re-sync. */
|
||||
const WAKE_RESYNC_THRESHOLD_MS = 30_000;
|
||||
@@ -102,6 +103,11 @@ export function useCopilotStream({
|
||||
// Set when the user explicitly clicks stop — prevents onError from
|
||||
// triggering a reconnect cycle for the resulting AbortError.
|
||||
const isUserStoppingRef = useRef(false);
|
||||
// Timer that fires when no SSE events arrive for STREAM_TIMEOUT_MS during
|
||||
// an active stream — auto-cancels the stream to avoid "Reasoning..." forever.
|
||||
const streamTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
// Ref to the latest stop() so the timeout callback never uses a stale closure.
|
||||
const stopRef = useRef<() => void>(() => {});
|
||||
// Set when all reconnect attempts are exhausted — prevents hasActiveStream
|
||||
// from keeping the UI blocked forever when the backend is slow to clear it.
|
||||
// Must be state (not ref) so that setting it triggers a re-render and
|
||||
@@ -245,8 +251,12 @@ export function useCopilotStream({
|
||||
// Wrap AI SDK's stop() to also cancel the backend executor task.
|
||||
// sdkStop() aborts the SSE fetch instantly (UI feedback), then we fire
|
||||
// the cancel API to actually stop the executor and wait for confirmation.
|
||||
// Also kept in stopRef so the stream-timeout callback always calls the
|
||||
// latest version without needing it in the effect dependency array.
|
||||
async function stop() {
|
||||
isUserStoppingRef.current = true;
|
||||
clearTimeout(streamTimeoutRef.current);
|
||||
streamTimeoutRef.current = undefined;
|
||||
sdkStop();
|
||||
// Resolve pending tool calls and inject a cancellation marker so the UI
|
||||
// shows "You manually stopped this chat" immediately (the backend writes
|
||||
@@ -295,6 +305,7 @@ export function useCopilotStream({
|
||||
});
|
||||
}
|
||||
}
|
||||
stopRef.current = stop;
|
||||
|
||||
// Keep a ref to sessionId so the async wake handler can detect staleness.
|
||||
const sessionIdRef = useRef(sessionId);
|
||||
@@ -375,6 +386,8 @@ export function useCopilotStream({
|
||||
useEffect(() => {
|
||||
clearTimeout(reconnectTimerRef.current);
|
||||
reconnectTimerRef.current = undefined;
|
||||
clearTimeout(streamTimeoutRef.current);
|
||||
streamTimeoutRef.current = undefined;
|
||||
reconnectAttemptsRef.current = 0;
|
||||
isReconnectScheduledRef.current = false;
|
||||
setIsReconnectScheduled(false);
|
||||
@@ -387,6 +400,8 @@ export function useCopilotStream({
|
||||
return () => {
|
||||
clearTimeout(reconnectTimerRef.current);
|
||||
reconnectTimerRef.current = undefined;
|
||||
clearTimeout(streamTimeoutRef.current);
|
||||
streamTimeoutRef.current = undefined;
|
||||
};
|
||||
}, [sessionId]);
|
||||
|
||||
@@ -468,6 +483,41 @@ export function useCopilotStream({
|
||||
}
|
||||
}, [hasActiveStream]);
|
||||
|
||||
// Stream timeout guard: if no SSE events arrive for STREAM_TIMEOUT_MS while
|
||||
// the stream is active, auto-cancel to avoid the UI stuck in "Reasoning..."
|
||||
// indefinitely (e.g. when the SSE connection dies silently without a
|
||||
// disconnect event).
|
||||
useEffect(() => {
|
||||
// rawMessages is intentionally in the dependency array: each SSE event
|
||||
// updates rawMessages, which re-runs this effect and resets the timer.
|
||||
// Referencing its length here satisfies the exhaustive-deps rule.
|
||||
void rawMessages.length;
|
||||
|
||||
const isActive = status === "streaming" || status === "submitted";
|
||||
if (!isActive) {
|
||||
clearTimeout(streamTimeoutRef.current);
|
||||
streamTimeoutRef.current = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(streamTimeoutRef.current);
|
||||
streamTimeoutRef.current = setTimeout(() => {
|
||||
streamTimeoutRef.current = undefined;
|
||||
toast({
|
||||
title: "Connection lost",
|
||||
description:
|
||||
"No response received — please try sending your message again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
stopRef.current();
|
||||
}, STREAM_TIMEOUT_MS);
|
||||
|
||||
return () => {
|
||||
clearTimeout(streamTimeoutRef.current);
|
||||
streamTimeoutRef.current = undefined;
|
||||
};
|
||||
}, [status, rawMessages]);
|
||||
|
||||
// True while reconnecting or backend has active stream but we haven't connected yet.
|
||||
// Suppressed when the user explicitly stopped or when all reconnect attempts
|
||||
// are exhausted — the backend may be slow to clear active_stream but the UI
|
||||
|
||||
Reference in New Issue
Block a user