mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-09 14:25:25 -05:00
chore: more fixes
This commit is contained in:
@@ -15,6 +15,7 @@ export interface ChatContainerProps {
|
||||
isCreatingSession: boolean;
|
||||
onCreateSession: () => void | Promise<string>;
|
||||
onSend: (message: string) => void | Promise<void>;
|
||||
onStop: () => void;
|
||||
}
|
||||
export const ChatContainer = ({
|
||||
messages,
|
||||
@@ -25,6 +26,7 @@ export const ChatContainer = ({
|
||||
isCreatingSession,
|
||||
onCreateSession,
|
||||
onSend,
|
||||
onStop,
|
||||
}: ChatContainerProps) => {
|
||||
const inputLayoutId = "copilot-2-chat-input";
|
||||
|
||||
@@ -52,7 +54,7 @@ export const ChatContainer = ({
|
||||
onSend={onSend}
|
||||
disabled={status === "streaming"}
|
||||
isStreaming={status === "streaming"}
|
||||
onStop={() => {}}
|
||||
onStop={onStop}
|
||||
placeholder="What else can I help with?"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
.loader {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
transform: rotateZ(45deg);
|
||||
perspective: 1000px;
|
||||
border-radius: 50%;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.loader::before,
|
||||
.loader::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
border-radius: 50%;
|
||||
transform: rotateX(70deg);
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.loader::after {
|
||||
color: var(--spinner-accent, #a855f7);
|
||||
transform: rotateY(70deg);
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0.2em 0 0 0 currentColor;
|
||||
}
|
||||
12% {
|
||||
box-shadow: 0.2em 0.2em 0 0 currentColor;
|
||||
}
|
||||
25% {
|
||||
box-shadow: 0 0.2em 0 0 currentColor;
|
||||
}
|
||||
37% {
|
||||
box-shadow: -0.2em 0.2em 0 0 currentColor;
|
||||
}
|
||||
50% {
|
||||
box-shadow: -0.2em 0 0 0 currentColor;
|
||||
}
|
||||
62% {
|
||||
box-shadow: -0.2em -0.2em 0 0 currentColor;
|
||||
}
|
||||
75% {
|
||||
box-shadow: 0 -0.2em 0 0 currentColor;
|
||||
}
|
||||
87% {
|
||||
box-shadow: 0.2em -0.2em 0 0 currentColor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import styles from "./SpinnerLoader.module.css";
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function SpinnerLoader({ size = 24, className }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={cn(styles.loader, className)}
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export default function Page() {
|
||||
messages,
|
||||
status,
|
||||
error,
|
||||
stop,
|
||||
isLoadingSession,
|
||||
isCreatingSession,
|
||||
createSession,
|
||||
@@ -47,6 +48,7 @@ export default function Page() {
|
||||
isCreatingSession={isCreatingSession}
|
||||
onCreateSession={createSession}
|
||||
onSend={onSend}
|
||||
onStop={stop}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
WarningDiamondIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { PulseLoader } from "../../components/PulseLoader/PulseLoader";
|
||||
import { SpinnerLoader } from "../../components/SpinnerLoader/SpinnerLoader";
|
||||
|
||||
export interface RunAgentInput {
|
||||
username_agent_slug?: string;
|
||||
@@ -171,7 +171,7 @@ export function ToolIcon({
|
||||
);
|
||||
}
|
||||
if (isStreaming) {
|
||||
return <PulseLoader size={40} className="text-neutral-700" />;
|
||||
return <SpinnerLoader size={40} className="text-neutral-700" />;
|
||||
}
|
||||
return <PlayIcon size={14} weight="regular" className="text-neutral-400" />;
|
||||
}
|
||||
@@ -203,7 +203,7 @@ export function getAccordionMeta(output: RunAgentToolOutput): {
|
||||
? output.status.trim()
|
||||
: "started";
|
||||
return {
|
||||
icon: <PulseLoader size={28} className="text-neutral-700" />,
|
||||
icon: <SpinnerLoader size={28} className="text-neutral-700" />,
|
||||
title: output.graph_name,
|
||||
description: `Status: ${statusText}`,
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
WarningDiamondIcon,
|
||||
} from "@phosphor-icons/react";
|
||||
import type { ToolUIPart } from "ai";
|
||||
import { PulseLoader } from "../../components/PulseLoader/PulseLoader";
|
||||
import { SpinnerLoader } from "../../components/SpinnerLoader/SpinnerLoader";
|
||||
|
||||
export interface RunBlockInput {
|
||||
block_id?: string;
|
||||
@@ -120,7 +120,7 @@ export function ToolIcon({
|
||||
);
|
||||
}
|
||||
if (isStreaming) {
|
||||
return <PulseLoader size={40} className="text-neutral-700" />;
|
||||
return <SpinnerLoader size={40} className="text-neutral-700" />;
|
||||
}
|
||||
return <PlayIcon size={14} weight="regular" className="text-neutral-400" />;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export function getAccordionMeta(output: RunBlockToolOutput): {
|
||||
if (isRunBlockBlockOutput(output)) {
|
||||
const keys = Object.keys(output.outputs ?? {});
|
||||
return {
|
||||
icon: <PulseLoader size={32} className="text-neutral-700" />,
|
||||
icon: <SpinnerLoader size={32} className="text-neutral-700" />,
|
||||
title: output.block_name,
|
||||
description:
|
||||
keys.length > 0
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import {
|
||||
getGetV2GetSessionQueryKey,
|
||||
getGetV2ListSessionsQueryKey,
|
||||
useGetV2GetSession,
|
||||
usePostV2CreateSession,
|
||||
} from "@/app/api/__generated__/endpoints/chat/chat";
|
||||
import { toast } from "@/components/molecules/Toast/use-toast";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { parseAsString, useQueryState } from "nuqs";
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { convertChatSessionMessagesToUiMessages } from "./helpers/convertChatSessionToUiMessages";
|
||||
|
||||
export function useChatSession() {
|
||||
@@ -14,12 +17,28 @@ export function useChatSession() {
|
||||
|
||||
const sessionQuery = useGetV2GetSession(sessionId ?? "", {
|
||||
query: {
|
||||
enabled: !!sessionId,
|
||||
staleTime: Infinity,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
},
|
||||
});
|
||||
|
||||
// When the user navigates away from a session, invalidate its query cache.
|
||||
// useChat destroys its Chat instance on id change, so messages are lost.
|
||||
// Invalidating ensures the next visit fetches fresh data from the API
|
||||
// instead of hydrating from stale cache that's missing recent messages.
|
||||
const prevSessionIdRef = useRef(sessionId);
|
||||
useEffect(() => {
|
||||
const prev = prevSessionIdRef.current;
|
||||
prevSessionIdRef.current = sessionId;
|
||||
if (prev && prev !== sessionId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: getGetV2GetSessionQueryKey(prev),
|
||||
});
|
||||
}
|
||||
}, [sessionId, queryClient]);
|
||||
|
||||
// Memoize so the effect in useCopilotPage doesn't infinite-loop on a new
|
||||
// array reference every render. Re-derives only when query data changes.
|
||||
const hydratedMessages = useMemo(() => {
|
||||
@@ -46,11 +65,36 @@ export function useChatSession() {
|
||||
|
||||
async function createSession() {
|
||||
if (sessionId) return sessionId;
|
||||
const response = await createSessionMutation();
|
||||
if (response.status !== 200 || !response.data?.id) {
|
||||
throw new Error("Failed to create session");
|
||||
try {
|
||||
const response = await createSessionMutation();
|
||||
if (response.status !== 200 || !response.data?.id) {
|
||||
const error = new Error("Failed to create session");
|
||||
Sentry.captureException(error, {
|
||||
extra: { status: response.status },
|
||||
});
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Could not start a new chat session",
|
||||
description: "Please try again.",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
return response.data.id;
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message === "Failed to create session"
|
||||
) {
|
||||
throw error; // already handled above
|
||||
}
|
||||
Sentry.captureException(error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Could not start a new chat session",
|
||||
description: "Please try again.",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
return response.data.id;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useGetV2ListSessions } from "@/app/api/__generated__/endpoints/chat/cha
|
||||
import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { DefaultChatTransport } from "ai";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useChatSession } from "./useChatSession";
|
||||
|
||||
export function useCopilotPage() {
|
||||
@@ -22,38 +22,37 @@ export function useCopilotPage() {
|
||||
const isMobile =
|
||||
breakpoint === "base" || breakpoint === "sm" || breakpoint === "md";
|
||||
|
||||
const transport = sessionId
|
||||
? new DefaultChatTransport({
|
||||
api: `/api/chat/sessions/${sessionId}/stream`,
|
||||
prepareSendMessagesRequest: ({ messages }) => {
|
||||
const last = messages[messages.length - 1];
|
||||
return {
|
||||
body: {
|
||||
message: last.parts
|
||||
?.map((p) => (p.type === "text" ? p.text : ""))
|
||||
.join(""),
|
||||
is_user_message: last.role === "user",
|
||||
context: null,
|
||||
const transport = useMemo(
|
||||
() =>
|
||||
sessionId
|
||||
? new DefaultChatTransport({
|
||||
api: `/api/chat/sessions/${sessionId}/stream`,
|
||||
prepareSendMessagesRequest: ({ messages }) => {
|
||||
const last = messages[messages.length - 1];
|
||||
return {
|
||||
body: {
|
||||
message: last.parts
|
||||
?.map((p) => (p.type === "text" ? p.text : ""))
|
||||
.join(""),
|
||||
is_user_message: last.role === "user",
|
||||
context: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
// Resume uses GET on the same endpoint (no message param → backend resumes)
|
||||
prepareReconnectToStreamRequest: () => ({
|
||||
api: `/api/chat/sessions/${sessionId}/stream`,
|
||||
}),
|
||||
})
|
||||
: null;
|
||||
})
|
||||
: null,
|
||||
[sessionId],
|
||||
);
|
||||
|
||||
const { messages, sendMessage, status, error, setMessages } = useChat({
|
||||
const { messages, sendMessage, stop, status, error, setMessages } = useChat({
|
||||
id: sessionId ?? undefined,
|
||||
transport: transport ?? undefined,
|
||||
resume: !!sessionId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydratedMessages || hydratedMessages.length === 0) return;
|
||||
setMessages((prev) => {
|
||||
if (prev.length > hydratedMessages.length) return prev;
|
||||
if (prev.length >= hydratedMessages.length) return prev;
|
||||
return hydratedMessages;
|
||||
});
|
||||
}, [hydratedMessages, setMessages]);
|
||||
@@ -89,36 +88,34 @@ export function useCopilotPage() {
|
||||
const sessions =
|
||||
sessionsResponse?.status === 200 ? sessionsResponse.data.sessions : [];
|
||||
|
||||
const handleOpenDrawer = useCallback(() => {
|
||||
function handleOpenDrawer() {
|
||||
setIsDrawerOpen(true);
|
||||
}, []);
|
||||
}
|
||||
|
||||
const handleCloseDrawer = useCallback(() => {
|
||||
function handleCloseDrawer() {
|
||||
setIsDrawerOpen(false);
|
||||
}, []);
|
||||
}
|
||||
|
||||
const handleDrawerOpenChange = useCallback((open: boolean) => {
|
||||
function handleDrawerOpenChange(open: boolean) {
|
||||
setIsDrawerOpen(open);
|
||||
}, []);
|
||||
}
|
||||
|
||||
const handleSelectSession = useCallback(
|
||||
(id: string) => {
|
||||
setSessionId(id);
|
||||
if (isMobile) setIsDrawerOpen(false);
|
||||
},
|
||||
[setSessionId, isMobile],
|
||||
);
|
||||
function handleSelectSession(id: string) {
|
||||
setSessionId(id);
|
||||
if (isMobile) setIsDrawerOpen(false);
|
||||
}
|
||||
|
||||
const handleNewChat = useCallback(() => {
|
||||
function handleNewChat() {
|
||||
setSessionId(null);
|
||||
if (isMobile) setIsDrawerOpen(false);
|
||||
}, [setSessionId, isMobile]);
|
||||
}
|
||||
|
||||
return {
|
||||
sessionId,
|
||||
messages,
|
||||
status,
|
||||
error,
|
||||
stop,
|
||||
isLoadingSession,
|
||||
isCreatingSession,
|
||||
createSession,
|
||||
|
||||
Reference in New Issue
Block a user