refactor(frontend): Move OttoChatWidget out of root layout (#9951)

- Resolves #9950

### Changes 🏗️

- Move `<OttoChatWidget>` from root layout into `FlowEditor`
- Pass graph info directly into `OttoChatWidget` instead of using
`useAgentGraph`
- Rearrange z-indices of elements in the builder

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - Go to `/build`
  - [x] -> chat widget should show up in the bottom right corner
  - Open the widget and ask Otto something
  - [x] -> should work normally
  - Add a few blocks and save the graph
  - [x] -> "Include graph data" should show up
  - Click "Include graph data" and ask Otto something about your graph
  - [x] -> Otto should be aware of the graph structure and metadata
This commit is contained in:
Reinier van der Leer
2025-05-20 15:38:09 +02:00
committed by GitHub
parent 0a79e1c5fd
commit cf9cf4e7dd
5 changed files with 49 additions and 34 deletions

View File

@@ -10,7 +10,6 @@ import "./globals.css";
import { Toaster } from "@/components/ui/toaster";
import { Providers } from "@/app/providers";
import TallyPopupSimple from "@/components/TallyPopup";
import OttoChatWidget from "@/components/OttoChatWidget";
import { GoogleAnalytics } from "@/components/analytics/google-analytics";
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
@@ -57,9 +56,6 @@ export default async function RootLayout({
<div className="flex min-h-screen flex-col items-stretch justify-items-stretch">
{children}
<TallyPopupSimple />
<Suspense fallback={null}>
<OttoChatWidget />
</Suspense>
</div>
<Toaster />
</Providers>

View File

@@ -1,11 +1,12 @@
"use client";
import React, {
createContext,
useState,
useCallback,
useEffect,
useRef,
MouseEvent,
createContext,
Suspense,
} from "react";
import {
ReactFlow,
@@ -48,6 +49,7 @@ import RunnerUIWrapper, {
RunnerUIWrapperRef,
} from "@/components/RunnerUIWrapper";
import PrimaryActionBar from "@/components/PrimaryActionButton";
import OttoChatWidget from "@/components/OttoChatWidget";
import { useToast } from "@/components/ui/use-toast";
import { useCopyPaste } from "../hooks/useCopyPaste";
import { CronScheduler } from "./cronScheduler";
@@ -676,7 +678,7 @@ const FlowEditor: React.FC<{
<Controls />
<Background className="dark:bg-slate-800" />
<ControlPanel
className="absolute z-10"
className="absolute z-20"
controls={editorControls}
topChildren={
<BlocksControl
@@ -701,6 +703,7 @@ const FlowEditor: React.FC<{
}
></ControlPanel>
<PrimaryActionBar
className="absolute bottom-0 left-1/2 z-20 -translate-x-1/2"
onClickAgentOutputs={() => runnerUIRef.current?.openRunnerOutput()}
onClickRunAgent={() => {
if (!savedAgent) {
@@ -740,6 +743,12 @@ const FlowEditor: React.FC<{
scheduleRunner={scheduleRunner}
requestSaveAndRun={requestSaveAndRun}
/>
<Suspense fallback={null}>
<OttoChatWidget
graphID={flowID}
className="fixed bottom-4 right-4 z-20"
/>
</Suspense>
</FlowContext.Provider>
);
};

View File

@@ -1,32 +1,30 @@
"use client";
import React, { useEffect, useState, useRef } from "react";
import { useSearchParams, usePathname } from "next/navigation";
import { useToast } from "@/components/ui/use-toast";
import useAgentGraph from "../hooks/useAgentGraph";
import ReactMarkdown from "react-markdown";
import { GraphID } from "@/lib/autogpt-server-api/types";
import type { GraphID } from "@/lib/autogpt-server-api/types";
import { askOtto } from "@/app/(platform)/build/actions";
import { cn } from "@/lib/utils";
interface Message {
type: "user" | "assistant";
content: string;
}
const OttoChatWidget = () => {
export default function OttoChatWidget({
graphID,
className,
}: {
graphID?: GraphID;
className?: string;
}): React.ReactNode {
const [isOpen, setIsOpen] = useState(false);
const [messages, setMessages] = useState<Message[]>([]);
const [inputValue, setInputValue] = useState("");
const [isProcessing, setIsProcessing] = useState(false);
const [includeGraphData, setIncludeGraphData] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const searchParams = useSearchParams();
const pathname = usePathname();
const flowID = searchParams.get("flowID");
const { nodes, edges } = useAgentGraph(
flowID ? (flowID as GraphID) : undefined,
);
const { toast } = useToast();
useEffect(() => {
// Add welcome message when component mounts
@@ -34,7 +32,7 @@ const OttoChatWidget = () => {
setMessages([
{
type: "assistant",
content: "Hello im Otto! Ask me anything about AutoGPT!",
content: "Hello, I am Otto! Ask me anything about AutoGPT!",
},
]);
}
@@ -84,7 +82,7 @@ const OttoChatWidget = () => {
userMessage,
conversationHistory,
includeGraphData,
flowID || undefined,
graphID,
);
// Check if the response contains an error
@@ -131,13 +129,13 @@ const OttoChatWidget = () => {
};
// Don't render the chat widget if we're not on the build page or in local mode
if (process.env.NEXT_PUBLIC_BEHAVE_AS !== "CLOUD" || pathname !== "/build") {
if (process.env.NEXT_PUBLIC_BEHAVE_AS !== "CLOUD") {
return null;
}
if (!isOpen) {
return (
<div className="fixed bottom-4 right-4 z-50">
<div className={className}>
<button
onClick={() => setIsOpen(true)}
className="inline-flex h-14 w-14 items-center justify-center whitespace-nowrap rounded-2xl bg-[rgba(65,65,64,1)] text-neutral-50 shadow transition-colors hover:bg-neutral-900/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90 dark:focus-visible:ring-neutral-300"
@@ -160,7 +158,13 @@ const OttoChatWidget = () => {
}
return (
<div className="fixed bottom-4 right-4 z-50 flex h-[600px] w-[600px] flex-col rounded-lg border bg-background shadow-xl">
<div
className={cn(
"flex h-[600px] w-[600px] flex-col rounded-lg border bg-background shadow-xl",
className,
"z-40",
)}
>
{/* Header */}
<div className="flex items-center justify-between border-b p-4">
<h2 className="font-semibold">Otto Assistant</h2>
@@ -269,7 +273,7 @@ const OttoChatWidget = () => {
Send
</button>
</div>
{nodes && edges && (
{graphID && (
<button
type="button"
onClick={() => {
@@ -303,6 +307,4 @@ const OttoChatWidget = () => {
</form>
</div>
);
};
export default OttoChatWidget;
}

View File

@@ -1,13 +1,14 @@
import React, { useState } from "react";
import { Button } from "./ui/button";
import { Clock, LogOut, ChevronLeft } from "lucide-react";
import React from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { FaSpinner } from "react-icons/fa";
import { Clock, LogOut } from "lucide-react";
import { IconPlay, IconSquare } from "@/components/ui/icons";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { FaSpinner } from "react-icons/fa";
interface PrimaryActionBarProps {
onClickAgentOutputs: () => void;
@@ -18,6 +19,7 @@ interface PrimaryActionBarProps {
isScheduling: boolean;
requestStopRun: () => void;
runAgentTooltip: string;
className?: string;
}
const PrimaryActionBar: React.FC<PrimaryActionBarProps> = ({
@@ -29,6 +31,7 @@ const PrimaryActionBar: React.FC<PrimaryActionBarProps> = ({
isScheduling,
requestStopRun,
runAgentTooltip,
className,
}) => {
const runButtonLabel = !isRunning ? "Run" : "Stop";
@@ -37,8 +40,13 @@ const PrimaryActionBar: React.FC<PrimaryActionBarProps> = ({
const runButtonOnClick = !isRunning ? onClickRunAgent : requestStopRun;
return (
<div className="absolute bottom-0 left-1/2 z-50 flex w-fit -translate-x-1/2 transform select-none items-center justify-center p-4">
<div className={`flex gap-1 md:gap-4`}>
<div
className={cn(
"flex w-fit select-none items-center justify-center p-4",
className,
)}
>
<div className="flex gap-1 md:gap-4">
<Tooltip key="ViewOutputs" delayDuration={500}>
<TooltipTrigger asChild>
<Button

View File

@@ -56,7 +56,7 @@ const TallyPopupSimple = () => {
};
return (
<div className="fixed bottom-1 right-24 z-50 hidden select-none items-center gap-4 p-3 transition-all duration-300 ease-in-out md:flex">
<div className="fixed bottom-1 right-24 z-20 hidden select-none items-center gap-4 p-3 transition-all duration-300 ease-in-out md:flex">
{show_tutorial && (
<Button
variant="default"