mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Merge branch 'master' into aarushikansal/open-1426-server-setup-user-auth-add-user-table
This commit is contained in:
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -6,3 +6,5 @@ docs/_javascript/** linguist-vendored
|
||||
|
||||
# Exclude VCR cassettes from stats
|
||||
forge/tests/vcr_cassettes/**/**.y*ml linguist-generated
|
||||
|
||||
* text=auto
|
||||
10
.github/workflows/autogpt-server-ci.yml
vendored
10
.github/workflows/autogpt-server-ci.yml
vendored
@@ -39,8 +39,8 @@ jobs:
|
||||
if: matrix.db-platform == 'postgres'
|
||||
uses: ikalnytskyi/action-setup-postgres@v6
|
||||
with:
|
||||
username: ${{ secrets.DB_USER }}
|
||||
password: ${{ secrets.DB_PASS }}
|
||||
username: ${{ secrets.DB_USER || 'postgres' }}
|
||||
password: ${{ secrets.DB_PASS || 'postgres' }}
|
||||
database: postgres
|
||||
port: 5432
|
||||
id: postgres
|
||||
@@ -145,13 +145,13 @@ jobs:
|
||||
CI: true
|
||||
PLAIN_OUTPUT: True
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
DB_USER: ${{ secrets.DB_USER }}
|
||||
DB_PASS: ${{ secrets.DB_PASS }}
|
||||
DB_USER: ${{ secrets.DB_USER || 'postgres' }}
|
||||
DB_PASS: ${{ secrets.DB_PASS || 'postgres' }}
|
||||
DB_NAME: postgres
|
||||
DB_PORT: 5432
|
||||
RUN_ENV: local
|
||||
PORT: 8080
|
||||
DATABASE_URL: postgresql://${{ secrets.DB_USER }}:${{ secrets.DB_PASS }}@localhost:5432/${{ secrets.DB_NAME }}
|
||||
DATABASE_URL: postgresql://${{ secrets.DB_USER || 'postgres' }}:${{ secrets.DB_PASS || 'postgres' }}@localhost:5432/${{ secrets.DB_NAME || 'postgres'}}
|
||||
|
||||
# - name: Upload coverage reports to Codecov
|
||||
# uses: codecov/codecov-action@v4
|
||||
|
||||
@@ -215,4 +215,10 @@ If you would like to implement one of these blocks, open a pull request and we w
|
||||
- Read / Get most read books in a given month, year, etc from GoodReads or Amazon Books, etc
|
||||
- Get dates for specific shows across all streaming services
|
||||
- Suggest/Recommend/Get most watched shows in a given month, year, etc across all streaming platforms
|
||||
- Data analysis from xlsx data set
|
||||
- Gather via Excel or Google Sheets data > Sample the data randomly (sample block takes top X, bottom X, randomly, etc) > pass that to LLM Block to generate a script for analysis of the full data > Python block to run the script> making a loop back through LLM Fix Block on error > create chart/visualization (potentially in the code block?) > show the image as output (this may require frontend changes to show)
|
||||
- Tiktok video search and download
|
||||
|
||||
### Marketing
|
||||
|
||||
- Portfolio site design and enhancements
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { NavBar } from "@/components/NavBar";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import "./globals.css";
|
||||
import TallyPopupSimple from "@/components/TallyPopup";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
@@ -32,6 +33,7 @@ export default function RootLayout({
|
||||
<div className="flex flex-col min-h-screen ">
|
||||
<NavBar />
|
||||
<main className="flex-1 p-4 overflow-hidden">{children}</main>
|
||||
<TallyPopupSimple />
|
||||
</div>
|
||||
</Providers>
|
||||
</body>
|
||||
|
||||
@@ -1,66 +1,20 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import moment from "moment";
|
||||
import {
|
||||
ComposedChart,
|
||||
DefaultLegendContentProps,
|
||||
Legend,
|
||||
Line,
|
||||
ResponsiveContainer,
|
||||
Scatter,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
import AutoGPTServerAPI, {
|
||||
Graph,
|
||||
GraphMeta,
|
||||
NodeExecutionResult,
|
||||
safeCopyGraph,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ClockIcon,
|
||||
EnterIcon,
|
||||
ExitIcon,
|
||||
Pencil2Icon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import { cn, exportAsJSONFile, hashString } from "@/lib/utils";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { AgentImportForm } from "@/components/agent-import-form";
|
||||
AgentFlowList,
|
||||
FlowInfo,
|
||||
FlowRunInfo,
|
||||
FlowRunsList,
|
||||
FlowRunsStats,
|
||||
} from "@/components/monitor";
|
||||
|
||||
const Monitor = () => {
|
||||
const [flows, setFlows] = useState<GraphMeta[]>([]);
|
||||
@@ -165,19 +119,6 @@ const Monitor = () => {
|
||||
);
|
||||
};
|
||||
|
||||
type FlowRun = {
|
||||
id: string;
|
||||
graphID: string;
|
||||
graphVersion: number;
|
||||
status: "running" | "waiting" | "success" | "failed";
|
||||
startTime: number; // unix timestamp (ms)
|
||||
endTime: number; // unix timestamp (ms)
|
||||
duration: number; // seconds
|
||||
totalRunTime: number; // seconds
|
||||
|
||||
nodeExecutionResults: NodeExecutionResult[];
|
||||
};
|
||||
|
||||
function flowRunFromNodeExecutionResults(
|
||||
nodeExecutionResults: NodeExecutionResult[],
|
||||
): FlowRun {
|
||||
@@ -230,664 +171,4 @@ function flowRunFromNodeExecutionResults(
|
||||
};
|
||||
}
|
||||
|
||||
const AgentFlowList = ({
|
||||
flows,
|
||||
flowRuns,
|
||||
selectedFlow,
|
||||
onSelectFlow,
|
||||
className,
|
||||
}: {
|
||||
flows: GraphMeta[];
|
||||
flowRuns?: FlowRun[];
|
||||
selectedFlow: GraphMeta | null;
|
||||
onSelectFlow: (f: GraphMeta) => void;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [templates, setTemplates] = useState<GraphMeta[]>([]);
|
||||
const api = new AutoGPTServerAPI();
|
||||
useEffect(() => {
|
||||
api.listTemplates().then((templates) => setTemplates(templates));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader className="flex-row justify-between items-center space-x-3 space-y-0">
|
||||
<CardTitle>Agents</CardTitle>
|
||||
|
||||
<div className="flex items-center">
|
||||
{/* Split "Create" button */}
|
||||
<Button variant="outline" className="rounded-r-none" asChild>
|
||||
<Link href="/build">Create</Link>
|
||||
</Button>
|
||||
<Dialog>
|
||||
{/* https://ui.shadcn.com/docs/components/dialog#notes */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={"rounded-l-none border-l-0 px-2"}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem>
|
||||
<EnterIcon className="mr-2" /> Import from file
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
{templates.length > 0 && (
|
||||
<>
|
||||
{/* List of templates */}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Use a template</DropdownMenuLabel>
|
||||
{templates.map((template) => (
|
||||
<DropdownMenuItem
|
||||
key={template.id}
|
||||
onClick={() => {
|
||||
api
|
||||
.createGraph(template.id, template.version)
|
||||
.then((newGraph) => {
|
||||
window.location.href = `/build?flowID=${newGraph.id}`;
|
||||
});
|
||||
}}
|
||||
>
|
||||
{template.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DialogContent>
|
||||
<DialogHeader className="text-lg">
|
||||
Import an Agent (template) from a file
|
||||
</DialogHeader>
|
||||
<AgentImportForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
{/* <TableHead>Status</TableHead> */}
|
||||
{/* <TableHead>Last updated</TableHead> */}
|
||||
{flowRuns && (
|
||||
<TableHead className="md:hidden lg:table-cell">
|
||||
# of runs
|
||||
</TableHead>
|
||||
)}
|
||||
{flowRuns && <TableHead>Last run</TableHead>}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{flows
|
||||
.map((flow) => {
|
||||
let runCount = 0,
|
||||
lastRun: FlowRun | null = null;
|
||||
if (flowRuns) {
|
||||
const _flowRuns = flowRuns.filter(
|
||||
(r) => r.graphID == flow.id,
|
||||
);
|
||||
runCount = _flowRuns.length;
|
||||
lastRun =
|
||||
runCount == 0
|
||||
? null
|
||||
: _flowRuns.reduce((a, c) =>
|
||||
a.startTime > c.startTime ? a : c,
|
||||
);
|
||||
}
|
||||
return { flow, runCount, lastRun };
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (!a.lastRun && !b.lastRun) return 0;
|
||||
if (!a.lastRun) return 1;
|
||||
if (!b.lastRun) return -1;
|
||||
return b.lastRun.startTime - a.lastRun.startTime;
|
||||
})
|
||||
.map(({ flow, runCount, lastRun }) => (
|
||||
<TableRow
|
||||
key={flow.id}
|
||||
className="cursor-pointer"
|
||||
onClick={() => onSelectFlow(flow)}
|
||||
data-state={selectedFlow?.id == flow.id ? "selected" : null}
|
||||
>
|
||||
<TableCell>{flow.name}</TableCell>
|
||||
{/* <TableCell><FlowStatusBadge status={flow.status ?? "active"} /></TableCell> */}
|
||||
{/* <TableCell>
|
||||
{flow.updatedAt ?? "???"}
|
||||
</TableCell> */}
|
||||
{flowRuns && (
|
||||
<TableCell className="md:hidden lg:table-cell">
|
||||
{runCount}
|
||||
</TableCell>
|
||||
)}
|
||||
{flowRuns &&
|
||||
(!lastRun ? (
|
||||
<TableCell />
|
||||
) : (
|
||||
<TableCell title={moment(lastRun.startTime).toString()}>
|
||||
{moment(lastRun.startTime).fromNow()}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const FlowStatusBadge = ({
|
||||
status,
|
||||
}: {
|
||||
status: "active" | "disabled" | "failing";
|
||||
}) => (
|
||||
<Badge
|
||||
variant="default"
|
||||
className={
|
||||
status === "active"
|
||||
? "bg-green-500 dark:bg-green-600"
|
||||
: status === "failing"
|
||||
? "bg-red-500 dark:bg-red-700"
|
||||
: "bg-gray-500 dark:bg-gray-600"
|
||||
}
|
||||
>
|
||||
{status}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
const FlowRunsList: React.FC<{
|
||||
flows: GraphMeta[];
|
||||
runs: FlowRun[];
|
||||
className?: string;
|
||||
selectedRun?: FlowRun | null;
|
||||
onSelectRun: (r: FlowRun) => void;
|
||||
}> = ({ flows, runs, selectedRun, onSelectRun, className }) => (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle>Runs</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Agent</TableHead>
|
||||
<TableHead>Started</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Duration</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{runs.map((run) => (
|
||||
<TableRow
|
||||
key={run.id}
|
||||
className="cursor-pointer"
|
||||
onClick={() => onSelectRun(run)}
|
||||
data-state={selectedRun?.id == run.id ? "selected" : null}
|
||||
>
|
||||
<TableCell>
|
||||
{flows.find((f) => f.id == run.graphID)!.name}
|
||||
</TableCell>
|
||||
<TableCell>{moment(run.startTime).format("HH:mm")}</TableCell>
|
||||
<TableCell>
|
||||
<FlowRunStatusBadge status={run.status} />
|
||||
</TableCell>
|
||||
<TableCell>{formatDuration(run.duration)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
const FlowRunStatusBadge: React.FC<{
|
||||
status: FlowRun["status"];
|
||||
className?: string;
|
||||
}> = ({ status, className }) => (
|
||||
<Badge
|
||||
variant="default"
|
||||
className={cn(
|
||||
status === "running"
|
||||
? "bg-blue-500 dark:bg-blue-700"
|
||||
: status === "waiting"
|
||||
? "bg-yellow-500 dark:bg-yellow-600"
|
||||
: status === "success"
|
||||
? "bg-green-500 dark:bg-green-600"
|
||||
: "bg-red-500 dark:bg-red-700",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{status}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
const FlowInfo: React.FC<
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
flow: GraphMeta;
|
||||
flowRuns: FlowRun[];
|
||||
flowVersion?: number | "all";
|
||||
}
|
||||
> = ({ flow, flowRuns, flowVersion, ...props }) => {
|
||||
const api = new AutoGPTServerAPI();
|
||||
|
||||
const [flowVersions, setFlowVersions] = useState<Graph[] | null>(null);
|
||||
const [selectedVersion, setSelectedFlowVersion] = useState(
|
||||
flowVersion ?? "all",
|
||||
);
|
||||
const selectedFlowVersion: Graph | undefined = flowVersions?.find(
|
||||
(v) =>
|
||||
v.version == (selectedVersion == "all" ? flow.version : selectedVersion),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
api.getGraphAllVersions(flow.id).then((result) => setFlowVersions(result));
|
||||
}, [flow.id]);
|
||||
|
||||
return (
|
||||
<Card {...props}>
|
||||
<CardHeader className="flex-row justify-between space-y-0 space-x-3">
|
||||
<div>
|
||||
<CardTitle>
|
||||
{flow.name} <span className="font-light">v{flow.version}</span>
|
||||
</CardTitle>
|
||||
<p className="mt-2">
|
||||
Agent ID: <code>{flow.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-2">
|
||||
{(flowVersions?.length ?? 0) > 1 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">
|
||||
<ClockIcon className="mr-2" />
|
||||
{selectedVersion == "all"
|
||||
? "All versions"
|
||||
: `Version ${selectedVersion}`}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuLabel>Choose a version</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuRadioGroup
|
||||
value={String(selectedVersion)}
|
||||
onValueChange={(choice) =>
|
||||
setSelectedFlowVersion(
|
||||
choice == "all" ? choice : Number(choice),
|
||||
)
|
||||
}
|
||||
>
|
||||
<DropdownMenuRadioItem value="all">
|
||||
All versions
|
||||
</DropdownMenuRadioItem>
|
||||
{flowVersions?.map((v) => (
|
||||
<DropdownMenuRadioItem
|
||||
key={v.version}
|
||||
value={v.version.toString()}
|
||||
>
|
||||
Version {v.version}
|
||||
{v.is_active ? " (active)" : ""}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
<Link
|
||||
className={buttonVariants({ variant: "outline" })}
|
||||
href={`/build?flowID=${flow.id}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" /> Edit
|
||||
</Link>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="px-2.5"
|
||||
title="Export to a JSON-file"
|
||||
onClick={async () =>
|
||||
exportAsJSONFile(
|
||||
safeCopyGraph(
|
||||
flowVersions!.find(
|
||||
(v) => v.version == selectedFlowVersion!.version,
|
||||
)!,
|
||||
await api.getBlocks(),
|
||||
),
|
||||
`${flow.name}_v${selectedFlowVersion!.version}.json`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<ExitIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FlowRunsStats
|
||||
flows={[selectedFlowVersion ?? flow]}
|
||||
flowRuns={flowRuns.filter(
|
||||
(r) =>
|
||||
r.graphID == flow.id &&
|
||||
(selectedVersion == "all" || r.graphVersion == selectedVersion),
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const FlowRunInfo: React.FC<
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
flow: GraphMeta;
|
||||
flowRun: FlowRun;
|
||||
}
|
||||
> = ({ flow, flowRun, ...props }) => {
|
||||
if (flowRun.graphID != flow.id) {
|
||||
throw new Error(
|
||||
`FlowRunInfo can't be used with non-matching flowRun.flowID and flow.id`,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card {...props}>
|
||||
<CardHeader className="flex-row items-center justify-between space-y-0 space-x-3">
|
||||
<div>
|
||||
<CardTitle>
|
||||
{flow.name} <span className="font-light">v{flow.version}</span>
|
||||
</CardTitle>
|
||||
<p className="mt-2">
|
||||
Agent ID: <code>{flow.id}</code>
|
||||
</p>
|
||||
<p className="mt-1">
|
||||
Run ID: <code>{flowRun.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
className={buttonVariants({ variant: "outline" })}
|
||||
href={`/build?flowID=${flow.id}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" /> Edit Agent
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>
|
||||
<strong>Status:</strong>{" "}
|
||||
<FlowRunStatusBadge status={flowRun.status} />
|
||||
</p>
|
||||
<p>
|
||||
<strong>Started:</strong>{" "}
|
||||
{moment(flowRun.startTime).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Finished:</strong>{" "}
|
||||
{moment(flowRun.endTime).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Duration (run time):</strong> {flowRun.duration} (
|
||||
{flowRun.totalRunTime}) seconds
|
||||
</p>
|
||||
{/* <p><strong>Total cost:</strong> €1,23</p> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const FlowRunsStats: React.FC<{
|
||||
flows: GraphMeta[];
|
||||
flowRuns: FlowRun[];
|
||||
title?: string;
|
||||
className?: string;
|
||||
}> = ({ flows, flowRuns, title, className }) => {
|
||||
/* "dateMin": since the first flow in the dataset
|
||||
* number > 0: custom date (unix timestamp)
|
||||
* number < 0: offset relative to Date.now() (in seconds) */
|
||||
const [statsSince, setStatsSince] = useState<number | "dataMin">(-24 * 3600);
|
||||
const statsSinceTimestamp = // unix timestamp or null
|
||||
typeof statsSince == "string"
|
||||
? null
|
||||
: statsSince < 0
|
||||
? Date.now() + statsSince * 1000
|
||||
: statsSince;
|
||||
const filteredFlowRuns =
|
||||
statsSinceTimestamp != null
|
||||
? flowRuns.filter((fr) => fr.startTime > statsSinceTimestamp)
|
||||
: flowRuns;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<CardTitle>{title || "Stats"}</CardTitle>
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setStatsSince(-2 * 3600)}
|
||||
>
|
||||
2h
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setStatsSince(-8 * 3600)}
|
||||
>
|
||||
8h
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setStatsSince(-24 * 3600)}
|
||||
>
|
||||
24h
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setStatsSince(-7 * 24 * 3600)}
|
||||
>
|
||||
7d
|
||||
</Button>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant={"outline"} size="sm">
|
||||
Custom
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
onSelect={(_, selectedDay) =>
|
||||
setStatsSince(selectedDay.getTime())
|
||||
}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setStatsSince("dataMin")}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<FlowRunsTimeline
|
||||
flows={flows}
|
||||
flowRuns={flowRuns}
|
||||
dataMin={statsSince}
|
||||
className="mt-3"
|
||||
/>
|
||||
<hr className="my-4" />
|
||||
<div>
|
||||
<p>
|
||||
<strong>Total runs:</strong> {filteredFlowRuns.length}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Total run time:</strong>{" "}
|
||||
{filteredFlowRuns.reduce((total, run) => total + run.totalRunTime, 0)}{" "}
|
||||
seconds
|
||||
</p>
|
||||
{/* <p><strong>Total cost:</strong> €1,23</p> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FlowRunsTimeline = ({
|
||||
flows,
|
||||
flowRuns,
|
||||
dataMin,
|
||||
className,
|
||||
}: {
|
||||
flows: GraphMeta[];
|
||||
flowRuns: FlowRun[];
|
||||
dataMin: "dataMin" | number;
|
||||
className?: string;
|
||||
}) => (
|
||||
/* TODO: make logarithmic? */
|
||||
<ResponsiveContainer width="100%" height={120} className={className}>
|
||||
<ComposedChart>
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
type="number"
|
||||
domain={[
|
||||
typeof dataMin == "string"
|
||||
? dataMin
|
||||
: dataMin < 0
|
||||
? Date.now() + dataMin * 1000
|
||||
: dataMin,
|
||||
Date.now(),
|
||||
]}
|
||||
allowDataOverflow={true}
|
||||
tickFormatter={(unixTime) => {
|
||||
const now = moment();
|
||||
const time = moment(unixTime);
|
||||
return now.diff(time, "hours") < 24
|
||||
? time.format("HH:mm")
|
||||
: time.format("YYYY-MM-DD HH:mm");
|
||||
}}
|
||||
name="Time"
|
||||
scale="time"
|
||||
/>
|
||||
<YAxis
|
||||
dataKey="_duration"
|
||||
name="Duration (s)"
|
||||
tickFormatter={(s) => (s > 90 ? `${Math.round(s / 60)}m` : `${s}s`)}
|
||||
/>
|
||||
<Tooltip
|
||||
content={({ payload, label }) => {
|
||||
if (payload && payload.length) {
|
||||
const data: FlowRun & { time: number; _duration: number } =
|
||||
payload[0].payload;
|
||||
const flow = flows.find((f) => f.id === data.graphID);
|
||||
return (
|
||||
<Card className="p-2 text-xs leading-normal">
|
||||
<p>
|
||||
<strong>Agent:</strong> {flow ? flow.name : "Unknown"}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Status:</strong>
|
||||
<FlowRunStatusBadge
|
||||
status={data.status}
|
||||
className="px-1.5 py-0"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Started:</strong>{" "}
|
||||
{moment(data.startTime).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Duration / run time:</strong>{" "}
|
||||
{formatDuration(data.duration)} /{" "}
|
||||
{formatDuration(data.totalRunTime)}
|
||||
</p>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
{flows.map((flow) => (
|
||||
<Scatter
|
||||
key={flow.id}
|
||||
data={flowRuns
|
||||
.filter((fr) => fr.graphID == flow.id)
|
||||
.map((fr) => ({
|
||||
...fr,
|
||||
time: fr.startTime + fr.totalRunTime * 1000,
|
||||
_duration: fr.totalRunTime,
|
||||
}))}
|
||||
name={flow.name}
|
||||
fill={`hsl(${(hashString(flow.id) * 137.5) % 360}, 70%, 50%)`}
|
||||
/>
|
||||
))}
|
||||
{flowRuns.map((run) => (
|
||||
<Line
|
||||
key={run.id}
|
||||
type="linear"
|
||||
dataKey="_duration"
|
||||
data={[
|
||||
{ ...run, time: run.startTime, _duration: 0 },
|
||||
{ ...run, time: run.endTime, _duration: run.totalRunTime },
|
||||
]}
|
||||
stroke={`hsl(${(hashString(run.graphID) * 137.5) % 360}, 70%, 50%)`}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
legendType="none"
|
||||
/>
|
||||
))}
|
||||
<Legend
|
||||
content={<ScrollableLegend />}
|
||||
wrapperStyle={{
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
|
||||
const ScrollableLegend: React.FC<
|
||||
DefaultLegendContentProps & { className?: string }
|
||||
> = ({ payload, className }) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"whitespace-nowrap px-4 text-sm overflow-x-auto space-x-3",
|
||||
className,
|
||||
)}
|
||||
style={{ scrollbarWidth: "none" }}
|
||||
>
|
||||
{payload.map((entry, index) => {
|
||||
if (entry.type == "none") return;
|
||||
return (
|
||||
<span key={`item-${index}`} className="inline-flex items-center">
|
||||
<span
|
||||
className="size-2.5 inline-block mr-1 rounded-full"
|
||||
style={{ backgroundColor: entry.color }}
|
||||
/>
|
||||
<span>{entry.value}</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function formatDuration(seconds: number): string {
|
||||
return (
|
||||
(seconds < 100 ? seconds.toPrecision(2) : Math.round(seconds)).toString() +
|
||||
"s"
|
||||
);
|
||||
}
|
||||
|
||||
export default Monitor;
|
||||
|
||||
@@ -32,9 +32,8 @@ const CustomEdgeFC: FC<EdgeProps<CustomEdgeData>> = ({
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const { setEdges } = useReactFlow();
|
||||
|
||||
const onEdgeClick = () => {
|
||||
const onEdgeRemoveClick = () => {
|
||||
setEdges((edges) => edges.filter((edge) => edge.id !== id));
|
||||
data.clearNodesStatusAndOutput();
|
||||
};
|
||||
|
||||
const [path, labelX, labelY] = getBezierPath({
|
||||
@@ -105,7 +104,7 @@ const CustomEdgeFC: FC<EdgeProps<CustomEdgeData>> = ({
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className={`edge-label-button ${isHovered ? "visible" : ""}`}
|
||||
onClick={onEdgeClick}
|
||||
onClick={onEdgeRemoveClick}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
|
||||
@@ -15,13 +15,15 @@ import {
|
||||
BlockIORootSchema,
|
||||
NodeExecutionResult,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import { BlockSchema } from "@/lib/types";
|
||||
import { beautifyString, setNestedProperty } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import NodeHandle from "./NodeHandle";
|
||||
import NodeInputField from "./NodeInputField";
|
||||
import { Copy, Trash2 } from "lucide-react";
|
||||
import { history } from "./history";
|
||||
import NodeHandle from "./NodeHandle";
|
||||
import { NodeGenericInputField } from "./node-input-components";
|
||||
|
||||
type ParsedKey = { key: string; index?: number };
|
||||
|
||||
export type CustomNodeData = {
|
||||
blockType: string;
|
||||
@@ -37,8 +39,8 @@ export type CustomNodeData = {
|
||||
targetHandle: string;
|
||||
}>;
|
||||
isOutputOpen: boolean;
|
||||
status?: string;
|
||||
output_data?: any;
|
||||
status?: NodeExecutionResult["status"];
|
||||
output_data?: NodeExecutionResult["output_data"];
|
||||
block_id: string;
|
||||
backend_id?: string;
|
||||
errors?: { [key: string]: string | null };
|
||||
@@ -110,16 +112,30 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
));
|
||||
};
|
||||
|
||||
const handleInputChange = (key: string, value: any) => {
|
||||
const keys = key.split(".");
|
||||
const handleInputChange = (path: string, value: any) => {
|
||||
const keys = parseKeys(path);
|
||||
const newValues = JSON.parse(JSON.stringify(data.hardcodedValues));
|
||||
let current = newValues;
|
||||
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
if (!current[keys[i]]) current[keys[i]] = {};
|
||||
current = current[keys[i]];
|
||||
const { key: currentKey, index } = keys[i];
|
||||
if (index !== undefined) {
|
||||
if (!current[currentKey]) current[currentKey] = [];
|
||||
if (!current[currentKey][index]) current[currentKey][index] = {};
|
||||
current = current[currentKey][index];
|
||||
} else {
|
||||
if (!current[currentKey]) current[currentKey] = {};
|
||||
current = current[currentKey];
|
||||
}
|
||||
}
|
||||
|
||||
const lastKey = keys[keys.length - 1];
|
||||
if (lastKey.index !== undefined) {
|
||||
if (!current[lastKey.key]) current[lastKey.key] = [];
|
||||
current[lastKey.key][lastKey.index] = value;
|
||||
} else {
|
||||
current[lastKey.key] = value;
|
||||
}
|
||||
current[keys[keys.length - 1]] = value;
|
||||
|
||||
console.log(`Updating hardcoded values for node ${id}:`, newValues);
|
||||
|
||||
@@ -135,16 +151,49 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
data.setHardcodedValues(newValues);
|
||||
const errors = data.errors || {};
|
||||
// Remove error with the same key
|
||||
setNestedProperty(errors, key, null);
|
||||
setNestedProperty(errors, path, null);
|
||||
data.setErrors({ ...errors });
|
||||
};
|
||||
|
||||
// Helper function to parse keys with array indices
|
||||
const parseKeys = (key: string): ParsedKey[] => {
|
||||
const regex = /(\w+)|\[(\d+)\]/g;
|
||||
const keys: ParsedKey[] = [];
|
||||
let match;
|
||||
let currentKey: string | null = null;
|
||||
|
||||
while ((match = regex.exec(key)) !== null) {
|
||||
if (match[1]) {
|
||||
if (currentKey !== null) {
|
||||
keys.push({ key: currentKey });
|
||||
}
|
||||
currentKey = match[1];
|
||||
} else if (match[2]) {
|
||||
if (currentKey !== null) {
|
||||
keys.push({ key: currentKey, index: parseInt(match[2], 10) });
|
||||
currentKey = null;
|
||||
} else {
|
||||
throw new Error("Invalid key format: array index without a key");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentKey !== null) {
|
||||
keys.push({ key: currentKey });
|
||||
}
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
const getValue = (key: string) => {
|
||||
const keys = key.split(".");
|
||||
return keys.reduce(
|
||||
(acc, k) => (acc && acc[k] !== undefined ? acc[k] : ""),
|
||||
data.hardcodedValues,
|
||||
);
|
||||
const keys = parseKeys(key);
|
||||
return keys.reduce((acc, k) => {
|
||||
if (acc === undefined) return undefined;
|
||||
if (k.index !== undefined) {
|
||||
return Array.isArray(acc[k.key]) ? acc[k.key][k.index] : undefined;
|
||||
}
|
||||
return acc[k.key];
|
||||
}, data.hardcodedValues as any);
|
||||
};
|
||||
|
||||
const isHandleConnected = (key: string) => {
|
||||
@@ -208,12 +257,10 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
|
||||
const handleHovered = () => {
|
||||
setIsHovered(true);
|
||||
console.log("isHovered", isHovered);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsHovered(false);
|
||||
console.log("isHovered", isHovered);
|
||||
};
|
||||
|
||||
const deleteNode = useCallback(() => {
|
||||
@@ -274,58 +321,66 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
<div className="text-lg font-bold">
|
||||
{beautifyString(data.blockType?.replace(/Block$/, "") || data.title)}
|
||||
</div>
|
||||
<div className="node-actions">
|
||||
<div className="flex gap-[5px]">
|
||||
{isHovered && (
|
||||
<>
|
||||
<button
|
||||
className="node-action-button"
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={copyNode}
|
||||
title="Copy node"
|
||||
>
|
||||
<Copy size={18} />
|
||||
</button>
|
||||
<button
|
||||
className="node-action-button"
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={deleteNode}
|
||||
title="Delete node"
|
||||
>
|
||||
<Trash2 size={18} />
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="node-content">
|
||||
<div className="flex justify-between items-start gap-2">
|
||||
<div>
|
||||
{data.inputSchema &&
|
||||
Object.entries(data.inputSchema.properties).map(([key, schema]) => {
|
||||
const isRequired = data.inputSchema.required?.includes(key);
|
||||
return (
|
||||
(isRequired || isAdvancedOpen) && (
|
||||
<div key={key} onMouseOver={() => {}}>
|
||||
<NodeHandle
|
||||
keyName={key}
|
||||
isConnected={isHandleConnected(key)}
|
||||
isRequired={isRequired}
|
||||
schema={schema}
|
||||
side="left"
|
||||
/>
|
||||
{!isHandleConnected(key) && (
|
||||
<NodeInputField
|
||||
keyName={key}
|
||||
schema={schema}
|
||||
value={getValue(key)}
|
||||
handleInputClick={handleInputClick}
|
||||
handleInputChange={handleInputChange}
|
||||
errors={data.errors?.[key]}
|
||||
Object.entries(data.inputSchema.properties).map(
|
||||
([propKey, propSchema]) => {
|
||||
const isRequired = data.inputSchema.required?.includes(propKey);
|
||||
return (
|
||||
(isRequired || isAdvancedOpen) && (
|
||||
<div key={propKey} onMouseOver={() => {}}>
|
||||
<NodeHandle
|
||||
keyName={propKey}
|
||||
isConnected={isHandleConnected(propKey)}
|
||||
isRequired={isRequired}
|
||||
schema={propSchema}
|
||||
side="left"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
})}
|
||||
{!isHandleConnected(propKey) && (
|
||||
<NodeGenericInputField
|
||||
className="mt-1 mb-2"
|
||||
propKey={propKey}
|
||||
propSchema={propSchema}
|
||||
currentValue={getValue(propKey)}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
errors={data.errors ?? {}}
|
||||
displayName={
|
||||
propSchema.title || beautifyString(propKey)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex-none">
|
||||
{data.outputSchema && generateOutputHandles(data.outputSchema)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -355,14 +410,11 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center mt-2.5">
|
||||
<Switch onCheckedChange={toggleOutput} className="custom-switch" />
|
||||
<Switch onCheckedChange={toggleOutput} />
|
||||
<span className="m-1 mr-4">Output</span>
|
||||
{hasOptionalFields() && (
|
||||
<>
|
||||
<Switch
|
||||
onCheckedChange={toggleAdvancedSettings}
|
||||
className="custom-switch"
|
||||
/>
|
||||
<Switch onCheckedChange={toggleAdvancedSettings} />
|
||||
<span className="m-1">Advanced</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -23,7 +23,6 @@ import CustomNode, { CustomNodeData } from "./CustomNode";
|
||||
import "./flow.css";
|
||||
import AutoGPTServerAPI, {
|
||||
Block,
|
||||
BlockIOSchema,
|
||||
Graph,
|
||||
NodeExecutionResult,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
@@ -458,7 +457,6 @@ const FlowEditor: React.FC<{
|
||||
targetHandle: link.sink_name,
|
||||
})),
|
||||
isOutputOpen: false,
|
||||
setIsAnyModalOpen: setIsAnyModalOpen, // Pass setIsAnyModalOpen function
|
||||
setErrors: (errors: { [key: string]: string | null }) => {
|
||||
setNodes((nds) =>
|
||||
nds.map((node) =>
|
||||
@@ -502,11 +500,7 @@ const FlowEditor: React.FC<{
|
||||
);
|
||||
}
|
||||
|
||||
const prepareNodeInputData = (
|
||||
node: Node<CustomNodeData>,
|
||||
allNodes: Node<CustomNodeData>[],
|
||||
allEdges: Edge<CustomEdgeData>[],
|
||||
) => {
|
||||
const prepareNodeInputData = (node: Node<CustomNodeData>) => {
|
||||
console.log("Preparing input data for node:", node.id, node.data.blockType);
|
||||
|
||||
const blockSchema = availableNodes.find(
|
||||
@@ -519,7 +513,7 @@ const FlowEditor: React.FC<{
|
||||
}
|
||||
|
||||
const getNestedData = (
|
||||
schema: BlockIOSchema,
|
||||
schema: BlockIOSubSchema,
|
||||
values: { [key: string]: any },
|
||||
): { [key: string]: any } => {
|
||||
let inputData: { [key: string]: any } = {};
|
||||
@@ -580,7 +574,7 @@ const FlowEditor: React.FC<{
|
||||
const key = `${node.data.block_id}_${node.position.x}_${node.position.y}`;
|
||||
blockIdToNodeIdMap[key] = node.id;
|
||||
});
|
||||
const inputDefault = prepareNodeInputData(node, nodes, edges);
|
||||
const inputDefault = prepareNodeInputData(node);
|
||||
const inputNodes = edges
|
||||
.filter((edge) => edge.target === node.id)
|
||||
.map((edge) => ({
|
||||
@@ -685,7 +679,10 @@ const FlowEditor: React.FC<{
|
||||
// Populate errors if validation fails
|
||||
validate.errors?.forEach((error) => {
|
||||
// Skip error if there's an edge connected
|
||||
const path = error.instancePath || error.schemaPath;
|
||||
const path =
|
||||
"dataPath" in error
|
||||
? (error.dataPath as string)
|
||||
: error.instancePath;
|
||||
const handle = path.split(/[\/.]/)[0];
|
||||
if (
|
||||
node.data.connections.some(
|
||||
@@ -845,17 +842,17 @@ const FlowEditor: React.FC<{
|
||||
const editorControls: Control[] = [
|
||||
{
|
||||
label: "Undo",
|
||||
icon: <Undo2 />,
|
||||
icon: <Undo2 size={18} />,
|
||||
onClick: handleUndo,
|
||||
},
|
||||
{
|
||||
label: "Redo",
|
||||
icon: <Redo2 />,
|
||||
icon: <Redo2 size={18} />,
|
||||
onClick: handleRedo,
|
||||
},
|
||||
{
|
||||
label: "Run",
|
||||
icon: <Play />,
|
||||
icon: <Play size={18} />,
|
||||
onClick: runAgent,
|
||||
},
|
||||
];
|
||||
@@ -883,17 +880,15 @@ const FlowEditor: React.FC<{
|
||||
onNodeDragStart={onNodesChangeStart}
|
||||
onNodeDragStop={onNodesChangeEnd}
|
||||
>
|
||||
<div className={"flex flex-row absolute z-10 gap-2"}>
|
||||
<ControlPanel controls={editorControls}>
|
||||
<BlocksControl blocks={availableNodes} addBlock={addNode} />
|
||||
<SaveControl
|
||||
agentMeta={savedAgent}
|
||||
onSave={saveAgent}
|
||||
onDescriptionChange={setAgentDescription}
|
||||
onNameChange={setAgentName}
|
||||
/>
|
||||
</ControlPanel>
|
||||
</div>
|
||||
<ControlPanel className="absolute z-10" controls={editorControls}>
|
||||
<BlocksControl blocks={availableNodes} addBlock={addNode} />
|
||||
<SaveControl
|
||||
agentMeta={savedAgent}
|
||||
onSave={saveAgent}
|
||||
onDescriptionChange={setAgentDescription}
|
||||
onNameChange={setAgentName}
|
||||
/>
|
||||
</ControlPanel>
|
||||
</ReactFlow>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BlockIOSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { BlockIOSubSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { beautifyString, getTypeBgColor, getTypeTextColor } from "@/lib/utils";
|
||||
import { FC } from "react";
|
||||
import { Handle, Position } from "reactflow";
|
||||
@@ -6,7 +6,7 @@ import SchemaTooltip from "./SchemaTooltip";
|
||||
|
||||
type HandleProps = {
|
||||
keyName: string;
|
||||
schema: BlockIOSchema;
|
||||
schema: BlockIOSubSchema;
|
||||
isConnected: boolean;
|
||||
isRequired?: boolean;
|
||||
side: "left" | "right";
|
||||
@@ -28,7 +28,7 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
null: "null",
|
||||
};
|
||||
|
||||
const typeClass = `text-sm ${getTypeTextColor(schema.type)} ${side === "left" ? "text-left" : "text-right"}`;
|
||||
const typeClass = `text-sm ${getTypeTextColor(schema.type || "any")} ${side === "left" ? "text-left" : "text-right"}`;
|
||||
|
||||
const label = (
|
||||
<div className="flex flex-col flex-grow">
|
||||
@@ -36,13 +36,13 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
{schema.title || beautifyString(keyName)}
|
||||
{isRequired ? "*" : ""}
|
||||
</span>
|
||||
<span className={typeClass}>{typeName[schema.type]}</span>
|
||||
<span className={typeClass}>{typeName[schema.type] || "any"}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const dot = (
|
||||
<div
|
||||
className={`w-4 h-4 m-1 ${isConnected ? getTypeBgColor(schema.type) : "bg-gray-600"} rounded-full transition-colors duration-100 group-hover:bg-gray-300`}
|
||||
className={`w-4 h-4 m-1 ${isConnected ? getTypeBgColor(schema.type || "any") : "bg-gray-600"} rounded-full transition-colors duration-100 group-hover:bg-gray-300`}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -53,7 +53,7 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={keyName}
|
||||
className="group -ml-[29px]"
|
||||
className="group -ml-[26px]"
|
||||
>
|
||||
<div className="pointer-events-none flex items-center">
|
||||
{dot}
|
||||
@@ -70,7 +70,7 @@ const NodeHandle: FC<HandleProps> = ({
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={keyName}
|
||||
className="group -mr-[29px]"
|
||||
className="group -mr-[26px]"
|
||||
>
|
||||
<div className="pointer-events-none flex items-center">
|
||||
{label}
|
||||
|
||||
@@ -1,357 +0,0 @@
|
||||
import { Cross2Icon, PlusIcon } from "@radix-ui/react-icons";
|
||||
import { beautifyString } from "@/lib/utils";
|
||||
import { BlockIOSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { FC, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Input } from "./ui/input";
|
||||
|
||||
type BlockInputFieldProps = {
|
||||
keyName: string;
|
||||
schema: BlockIOSchema;
|
||||
parentKey?: string;
|
||||
value: string | Array<string> | { [key: string]: string };
|
||||
handleInputClick: (key: string) => void;
|
||||
handleInputChange: (key: string, value: any) => void;
|
||||
errors?: { [key: string]: string } | string | null;
|
||||
};
|
||||
|
||||
const NodeInputField: FC<BlockInputFieldProps> = ({
|
||||
keyName: key,
|
||||
schema,
|
||||
parentKey = "",
|
||||
value,
|
||||
handleInputClick,
|
||||
handleInputChange,
|
||||
errors,
|
||||
}) => {
|
||||
const fullKey = parentKey ? `${parentKey}.${key}` : key;
|
||||
const error = typeof errors === "string" ? errors : (errors?.[key] ?? "");
|
||||
const displayKey = schema.title || beautifyString(key);
|
||||
|
||||
const [keyValuePairs, _setKeyValuePairs] = useState<
|
||||
{ key: string; value: string }[]
|
||||
>(
|
||||
"additionalProperties" in schema && value
|
||||
? Object.entries(value).map(([key, value]) => ({
|
||||
key: key,
|
||||
value: value,
|
||||
}))
|
||||
: [],
|
||||
);
|
||||
|
||||
function setKeyValuePairs(newKVPairs: typeof keyValuePairs): void {
|
||||
_setKeyValuePairs(newKVPairs);
|
||||
handleInputChange(
|
||||
fullKey,
|
||||
newKVPairs.reduce(
|
||||
(obj, { key, value }) => ({ ...obj, [key]: value }),
|
||||
{},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const renderClickableInput = (
|
||||
value: string | null = null,
|
||||
placeholder: string = "",
|
||||
secret: boolean = false,
|
||||
) => {
|
||||
const className = `clickable-input ${error ? "border-error" : ""}`;
|
||||
|
||||
return secret ? (
|
||||
<div className={className} onClick={() => handleInputClick(fullKey)}>
|
||||
{value ? (
|
||||
<span>********</span>
|
||||
) : (
|
||||
<i className="text-gray-500">{placeholder}</i>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className={className} onClick={() => handleInputClick(fullKey)}>
|
||||
{value || <i className="text-gray-500">{placeholder}</i>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if ("properties" in schema) {
|
||||
return (
|
||||
<div key={fullKey} className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{Object.entries(schema.properties).map(([propKey, propSchema]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
<NodeInputField
|
||||
keyName={propKey}
|
||||
schema={propSchema}
|
||||
parentKey={fullKey}
|
||||
value={(value as { [key: string]: string })[propKey]}
|
||||
handleInputClick={handleInputClick}
|
||||
handleInputChange={handleInputChange}
|
||||
errors={errors}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.type === "object" && schema.additionalProperties) {
|
||||
return (
|
||||
<div key={fullKey}>
|
||||
<div>
|
||||
{keyValuePairs.map(({ key, value }, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center w-[325px] space-x-2 mb-2"
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Key"
|
||||
value={key}
|
||||
onChange={(e) =>
|
||||
setKeyValuePairs(
|
||||
keyValuePairs.toSpliced(index, 1, {
|
||||
key: e.target.value,
|
||||
value: value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
setKeyValuePairs(
|
||||
keyValuePairs.toSpliced(index, 1, {
|
||||
key: key,
|
||||
value: e.target.value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="px-2"
|
||||
onClick={() =>
|
||||
setKeyValuePairs(keyValuePairs.toSpliced(index, 1))
|
||||
}
|
||||
>
|
||||
<Cross2Icon />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() =>
|
||||
setKeyValuePairs(keyValuePairs.concat({ key: "", value: "" }))
|
||||
}
|
||||
>
|
||||
<PlusIcon className="mr-2" /> Add Property
|
||||
</Button>
|
||||
</div>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if ("anyOf" in schema) {
|
||||
const types = schema.anyOf.map((s) => ("type" in s ? s.type : undefined));
|
||||
if (types.includes("string") && types.includes("null")) {
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(
|
||||
value as string,
|
||||
schema.placeholder || `Enter ${displayKey} (optional)`,
|
||||
)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ("allOf" in schema) {
|
||||
return (
|
||||
<div key={fullKey} className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{"properties" in schema.allOf[0] &&
|
||||
Object.entries(schema.allOf[0].properties).map(
|
||||
([propKey, propSchema]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
<NodeInputField
|
||||
keyName={propKey}
|
||||
schema={propSchema}
|
||||
parentKey={fullKey}
|
||||
value={(value as { [key: string]: string })[propKey]}
|
||||
handleInputClick={handleInputClick}
|
||||
handleInputChange={handleInputChange}
|
||||
errors={errors}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if ("oneOf" in schema) {
|
||||
return (
|
||||
<div key={fullKey} className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{"properties" in schema.oneOf[0] &&
|
||||
Object.entries(schema.oneOf[0].properties).map(
|
||||
([propKey, propSchema]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
<NodeInputField
|
||||
keyName={propKey}
|
||||
schema={propSchema}
|
||||
parentKey={fullKey}
|
||||
value={(value as { [key: string]: string })[propKey]}
|
||||
handleInputClick={handleInputClick}
|
||||
handleInputChange={handleInputChange}
|
||||
errors={errors}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!("type" in schema)) {
|
||||
console.warn(`Schema for input ${key} does not specify a type:`, schema);
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(
|
||||
value as string,
|
||||
schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`,
|
||||
)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (schema.type) {
|
||||
case "string":
|
||||
if (schema.enum) {
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
<select
|
||||
value={(value as string) || ""}
|
||||
onChange={(e) => handleInputChange(fullKey, e.target.value)}
|
||||
className="select-input"
|
||||
>
|
||||
<option value="">Select {displayKey}</option>
|
||||
{schema.enum.map((option: string) => (
|
||||
<option key={option} value={option}>
|
||||
{beautifyString(option)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.secret) {
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(
|
||||
value as string,
|
||||
schema.placeholder || `Enter ${displayKey}`,
|
||||
true,
|
||||
)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(
|
||||
value as string,
|
||||
schema.placeholder || `Enter ${displayKey}`,
|
||||
)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
case "boolean":
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
<select
|
||||
value={value === undefined ? "" : value.toString()}
|
||||
onChange={(e) =>
|
||||
handleInputChange(fullKey, e.target.value === "true")
|
||||
}
|
||||
className="select-input"
|
||||
>
|
||||
<option value="">Select {displayKey}</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
case "number":
|
||||
case "integer":
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
<Input
|
||||
type="number"
|
||||
value={(value as string) || ""}
|
||||
onChange={(e) =>
|
||||
handleInputChange(fullKey, parseFloat(e.target.value))
|
||||
}
|
||||
className={`number-input ${error ? "border-error" : ""}`}
|
||||
/>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
case "array":
|
||||
if (schema.items && schema.items.type === "string") {
|
||||
const arrayValues = (value as Array<string>) || [];
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{arrayValues.map((item: string, index: number) => (
|
||||
<div key={`${fullKey}.${index}`} className="array-item-container">
|
||||
<Input
|
||||
type="text"
|
||||
value={item}
|
||||
onChange={(e) =>
|
||||
handleInputChange(`${fullKey}.${index}`, e.target.value)
|
||||
}
|
||||
className="array-item-input"
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handleInputChange(`${fullKey}.${index}`, "")}
|
||||
className="array-item-remove"
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => handleInputChange(fullKey, [...arrayValues, ""])}
|
||||
className="array-item-add"
|
||||
>
|
||||
Add Item
|
||||
</Button>
|
||||
{error && <span className="error-message ml-2">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
console.warn(`Schema for input ${key} specifies unknown type:`, schema);
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(
|
||||
value as string,
|
||||
schema.placeholder ||
|
||||
`Enter ${beautifyString(displayKey)} (Complex)`,
|
||||
)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default NodeInputField;
|
||||
@@ -4,11 +4,11 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { BlockIOSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { BlockIOSubSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { Info } from "lucide-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
const SchemaTooltip: React.FC<{ schema: BlockIOSchema }> = ({ schema }) => {
|
||||
const SchemaTooltip: React.FC<{ schema: BlockIOSubSchema }> = ({ schema }) => {
|
||||
if (!schema.description) return null;
|
||||
|
||||
return (
|
||||
|
||||
59
rnd/autogpt_builder/src/components/TallyPopup.tsx
Normal file
59
rnd/autogpt_builder/src/components/TallyPopup.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Megaphone } from "lucide-react";
|
||||
|
||||
const TallyPopupSimple = () => {
|
||||
const [isFormVisible, setIsFormVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Load Tally script
|
||||
const script = document.createElement("script");
|
||||
script.src = "https://tally.so/widgets/embed.js";
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
|
||||
// Setup event listeners for Tally events
|
||||
const handleTallyMessage = (event: MessageEvent) => {
|
||||
if (typeof event.data === "string") {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.event === "Tally.FormLoaded") {
|
||||
setIsFormVisible(true);
|
||||
} else if (data.event === "Tally.PopupClosed") {
|
||||
setIsFormVisible(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing Tally message:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handleTallyMessage);
|
||||
|
||||
return () => {
|
||||
document.head.removeChild(script);
|
||||
window.removeEventListener("message", handleTallyMessage);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (isFormVisible) {
|
||||
return null; // Hide the button when the form is visible
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-6 right-6 p-3 transition-all duration-300 ease-in-out z-50">
|
||||
<Button
|
||||
variant="default"
|
||||
data-tally-open="3yx2L0"
|
||||
data-tally-emoji-text="👋"
|
||||
data-tally-emoji-animation="wave"
|
||||
>
|
||||
<Megaphone />
|
||||
<span className="sr-only">Reach Out</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TallyPopupSimple;
|
||||
@@ -1,5 +1,5 @@
|
||||
.custom-node {
|
||||
padding: 15px;
|
||||
@apply p-3;
|
||||
border: 3px solid #4b5563;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
@@ -9,13 +9,6 @@
|
||||
transition: border-color 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.custom-node .mb-2 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -30,45 +23,6 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.node-actions {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.node-action-button {
|
||||
width: 32px;
|
||||
/* Increased size */
|
||||
height: 32px;
|
||||
/* Increased size */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f3f4f6;
|
||||
/* Light gray background */
|
||||
border: 1px solid #d1d5db;
|
||||
/* Light border */
|
||||
border-radius: 6px;
|
||||
color: #4b5563;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.node-action-button:hover {
|
||||
background-color: #e5e7eb;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.node-action-button:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
.node-action-button svg {
|
||||
width: 18px;
|
||||
/* Increased icon size */
|
||||
height: 18px;
|
||||
/* Increased icon size */
|
||||
}
|
||||
/* Existing styles */
|
||||
.handle-container {
|
||||
display: flex;
|
||||
@@ -89,38 +43,10 @@
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.clickable-input {
|
||||
padding: 5px;
|
||||
width: 325px;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d1d1d1;
|
||||
color: #000000;
|
||||
cursor: pointer;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.border-error {
|
||||
border: 1px solid #d9534f;
|
||||
}
|
||||
|
||||
.clickable-input span {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: calc(100% - 100px);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.select-input {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
@@ -191,29 +117,9 @@
|
||||
|
||||
.error-message {
|
||||
color: #d9534f;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.object-input {
|
||||
margin-left: 10px;
|
||||
border-left: 1px solid #000; /* Border for nested inputs */
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.nested-input {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.key-value-input {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.key-value-input input {
|
||||
flex-grow: 1;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Styles for node states */
|
||||
@@ -240,3 +146,13 @@
|
||||
.custom-switch {
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-outer-spin-button,
|
||||
input[type="number"]::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
@@ -40,8 +40,10 @@ export const BlocksControl: React.FC<BlocksControlProps> = ({
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger className="hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-50 dark:text-white">
|
||||
<ToyBrick className="size-4" />
|
||||
<PopoverTrigger asChild>
|
||||
<Button size="icon" variant="ghost">
|
||||
<ToyBrick size={18} />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side="right"
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { cn } from "@/lib/utils";
|
||||
import React from "react";
|
||||
|
||||
/**
|
||||
@@ -24,6 +25,7 @@ export type Control = {
|
||||
interface ControlPanelProps {
|
||||
controls: Control[];
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,11 +33,16 @@ interface ControlPanelProps {
|
||||
* @param {Object} ControlPanelProps - The properties of the control panel component.
|
||||
* @param {Array} ControlPanelProps.controls - An array of control objects representing actions to be preformed.
|
||||
* @param {Array} ControlPanelProps.children - The child components of the control panel.
|
||||
* @param {string} ControlPanelProps.className - Additional CSS class names for the control panel.
|
||||
* @returns The rendered control panel component.
|
||||
*/
|
||||
export const ControlPanel = ({ controls, children }: ControlPanelProps) => {
|
||||
export const ControlPanel = ({
|
||||
controls,
|
||||
children,
|
||||
className,
|
||||
}: ControlPanelProps) => {
|
||||
return (
|
||||
<aside className="hidden w-14 flex-col sm:flex">
|
||||
<aside className={cn("hidden w-14 flex-col sm:flex", className)}>
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col items-center gap-4 px-2 sm:py-5 rounded-radius">
|
||||
|
||||
@@ -51,8 +51,10 @@ export const SaveControl = ({
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger className="hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-50 dark:text-white">
|
||||
<Save className="size-4" />
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Save size={18} />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent side="right" sideOffset={15} align="start">
|
||||
<Card className="border-none shadow-none">
|
||||
|
||||
@@ -11,20 +11,6 @@ code {
|
||||
monospace;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
background-color: #ffffff;
|
||||
@@ -128,24 +114,3 @@ textarea::placeholder {
|
||||
width: 100%;
|
||||
height: 600px; /* Adjust this height as needed */
|
||||
}
|
||||
|
||||
.flow-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flow-controls {
|
||||
position: absolute;
|
||||
left: -80px;
|
||||
z-index: 1001;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.flow-controls.open {
|
||||
transform: translateX(350px);
|
||||
}
|
||||
|
||||
187
rnd/autogpt_builder/src/components/monitor/AgentFlowList.tsx
Normal file
187
rnd/autogpt_builder/src/components/monitor/AgentFlowList.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import AutoGPTServerAPI, { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { ChevronDownIcon, EnterIcon } from "@radix-ui/react-icons";
|
||||
import { AgentImportForm } from "@/components/agent-import-form";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import moment from "moment/moment";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
|
||||
export const AgentFlowList = ({
|
||||
flows,
|
||||
flowRuns,
|
||||
selectedFlow,
|
||||
onSelectFlow,
|
||||
className,
|
||||
}: {
|
||||
flows: GraphMeta[];
|
||||
flowRuns?: FlowRun[];
|
||||
selectedFlow: GraphMeta | null;
|
||||
onSelectFlow: (f: GraphMeta) => void;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [templates, setTemplates] = useState<GraphMeta[]>([]);
|
||||
const api = new AutoGPTServerAPI();
|
||||
useEffect(() => {
|
||||
api.listTemplates().then((templates) => setTemplates(templates));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card className={className}>
|
||||
<CardHeader className="flex-row justify-between items-center space-x-3 space-y-0">
|
||||
<CardTitle>Agents</CardTitle>
|
||||
|
||||
<div className="flex items-center">
|
||||
{/* Split "Create" button */}
|
||||
<Button variant="outline" className="rounded-r-none" asChild>
|
||||
<Link href="/build">Create</Link>
|
||||
</Button>
|
||||
<Dialog>
|
||||
{/* https://ui.shadcn.com/docs/components/dialog#notes */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={"rounded-l-none border-l-0 px-2"}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem>
|
||||
<EnterIcon className="mr-2" /> Import from file
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
{templates.length > 0 && (
|
||||
<>
|
||||
{/* List of templates */}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Use a template</DropdownMenuLabel>
|
||||
{templates.map((template) => (
|
||||
<DropdownMenuItem
|
||||
key={template.id}
|
||||
onClick={() => {
|
||||
api
|
||||
.createGraph(template.id, template.version)
|
||||
.then((newGraph) => {
|
||||
window.location.href = `/build?flowID=${newGraph.id}`;
|
||||
});
|
||||
}}
|
||||
>
|
||||
{template.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<DialogContent>
|
||||
<DialogHeader className="text-lg">
|
||||
Import an Agent (template) from a file
|
||||
</DialogHeader>
|
||||
<AgentImportForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
{/* <TableHead>Status</TableHead> */}
|
||||
{/* <TableHead>Last updated</TableHead> */}
|
||||
{flowRuns && (
|
||||
<TableHead className="md:hidden lg:table-cell">
|
||||
# of runs
|
||||
</TableHead>
|
||||
)}
|
||||
{flowRuns && <TableHead>Last run</TableHead>}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{flows
|
||||
.map((flow) => {
|
||||
let runCount = 0,
|
||||
lastRun: FlowRun | null = null;
|
||||
if (flowRuns) {
|
||||
const _flowRuns = flowRuns.filter(
|
||||
(r) => r.graphID == flow.id,
|
||||
);
|
||||
runCount = _flowRuns.length;
|
||||
lastRun =
|
||||
runCount == 0
|
||||
? null
|
||||
: _flowRuns.reduce((a, c) =>
|
||||
a.startTime > c.startTime ? a : c,
|
||||
);
|
||||
}
|
||||
return { flow, runCount, lastRun };
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (!a.lastRun && !b.lastRun) return 0;
|
||||
if (!a.lastRun) return 1;
|
||||
if (!b.lastRun) return -1;
|
||||
return b.lastRun.startTime - a.lastRun.startTime;
|
||||
})
|
||||
.map(({ flow, runCount, lastRun }) => (
|
||||
<TableRow
|
||||
key={flow.id}
|
||||
className="cursor-pointer"
|
||||
onClick={() => onSelectFlow(flow)}
|
||||
data-state={selectedFlow?.id == flow.id ? "selected" : null}
|
||||
>
|
||||
<TableCell>{flow.name}</TableCell>
|
||||
{/* <TableCell><FlowStatusBadge status={flow.status ?? "active"} /></TableCell> */}
|
||||
{/* <TableCell>
|
||||
{flow.updatedAt ?? "???"}
|
||||
</TableCell> */}
|
||||
{flowRuns && (
|
||||
<TableCell className="md:hidden lg:table-cell">
|
||||
{runCount}
|
||||
</TableCell>
|
||||
)}
|
||||
{flowRuns &&
|
||||
(!lastRun ? (
|
||||
<TableCell />
|
||||
) : (
|
||||
<TableCell title={moment(lastRun.startTime).toString()}>
|
||||
{moment(lastRun.startTime).fromNow()}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default AgentFlowList;
|
||||
134
rnd/autogpt_builder/src/components/monitor/FlowInfo.tsx
Normal file
134
rnd/autogpt_builder/src/components/monitor/FlowInfo.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import AutoGPTServerAPI, {
|
||||
Graph,
|
||||
GraphMeta,
|
||||
safeCopyGraph,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { ClockIcon, ExitIcon, Pencil2Icon } from "@radix-ui/react-icons";
|
||||
import Link from "next/link";
|
||||
import { exportAsJSONFile } from "@/lib/utils";
|
||||
import { FlowRunsStats } from "@/components/monitor/index";
|
||||
|
||||
export const FlowInfo: React.FC<
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
flow: GraphMeta;
|
||||
flowRuns: FlowRun[];
|
||||
flowVersion?: number | "all";
|
||||
}
|
||||
> = ({ flow, flowRuns, flowVersion, ...props }) => {
|
||||
const api = new AutoGPTServerAPI();
|
||||
|
||||
const [flowVersions, setFlowVersions] = useState<Graph[] | null>(null);
|
||||
const [selectedVersion, setSelectedFlowVersion] = useState(
|
||||
flowVersion ?? "all",
|
||||
);
|
||||
const selectedFlowVersion: Graph | undefined = flowVersions?.find(
|
||||
(v) =>
|
||||
v.version == (selectedVersion == "all" ? flow.version : selectedVersion),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
api.getGraphAllVersions(flow.id).then((result) => setFlowVersions(result));
|
||||
}, [flow.id]);
|
||||
|
||||
return (
|
||||
<Card {...props}>
|
||||
<CardHeader className="flex-row justify-between space-y-0 space-x-3">
|
||||
<div>
|
||||
<CardTitle>
|
||||
{flow.name} <span className="font-light">v{flow.version}</span>
|
||||
</CardTitle>
|
||||
<p className="mt-2">
|
||||
Agent ID: <code>{flow.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-2">
|
||||
{(flowVersions?.length ?? 0) > 1 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">
|
||||
<ClockIcon className="mr-2" />
|
||||
{selectedVersion == "all"
|
||||
? "All versions"
|
||||
: `Version ${selectedVersion}`}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuLabel>Choose a version</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuRadioGroup
|
||||
value={String(selectedVersion)}
|
||||
onValueChange={(choice) =>
|
||||
setSelectedFlowVersion(
|
||||
choice == "all" ? choice : Number(choice),
|
||||
)
|
||||
}
|
||||
>
|
||||
<DropdownMenuRadioItem value="all">
|
||||
All versions
|
||||
</DropdownMenuRadioItem>
|
||||
{flowVersions?.map((v) => (
|
||||
<DropdownMenuRadioItem
|
||||
key={v.version}
|
||||
value={v.version.toString()}
|
||||
>
|
||||
Version {v.version}
|
||||
{v.is_active ? " (active)" : ""}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
<Link
|
||||
className={buttonVariants({ variant: "outline" })}
|
||||
href={`/build?flowID=${flow.id}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" /> Edit
|
||||
</Link>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="px-2.5"
|
||||
title="Export to a JSON-file"
|
||||
onClick={async () =>
|
||||
exportAsJSONFile(
|
||||
safeCopyGraph(
|
||||
flowVersions!.find(
|
||||
(v) => v.version == selectedFlowVersion!.version,
|
||||
)!,
|
||||
await api.getBlocks(),
|
||||
),
|
||||
`${flow.name}_v${selectedFlowVersion!.version}.json`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<ExitIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FlowRunsStats
|
||||
flows={[selectedFlowVersion ?? flow]}
|
||||
flowRuns={flowRuns.filter(
|
||||
(r) =>
|
||||
r.graphID == flow.id &&
|
||||
(selectedVersion == "all" || r.graphVersion == selectedVersion),
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default FlowInfo;
|
||||
66
rnd/autogpt_builder/src/components/monitor/FlowRunInfo.tsx
Normal file
66
rnd/autogpt_builder/src/components/monitor/FlowRunInfo.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import Link from "next/link";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { Pencil2Icon } from "@radix-ui/react-icons";
|
||||
import moment from "moment/moment";
|
||||
import { FlowRunStatusBadge } from "@/components/monitor/FlowRunStatusBadge";
|
||||
|
||||
export const FlowRunInfo: React.FC<
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
flow: GraphMeta;
|
||||
flowRun: FlowRun;
|
||||
}
|
||||
> = ({ flow, flowRun, ...props }) => {
|
||||
if (flowRun.graphID != flow.id) {
|
||||
throw new Error(
|
||||
`FlowRunInfo can't be used with non-matching flowRun.flowID and flow.id`,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card {...props}>
|
||||
<CardHeader className="flex-row items-center justify-between space-y-0 space-x-3">
|
||||
<div>
|
||||
<CardTitle>
|
||||
{flow.name} <span className="font-light">v{flow.version}</span>
|
||||
</CardTitle>
|
||||
<p className="mt-2">
|
||||
Agent ID: <code>{flow.id}</code>
|
||||
</p>
|
||||
<p className="mt-1">
|
||||
Run ID: <code>{flowRun.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
className={buttonVariants({ variant: "outline" })}
|
||||
href={`/build?flowID=${flow.id}`}
|
||||
>
|
||||
<Pencil2Icon className="mr-2" /> Edit Agent
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>
|
||||
<strong>Status:</strong>{" "}
|
||||
<FlowRunStatusBadge status={flowRun.status} />
|
||||
</p>
|
||||
<p>
|
||||
<strong>Started:</strong>{" "}
|
||||
{moment(flowRun.startTime).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Finished:</strong>{" "}
|
||||
{moment(flowRun.endTime).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Duration (run time):</strong> {flowRun.duration} (
|
||||
{flowRun.totalRunTime}) seconds
|
||||
</p>
|
||||
{/* <p><strong>Total cost:</strong> €1,23</p> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default FlowRunInfo;
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const FlowRunStatusBadge: React.FC<{
|
||||
status: FlowRun["status"];
|
||||
className?: string;
|
||||
}> = ({ status, className }) => (
|
||||
<Badge
|
||||
variant="default"
|
||||
className={cn(
|
||||
status === "running"
|
||||
? "bg-blue-500 dark:bg-blue-700"
|
||||
: status === "waiting"
|
||||
? "bg-yellow-500 dark:bg-yellow-600"
|
||||
: status === "success"
|
||||
? "bg-green-500 dark:bg-green-600"
|
||||
: "bg-red-500 dark:bg-red-700",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{status}
|
||||
</Badge>
|
||||
);
|
||||
68
rnd/autogpt_builder/src/components/monitor/FlowRunsList.tsx
Normal file
68
rnd/autogpt_builder/src/components/monitor/FlowRunsList.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from "react";
|
||||
import { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import moment from "moment/moment";
|
||||
import { FlowRunStatusBadge } from "@/components/monitor/FlowRunStatusBadge";
|
||||
|
||||
export const FlowRunsList: React.FC<{
|
||||
flows: GraphMeta[];
|
||||
runs: FlowRun[];
|
||||
className?: string;
|
||||
selectedRun?: FlowRun | null;
|
||||
onSelectRun: (r: FlowRun) => void;
|
||||
}> = ({ flows, runs, selectedRun, onSelectRun, className }) => (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
<CardTitle>Runs</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Agent</TableHead>
|
||||
<TableHead>Started</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Duration</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{runs.map((run) => (
|
||||
<TableRow
|
||||
key={run.id}
|
||||
className="cursor-pointer"
|
||||
onClick={() => onSelectRun(run)}
|
||||
data-state={selectedRun?.id == run.id ? "selected" : null}
|
||||
>
|
||||
<TableCell>
|
||||
{flows.find((f) => f.id == run.graphID)!.name}
|
||||
</TableCell>
|
||||
<TableCell>{moment(run.startTime).format("HH:mm")}</TableCell>
|
||||
<TableCell>
|
||||
<FlowRunStatusBadge status={run.status} />
|
||||
</TableCell>
|
||||
<TableCell>{formatDuration(run.duration)}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
function formatDuration(seconds: number): string {
|
||||
return (
|
||||
(seconds < 100 ? seconds.toPrecision(2) : Math.round(seconds)).toString() +
|
||||
"s"
|
||||
);
|
||||
}
|
||||
|
||||
export default FlowRunsList;
|
||||
114
rnd/autogpt_builder/src/components/monitor/FlowRunsStatus.tsx
Normal file
114
rnd/autogpt_builder/src/components/monitor/FlowRunsStatus.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { useState } from "react";
|
||||
import { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import { CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { FlowRunsTimeline } from "@/components/monitor/FlowRunsTimeline";
|
||||
|
||||
export const FlowRunsStatus: React.FC<{
|
||||
flows: GraphMeta[];
|
||||
flowRuns: FlowRun[];
|
||||
title?: string;
|
||||
className?: string;
|
||||
}> = ({ flows, flowRuns, title, className }) => {
|
||||
/* "dateMin": since the first flow in the dataset
|
||||
* number > 0: custom date (unix timestamp)
|
||||
* number < 0: offset relative to Date.now() (in seconds) */
|
||||
const [statsSince, setStatsSince] = useState<number | "dataMin">(-24 * 3600);
|
||||
const statsSinceTimestamp = // unix timestamp or null
|
||||
typeof statsSince == "string"
|
||||
? null
|
||||
: statsSince < 0
|
||||
? Date.now() + statsSince * 1000
|
||||
: statsSince;
|
||||
const filteredFlowRuns =
|
||||
statsSinceTimestamp != null
|
||||
? flowRuns.filter((fr) => fr.startTime > statsSinceTimestamp)
|
||||
: flowRuns;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<CardTitle>{title || "Stats"}</CardTitle>
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setStatsSince(-2 * 3600)}
|
||||
>
|
||||
2h
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setStatsSince(-8 * 3600)}
|
||||
>
|
||||
8h
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setStatsSince(-24 * 3600)}
|
||||
>
|
||||
24h
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setStatsSince(-7 * 24 * 3600)}
|
||||
>
|
||||
7d
|
||||
</Button>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant={"outline"} size="sm">
|
||||
Custom
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
onSelect={(_, selectedDay) =>
|
||||
setStatsSince(selectedDay.getTime())
|
||||
}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setStatsSince("dataMin")}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<FlowRunsTimeline
|
||||
flows={flows}
|
||||
flowRuns={flowRuns}
|
||||
dataMin={statsSince}
|
||||
className="mt-3"
|
||||
/>
|
||||
<hr className="my-4" />
|
||||
<div>
|
||||
<p>
|
||||
<strong>Total runs:</strong> {filteredFlowRuns.length}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Total run time:</strong>{" "}
|
||||
{filteredFlowRuns.reduce((total, run) => total + run.totalRunTime, 0)}{" "}
|
||||
seconds
|
||||
</p>
|
||||
{/* <p><strong>Total cost:</strong> €1,23</p> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default FlowRunsStatus;
|
||||
172
rnd/autogpt_builder/src/components/monitor/FlowRunsTimeline.tsx
Normal file
172
rnd/autogpt_builder/src/components/monitor/FlowRunsTimeline.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
import { GraphMeta } from "@/lib/autogpt-server-api";
|
||||
import {
|
||||
ComposedChart,
|
||||
DefaultLegendContentProps,
|
||||
Legend,
|
||||
Line,
|
||||
ResponsiveContainer,
|
||||
Scatter,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import moment from "moment/moment";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { cn, hashString } from "@/lib/utils";
|
||||
import React from "react";
|
||||
import { FlowRun } from "@/lib/types";
|
||||
import { FlowRunStatusBadge } from "@/components/monitor/FlowRunStatusBadge";
|
||||
|
||||
export const FlowRunsTimeline = ({
|
||||
flows,
|
||||
flowRuns,
|
||||
dataMin,
|
||||
className,
|
||||
}: {
|
||||
flows: GraphMeta[];
|
||||
flowRuns: FlowRun[];
|
||||
dataMin: "dataMin" | number;
|
||||
className?: string;
|
||||
}) => (
|
||||
/* TODO: make logarithmic? */
|
||||
<ResponsiveContainer width="100%" height={120} className={className}>
|
||||
<ComposedChart>
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
type="number"
|
||||
domain={[
|
||||
typeof dataMin == "string"
|
||||
? dataMin
|
||||
: dataMin < 0
|
||||
? Date.now() + dataMin * 1000
|
||||
: dataMin,
|
||||
Date.now(),
|
||||
]}
|
||||
allowDataOverflow={true}
|
||||
tickFormatter={(unixTime) => {
|
||||
const now = moment();
|
||||
const time = moment(unixTime);
|
||||
return now.diff(time, "hours") < 24
|
||||
? time.format("HH:mm")
|
||||
: time.format("YYYY-MM-DD HH:mm");
|
||||
}}
|
||||
name="Time"
|
||||
scale="time"
|
||||
/>
|
||||
<YAxis
|
||||
dataKey="_duration"
|
||||
name="Duration (s)"
|
||||
tickFormatter={(s) => (s > 90 ? `${Math.round(s / 60)}m` : `${s}s`)}
|
||||
/>
|
||||
<Tooltip
|
||||
content={({ payload, label }) => {
|
||||
if (payload && payload.length) {
|
||||
const data: FlowRun & { time: number; _duration: number } =
|
||||
payload[0].payload;
|
||||
const flow = flows.find((f) => f.id === data.graphID);
|
||||
return (
|
||||
<Card className="p-2 text-xs leading-normal">
|
||||
<p>
|
||||
<strong>Agent:</strong> {flow ? flow.name : "Unknown"}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Status:</strong>
|
||||
<FlowRunStatusBadge
|
||||
status={data.status}
|
||||
className="px-1.5 py-0"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Started:</strong>{" "}
|
||||
{moment(data.startTime).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Duration / run time:</strong>{" "}
|
||||
{formatDuration(data.duration)} /{" "}
|
||||
{formatDuration(data.totalRunTime)}
|
||||
</p>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
{flows.map((flow) => (
|
||||
<Scatter
|
||||
key={flow.id}
|
||||
data={flowRuns
|
||||
.filter((fr) => fr.graphID == flow.id)
|
||||
.map((fr) => ({
|
||||
...fr,
|
||||
time: fr.startTime + fr.totalRunTime * 1000,
|
||||
_duration: fr.totalRunTime,
|
||||
}))}
|
||||
name={flow.name}
|
||||
fill={`hsl(${(hashString(flow.id) * 137.5) % 360}, 70%, 50%)`}
|
||||
/>
|
||||
))}
|
||||
{flowRuns.map((run) => (
|
||||
<Line
|
||||
key={run.id}
|
||||
type="linear"
|
||||
dataKey="_duration"
|
||||
data={[
|
||||
{ ...run, time: run.startTime, _duration: 0 },
|
||||
{ ...run, time: run.endTime, _duration: run.totalRunTime },
|
||||
]}
|
||||
stroke={`hsl(${(hashString(run.graphID) * 137.5) % 360}, 70%, 50%)`}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
legendType="none"
|
||||
/>
|
||||
))}
|
||||
<Legend
|
||||
content={<ScrollableLegend />}
|
||||
wrapperStyle={{
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
|
||||
export default FlowRunsTimeline;
|
||||
|
||||
const ScrollableLegend: React.FC<
|
||||
DefaultLegendContentProps & { className?: string }
|
||||
> = ({ payload, className }) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"whitespace-nowrap px-4 text-sm overflow-x-auto space-x-3",
|
||||
className,
|
||||
)}
|
||||
style={{ scrollbarWidth: "none" }}
|
||||
>
|
||||
{payload?.map((entry, index) => {
|
||||
if (entry.type == "none") return;
|
||||
return (
|
||||
<span key={`item-${index}`} className="inline-flex items-center">
|
||||
<span
|
||||
className="size-2.5 inline-block mr-1 rounded-full"
|
||||
style={{ backgroundColor: entry.color }}
|
||||
/>
|
||||
<span>{entry.value}</span>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function formatDuration(seconds: number): string {
|
||||
return (
|
||||
(seconds < 100 ? seconds.toPrecision(2) : Math.round(seconds)).toString() +
|
||||
"s"
|
||||
);
|
||||
}
|
||||
6
rnd/autogpt_builder/src/components/monitor/index.ts
Normal file
6
rnd/autogpt_builder/src/components/monitor/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export { default as AgentFlowList } from "./AgentFlowList";
|
||||
export { default as FlowRunsList } from "./FlowRunsList";
|
||||
export { default as FlowInfo } from "./FlowInfo";
|
||||
export { default as FlowRunInfo } from "./FlowRunInfo";
|
||||
export { default as FlowRunsStats } from "./FlowRunsStatus";
|
||||
export { default as FlowRunsTimeline } from "./FlowRunsTimeline";
|
||||
616
rnd/autogpt_builder/src/components/node-input-components.tsx
Normal file
616
rnd/autogpt_builder/src/components/node-input-components.tsx
Normal file
@@ -0,0 +1,616 @@
|
||||
import { Cross2Icon, Pencil2Icon, PlusIcon } from "@radix-ui/react-icons";
|
||||
import { beautifyString, cn } from "@/lib/utils";
|
||||
import {
|
||||
BlockIORootSchema,
|
||||
BlockIOSubSchema,
|
||||
BlockIOObjectSubSchema,
|
||||
BlockIOKVSubSchema,
|
||||
BlockIOArraySubSchema,
|
||||
BlockIOStringSubSchema,
|
||||
BlockIONumberSubSchema,
|
||||
BlockIOBooleanSubSchema,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import { FC, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Switch } from "./ui/switch";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "./ui/select";
|
||||
import { Input } from "./ui/input";
|
||||
|
||||
type NodeObjectInputTreeProps = {
|
||||
selfKey?: string;
|
||||
schema: BlockIORootSchema | BlockIOObjectSubSchema;
|
||||
object?: { [key: string]: any };
|
||||
handleInputClick: (key: string) => void;
|
||||
handleInputChange: (key: string, value: any) => void;
|
||||
errors: { [key: string]: string | undefined };
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
};
|
||||
|
||||
const NodeObjectInputTree: FC<NodeObjectInputTreeProps> = ({
|
||||
selfKey = "",
|
||||
schema,
|
||||
object,
|
||||
handleInputClick,
|
||||
handleInputChange,
|
||||
errors,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
object ??= ("default" in schema ? schema.default : null) ?? {};
|
||||
return (
|
||||
<div className={cn(className, "flex-col w-full")}>
|
||||
{displayName && <strong>{displayName}</strong>}
|
||||
{Object.entries(schema.properties).map(([propKey, propSchema]) => {
|
||||
const childKey = selfKey ? `${selfKey}.${propKey}` : propKey;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={propKey}
|
||||
className="flex flex-row justify-between space-y-2 w-full"
|
||||
>
|
||||
<span className="mr-2 mt-3">
|
||||
{propSchema.title || beautifyString(propKey)}
|
||||
</span>
|
||||
<NodeGenericInputField
|
||||
key={propKey}
|
||||
propKey={childKey}
|
||||
propSchema={propSchema}
|
||||
currentValue={object ? object[propKey] : undefined}
|
||||
errors={errors}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
displayName={propSchema.title || beautifyString(propKey)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeObjectInputTree;
|
||||
|
||||
export const NodeGenericInputField: FC<{
|
||||
propKey: string;
|
||||
propSchema: BlockIOSubSchema;
|
||||
currentValue?: any;
|
||||
errors: NodeObjectInputTreeProps["errors"];
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
handleInputClick: NodeObjectInputTreeProps["handleInputClick"];
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
}> = ({
|
||||
propKey,
|
||||
propSchema,
|
||||
currentValue,
|
||||
errors,
|
||||
handleInputChange,
|
||||
handleInputClick,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
displayName ??= propSchema.title || beautifyString(propKey);
|
||||
|
||||
if ("allOf" in propSchema) {
|
||||
// If this happens, that is because Pydantic wraps $refs in an allOf if the
|
||||
// $ref has sibling schema properties (which isn't technically allowed),
|
||||
// so there will only be one item in allOf[].
|
||||
// AFAIK this should NEVER happen though, as $refs are resolved server-side.
|
||||
propSchema = propSchema.allOf[0];
|
||||
console.warn(`Unsupported 'allOf' in schema for '${propKey}'!`, propSchema);
|
||||
}
|
||||
|
||||
if ("properties" in propSchema) {
|
||||
return (
|
||||
<NodeObjectInputTree
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
object={currentValue}
|
||||
errors={errors}
|
||||
className={cn("border-l border-gray-500 pl-2", className)} // visual indent
|
||||
displayName={displayName}
|
||||
handleInputClick={handleInputClick}
|
||||
handleInputChange={handleInputChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if ("additionalProperties" in propSchema) {
|
||||
return (
|
||||
<NodeKeyValueInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
entries={currentValue}
|
||||
errors={errors}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if ("anyOf" in propSchema) {
|
||||
// optional items
|
||||
const types = propSchema.anyOf.map((s) =>
|
||||
"type" in s ? s.type : undefined,
|
||||
);
|
||||
if (types.includes("string") && types.includes("null")) {
|
||||
// optional string
|
||||
return (
|
||||
<NodeStringInput
|
||||
selfKey={propKey}
|
||||
schema={{ ...propSchema, type: "string" } as BlockIOStringSubSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ("oneOf" in propSchema) {
|
||||
// At the time of writing, this isn't used in the backend -> no impl. needed
|
||||
console.error(
|
||||
`Unsupported 'oneOf' in schema for '${propKey}'!`,
|
||||
propSchema,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!("type" in propSchema)) {
|
||||
return (
|
||||
<NodeFallbackInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
switch (propSchema.type) {
|
||||
case "string":
|
||||
return (
|
||||
<NodeStringInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
);
|
||||
case "boolean":
|
||||
return (
|
||||
<NodeBooleanInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
/>
|
||||
);
|
||||
case "number":
|
||||
case "integer":
|
||||
return (
|
||||
<NodeNumberInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
/>
|
||||
);
|
||||
case "array":
|
||||
return (
|
||||
<NodeArrayInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
entries={currentValue}
|
||||
errors={errors}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
console.warn(
|
||||
`Schema for '${propKey}' specifies unknown type:`,
|
||||
propSchema,
|
||||
);
|
||||
return (
|
||||
<NodeFallbackInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const NodeKeyValueInput: FC<{
|
||||
selfKey: string;
|
||||
schema: BlockIOKVSubSchema;
|
||||
entries?: { [key: string]: string } | { [key: string]: number };
|
||||
errors: { [key: string]: string | undefined };
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
}> = ({
|
||||
selfKey,
|
||||
entries,
|
||||
schema,
|
||||
handleInputChange,
|
||||
errors,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
const [keyValuePairs, setKeyValuePairs] = useState<
|
||||
{
|
||||
key: string;
|
||||
value: string | number | null;
|
||||
}[]
|
||||
>(
|
||||
Object.entries(entries ?? schema.default ?? {}).map(([key, value]) => ({
|
||||
key,
|
||||
value: value,
|
||||
})),
|
||||
);
|
||||
|
||||
function updateKeyValuePairs(newPairs: typeof keyValuePairs) {
|
||||
setKeyValuePairs(newPairs);
|
||||
handleInputChange(
|
||||
selfKey,
|
||||
newPairs.reduce((obj, { key, value }) => ({ ...obj, [key]: value }), {}),
|
||||
);
|
||||
}
|
||||
|
||||
function convertValueType(value: string): string | number | null {
|
||||
if (schema.additionalProperties.type == "string") return value;
|
||||
if (!value) return null;
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(className, "flex flex-col")}>
|
||||
{displayName && <strong>{displayName}</strong>}
|
||||
<div>
|
||||
{keyValuePairs.map(({ key, value }, index) => (
|
||||
<div key={index}>
|
||||
<div className="flex items-center space-x-2 mb-2 nodrag">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Key"
|
||||
value={key}
|
||||
onChange={(e) =>
|
||||
updateKeyValuePairs(
|
||||
keyValuePairs.toSpliced(index, 1, {
|
||||
key: e.target.value,
|
||||
value: value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
value={value ?? ""}
|
||||
onChange={(e) =>
|
||||
updateKeyValuePairs(
|
||||
keyValuePairs.toSpliced(index, 1, {
|
||||
key: key,
|
||||
value: convertValueType(e.target.value),
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="px-2"
|
||||
onClick={() =>
|
||||
updateKeyValuePairs(keyValuePairs.toSpliced(index, 1))
|
||||
}
|
||||
>
|
||||
<Cross2Icon />
|
||||
</Button>
|
||||
</div>
|
||||
{errors[`${selfKey}.${key}`] && (
|
||||
<span className="error-message">
|
||||
{errors[`${selfKey}.${key}`]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() =>
|
||||
updateKeyValuePairs(keyValuePairs.concat({ key: "", value: "" }))
|
||||
}
|
||||
>
|
||||
<PlusIcon className="mr-2" /> Add Property
|
||||
</Button>
|
||||
</div>
|
||||
{errors[selfKey] && (
|
||||
<span className="error-message">{errors[selfKey]}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeArrayInput: FC<{
|
||||
selfKey: string;
|
||||
schema: BlockIOArraySubSchema;
|
||||
entries?: string[];
|
||||
errors: { [key: string]: string | undefined };
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
handleInputClick: NodeObjectInputTreeProps["handleInputClick"];
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
}> = ({
|
||||
selfKey,
|
||||
schema,
|
||||
entries,
|
||||
errors,
|
||||
handleInputChange,
|
||||
handleInputClick,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
entries ??= schema.default ?? [];
|
||||
const isItemObject = "items" in schema && "properties" in schema.items!;
|
||||
const error =
|
||||
typeof errors[selfKey] === "string" ? errors[selfKey] : undefined;
|
||||
return (
|
||||
<div className={cn(className, "flex flex-col")}>
|
||||
{displayName && <strong>{displayName}</strong>}
|
||||
{entries.map((entry: any, index: number) => {
|
||||
const entryKey = `${selfKey}[${index}]`;
|
||||
return (
|
||||
<div key={entryKey}>
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
{schema.items ? (
|
||||
<NodeGenericInputField
|
||||
propKey={entryKey}
|
||||
propSchema={schema.items}
|
||||
currentValue={entry}
|
||||
errors={errors}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
) : (
|
||||
<NodeFallbackInput
|
||||
selfKey={entryKey}
|
||||
schema={schema.items}
|
||||
value={entry}
|
||||
error={errors[entryKey]}
|
||||
displayName={displayName || beautifyString(selfKey)}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
handleInputChange(selfKey, entries.toSpliced(index, 1))
|
||||
}
|
||||
>
|
||||
<Cross2Icon />
|
||||
</Button>
|
||||
</div>
|
||||
{errors[entryKey] && typeof errors[entryKey] === "string" && (
|
||||
<span className="error-message">{errors[entryKey]}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<Button
|
||||
onClick={() =>
|
||||
handleInputChange(selfKey, [...entries, isItemObject ? {} : ""])
|
||||
}
|
||||
>
|
||||
<PlusIcon className="mr-2" /> Add Item
|
||||
</Button>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeStringInput: FC<{
|
||||
selfKey: string;
|
||||
schema: BlockIOStringSubSchema;
|
||||
value?: string;
|
||||
error?: string;
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
handleInputClick: NodeObjectInputTreeProps["handleInputClick"];
|
||||
className?: string;
|
||||
displayName: string;
|
||||
}> = ({
|
||||
selfKey,
|
||||
schema,
|
||||
value,
|
||||
error,
|
||||
handleInputChange,
|
||||
handleInputClick,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
{schema.enum ? (
|
||||
<Select
|
||||
defaultValue={value}
|
||||
onValueChange={(newValue) => handleInputChange(selfKey, newValue)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={schema.placeholder || displayName} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="nodrag">
|
||||
{schema.enum.map((option, index) => (
|
||||
<SelectItem key={index} value={option}>
|
||||
{beautifyString(option)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
<div
|
||||
className="nodrag relative"
|
||||
onClick={schema.secret ? () => handleInputClick(selfKey) : undefined}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
id={selfKey}
|
||||
value={schema.secret && value ? "********" : value}
|
||||
readOnly={schema.secret}
|
||||
placeholder={
|
||||
schema?.placeholder || `Enter ${beautifyString(displayName)}`
|
||||
}
|
||||
onChange={(e) => handleInputChange(selfKey, e.target.value)}
|
||||
className="pr-8 read-only:cursor-pointer read-only:text-gray-500"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute inset-1 left-auto h-7 w-7 rounded-[0.25rem]"
|
||||
onClick={() => handleInputClick(selfKey)}
|
||||
title="Open a larger textbox input"
|
||||
>
|
||||
<Pencil2Icon className="m-0 p-0" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeNumberInput: FC<{
|
||||
selfKey: string;
|
||||
schema: BlockIONumberSubSchema;
|
||||
value?: number;
|
||||
error?: string;
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
}> = ({
|
||||
selfKey,
|
||||
schema,
|
||||
value,
|
||||
error,
|
||||
handleInputChange,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
value ??= schema.default;
|
||||
displayName ??= schema.title || beautifyString(selfKey);
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-center justify-between space-x-3 nodrag">
|
||||
<Input
|
||||
type="number"
|
||||
id={selfKey}
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
handleInputChange(selfKey, parseFloat(e.target.value))
|
||||
}
|
||||
placeholder={
|
||||
schema.placeholder || `Enter ${beautifyString(displayName)}`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeBooleanInput: FC<{
|
||||
selfKey: string;
|
||||
schema: BlockIOBooleanSubSchema;
|
||||
value?: boolean;
|
||||
error?: string;
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
className?: string;
|
||||
displayName: string;
|
||||
}> = ({
|
||||
selfKey,
|
||||
schema,
|
||||
value,
|
||||
error,
|
||||
handleInputChange,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
value ??= schema.default ?? false;
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-center nodrag">
|
||||
<Switch
|
||||
checked={value}
|
||||
onCheckedChange={(v) => handleInputChange(selfKey, v)}
|
||||
/>
|
||||
<span className="ml-3">{displayName}</span>
|
||||
</div>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeFallbackInput: FC<{
|
||||
selfKey: string;
|
||||
schema?: BlockIOSubSchema;
|
||||
value: any;
|
||||
error?: string;
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
handleInputClick: NodeObjectInputTreeProps["handleInputClick"];
|
||||
className?: string;
|
||||
displayName: string;
|
||||
}> = ({
|
||||
selfKey,
|
||||
schema,
|
||||
value,
|
||||
error,
|
||||
handleInputChange,
|
||||
handleInputClick,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
return (
|
||||
<NodeStringInput
|
||||
selfKey={selfKey}
|
||||
schema={{ type: "string", ...schema } as BlockIOStringSubSchema}
|
||||
value={value}
|
||||
error={error}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
167
rnd/autogpt_builder/src/components/ui/select.tsx
Normal file
167
rnd/autogpt_builder/src/components/ui/select.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import {
|
||||
CaretSortIcon,
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group;
|
||||
|
||||
const SelectValue = SelectPrimitive.Value;
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-neutral-200 bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-white placeholder:text-neutral-500 focus:outline-none focus:ring-1 focus:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 dark:border-neutral-800 dark:ring-offset-neutral-950 dark:placeholder:text-neutral-400 dark:focus:ring-neutral-300",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<CaretSortIcon className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
));
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
));
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
));
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName;
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white text-neutral-950 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
));
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"-mx-1 my-1 h-px bg-neutral-100 dark:bg-neutral-800",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
};
|
||||
@@ -9,60 +9,86 @@ export type Block = {
|
||||
|
||||
export type BlockIORootSchema = {
|
||||
type: "object";
|
||||
properties: { [key: string]: BlockIOSchema };
|
||||
properties: { [key: string]: BlockIOSubSchema };
|
||||
required?: string[];
|
||||
additionalProperties?: { type: string };
|
||||
};
|
||||
|
||||
export type BlockIOSchema = {
|
||||
export type BlockIOSubSchema =
|
||||
| BlockIOSimpleTypeSubSchema
|
||||
| BlockIOCombinedTypeSubSchema;
|
||||
|
||||
type BlockIOSimpleTypeSubSchema =
|
||||
| BlockIOObjectSubSchema
|
||||
| BlockIOKVSubSchema
|
||||
| BlockIOArraySubSchema
|
||||
| BlockIOStringSubSchema
|
||||
| BlockIONumberSubSchema
|
||||
| BlockIOBooleanSubSchema
|
||||
| BlockIONullSubSchema;
|
||||
|
||||
type BlockIOSubSchemaMeta = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
} & (BlockIOSimpleTypeSchema | BlockIOCombinedTypeSchema);
|
||||
};
|
||||
|
||||
type BlockIOSimpleTypeSchema =
|
||||
| {
|
||||
type: "object";
|
||||
properties: { [key: string]: BlockIOSchema };
|
||||
required?: string[];
|
||||
additionalProperties?: { type: string };
|
||||
}
|
||||
| {
|
||||
type: "array";
|
||||
items?: BlockIOSimpleTypeSchema;
|
||||
}
|
||||
| {
|
||||
type: "string";
|
||||
enum?: string[];
|
||||
secret?: true;
|
||||
default?: string;
|
||||
}
|
||||
| {
|
||||
type: "integer" | "number";
|
||||
default?: number;
|
||||
}
|
||||
| {
|
||||
type: "boolean";
|
||||
default?: boolean;
|
||||
}
|
||||
| {
|
||||
type: "null";
|
||||
};
|
||||
export type BlockIOObjectSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "object";
|
||||
properties: { [key: string]: BlockIOSubSchema };
|
||||
default?: { [key: keyof BlockIOObjectSubSchema["properties"]]: any };
|
||||
required?: keyof BlockIOObjectSubSchema["properties"][];
|
||||
};
|
||||
|
||||
export type BlockIOKVSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "object";
|
||||
additionalProperties: { type: "string" | "number" | "integer" };
|
||||
default?: { [key: string]: string | number };
|
||||
};
|
||||
|
||||
export type BlockIOArraySubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "array";
|
||||
items?: BlockIOSimpleTypeSubSchema;
|
||||
default?: Array<string>;
|
||||
};
|
||||
|
||||
export type BlockIOStringSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "string";
|
||||
enum?: string[];
|
||||
secret?: true;
|
||||
default?: string;
|
||||
};
|
||||
|
||||
export type BlockIONumberSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "integer" | "number";
|
||||
default?: number;
|
||||
};
|
||||
|
||||
export type BlockIOBooleanSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "boolean";
|
||||
default?: boolean;
|
||||
};
|
||||
|
||||
export type BlockIONullSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "null";
|
||||
};
|
||||
|
||||
// At the time of writing, combined schemas only occur on the first nested level in a
|
||||
// block schema. It is typed this way to make the use of these objects less tedious.
|
||||
type BlockIOCombinedTypeSchema =
|
||||
| {
|
||||
allOf: [BlockIOSimpleTypeSchema];
|
||||
}
|
||||
| {
|
||||
anyOf: BlockIOSimpleTypeSchema[];
|
||||
default?: string | number | boolean | null;
|
||||
}
|
||||
| {
|
||||
oneOf: BlockIOSimpleTypeSchema[];
|
||||
default?: string | number | boolean | null;
|
||||
};
|
||||
type BlockIOCombinedTypeSubSchema = BlockIOSubSchemaMeta &
|
||||
(
|
||||
| {
|
||||
allOf: [BlockIOSimpleTypeSubSchema];
|
||||
}
|
||||
| {
|
||||
anyOf: BlockIOSimpleTypeSubSchema[];
|
||||
default?: string | number | boolean | null;
|
||||
}
|
||||
| {
|
||||
oneOf: BlockIOSimpleTypeSubSchema[];
|
||||
default?: string | number | boolean | null;
|
||||
}
|
||||
);
|
||||
|
||||
/* Mirror of autogpt_server/data/graph.py:Node */
|
||||
export type Node = {
|
||||
|
||||
13
rnd/autogpt_builder/src/lib/types.ts
Normal file
13
rnd/autogpt_builder/src/lib/types.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NodeExecutionResult } from "@/lib/autogpt-server-api";
|
||||
|
||||
export type FlowRun = {
|
||||
id: string;
|
||||
graphID: string;
|
||||
graphVersion: number;
|
||||
status: "running" | "waiting" | "success" | "failed";
|
||||
startTime: number; // unix timestamp (ms)
|
||||
endTime: number; // unix timestamp (ms)
|
||||
duration: number; // seconds
|
||||
totalRunTime: number; // seconds
|
||||
nodeExecutionResults: NodeExecutionResult[];
|
||||
};
|
||||
@@ -45,6 +45,7 @@ export function getTypeTextColor(type: string | null): string {
|
||||
object: "text-purple-500",
|
||||
array: "text-indigo-500",
|
||||
null: "text-gray-500",
|
||||
any: "text-gray-500",
|
||||
"": "text-gray-500",
|
||||
}[type] || "text-gray-500"
|
||||
);
|
||||
@@ -61,6 +62,7 @@ export function getTypeBgColor(type: string | null): string {
|
||||
object: "bg-purple-500",
|
||||
array: "bg-indigo-500",
|
||||
null: "bg-gray-500",
|
||||
any: "bg-gray-500",
|
||||
"": "bg-gray-500",
|
||||
}[type] || "bg-gray-500"
|
||||
);
|
||||
@@ -76,6 +78,7 @@ export function getTypeColor(type: string | null): string {
|
||||
object: "#a855f7",
|
||||
array: "#6366f1",
|
||||
null: "#6b7280",
|
||||
any: "#6b7280",
|
||||
"": "#6b7280",
|
||||
}[type] || "#6b7280"
|
||||
);
|
||||
|
||||
@@ -466,6 +466,33 @@
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
|
||||
"@radix-ui/react-select@^2.1.1":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-2.1.1.tgz#df05cb0b29d3deaef83b505917c4042e0e418a9f"
|
||||
integrity sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==
|
||||
dependencies:
|
||||
"@radix-ui/number" "1.1.0"
|
||||
"@radix-ui/primitive" "1.1.0"
|
||||
"@radix-ui/react-collection" "1.1.0"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.0"
|
||||
"@radix-ui/react-direction" "1.1.0"
|
||||
"@radix-ui/react-dismissable-layer" "1.1.0"
|
||||
"@radix-ui/react-focus-guards" "1.1.0"
|
||||
"@radix-ui/react-focus-scope" "1.1.0"
|
||||
"@radix-ui/react-id" "1.1.0"
|
||||
"@radix-ui/react-popper" "1.2.0"
|
||||
"@radix-ui/react-portal" "1.1.1"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-slot" "1.1.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
"@radix-ui/react-use-previous" "1.1.0"
|
||||
"@radix-ui/react-visually-hidden" "1.1.0"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.7"
|
||||
|
||||
"@radix-ui/react-scroll-area@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz#50b24b0fc9ada151d176395bcf47b2ec68feada5"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import glob
|
||||
import importlib
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from autogpt_server.data.block import Block
|
||||
@@ -15,6 +16,11 @@ modules = [
|
||||
if os.path.isfile(f) and f.endswith(".py") and not f.endswith("__init__.py")
|
||||
]
|
||||
for module in modules:
|
||||
if not re.match("^[a-z_]+$", module):
|
||||
raise ValueError(
|
||||
f"Block module {module} error: module name must be lowercase, separated by underscores, and contain only alphabet characters"
|
||||
)
|
||||
|
||||
importlib.import_module(f".{module}", package=__name__)
|
||||
AVAILABLE_MODULES.append(module)
|
||||
|
||||
@@ -30,9 +36,16 @@ def all_subclasses(clz):
|
||||
|
||||
|
||||
for cls in all_subclasses(Block):
|
||||
if not cls.__name__.endswith("Block"):
|
||||
name = cls.__name__
|
||||
|
||||
if cls.__name__.endswith("Base"):
|
||||
continue
|
||||
|
||||
if not cls.__name__.endswith("Block"):
|
||||
raise ValueError(
|
||||
f"Block class {cls.__name__} does not end with 'Block', If you are creating an abstract class, please name the class with 'Base' at the end"
|
||||
)
|
||||
|
||||
block = cls()
|
||||
|
||||
if not isinstance(block.id, str) or len(block.id) != 36:
|
||||
|
||||
@@ -131,7 +131,7 @@ class WebScraperBlock(Block, GetRequest):
|
||||
yield "error", f"Request to Jina-ai Reader failed: {e}"
|
||||
|
||||
|
||||
class GetOpenWeatherMapWeather(Block, GetRequest):
|
||||
class GetOpenWeatherMapBlock(Block, GetRequest):
|
||||
class Input(BlockSchema):
|
||||
location: str
|
||||
api_key: BlockSecret = SecretField(key="openweathermap_api_key")
|
||||
@@ -146,8 +146,8 @@ class GetOpenWeatherMapWeather(Block, GetRequest):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="f7a8b2c3-6d4e-5f8b-9e7f-6d4e5f8b9e7f",
|
||||
input_schema=GetOpenWeatherMapWeather.Input,
|
||||
output_schema=GetOpenWeatherMapWeather.Output,
|
||||
input_schema=GetOpenWeatherMapBlock.Input,
|
||||
output_schema=GetOpenWeatherMapBlock.Output,
|
||||
test_input={
|
||||
"location": "New York",
|
||||
"api_key": "YOUR_API_KEY",
|
||||
|
||||
91
rnd/autogpt_server/autogpt_server/blocks/time_blocks.py
Normal file
91
rnd/autogpt_server/autogpt_server/blocks/time_blocks.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Union
|
||||
|
||||
from autogpt_server.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
|
||||
|
||||
class CurrentTimeBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
trigger: str
|
||||
|
||||
class Output(BlockSchema):
|
||||
time: str
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="a892b8d9-3e4e-4e9c-9c1e-75f8efcf1bfa",
|
||||
description="This block outputs the current time.",
|
||||
categories={BlockCategory.TEXT},
|
||||
input_schema=CurrentTimeBlock.Input,
|
||||
output_schema=CurrentTimeBlock.Output,
|
||||
test_input=[
|
||||
{"trigger": "Hello", "format": "{time}"},
|
||||
],
|
||||
test_output=[
|
||||
("time", time.strftime("%H:%M:%S")),
|
||||
],
|
||||
)
|
||||
|
||||
def run(self, input_data: Input) -> BlockOutput:
|
||||
current_time = time.strftime("%H:%M:%S")
|
||||
yield "time", current_time
|
||||
|
||||
|
||||
class CurrentDateBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
trigger: str
|
||||
offset: Union[int, str]
|
||||
|
||||
class Output(BlockSchema):
|
||||
date: str
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="b29c1b50-5d0e-4d9f-8f9d-1b0e6fcbf0b1",
|
||||
description="This block outputs the current date with an optional offset.",
|
||||
categories={BlockCategory.TEXT},
|
||||
input_schema=CurrentDateBlock.Input,
|
||||
output_schema=CurrentDateBlock.Output,
|
||||
test_input=[
|
||||
{"trigger": "Hello", "format": "{date}", "offset": "7"},
|
||||
],
|
||||
test_output=[
|
||||
("date", (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")),
|
||||
],
|
||||
)
|
||||
|
||||
def run(self, input_data: Input) -> BlockOutput:
|
||||
try:
|
||||
offset = int(input_data.offset)
|
||||
except ValueError:
|
||||
offset = 0
|
||||
current_date = datetime.now() - timedelta(days=offset)
|
||||
yield "date", current_date.strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
class CurrentDateAndTimeBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
trigger: str
|
||||
|
||||
class Output(BlockSchema):
|
||||
date_time: str
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="b29c1b50-5d0e-4d9f-8f9d-1b0e6fcbf0h2",
|
||||
description="This block outputs the current date and time.",
|
||||
categories={BlockCategory.TEXT},
|
||||
input_schema=CurrentDateAndTimeBlock.Input,
|
||||
output_schema=CurrentDateAndTimeBlock.Output,
|
||||
test_input=[
|
||||
{"trigger": "Hello", "format": "{date_time}"},
|
||||
],
|
||||
test_output=[
|
||||
("date_time", time.strftime("%Y-%m-%d %H:%M:%S")),
|
||||
],
|
||||
)
|
||||
|
||||
def run(self, input_data: Input) -> BlockOutput:
|
||||
current_date_time = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
yield "date_time", current_date_time
|
||||
@@ -7,7 +7,7 @@ from autogpt_server.data.block import Block, BlockOutput, BlockSchema
|
||||
from autogpt_server.data.model import SchemaField
|
||||
|
||||
|
||||
class YouTubeTranscriber(Block):
|
||||
class YouTubeTranscriberBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
youtube_url: str = SchemaField(
|
||||
description="The URL of the YouTube video to transcribe",
|
||||
@@ -24,8 +24,8 @@ class YouTubeTranscriber(Block):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="f3a8f7e1-4b1d-4e5f-9f2a-7c3d5a2e6b4c",
|
||||
input_schema=YouTubeTranscriber.Input,
|
||||
output_schema=YouTubeTranscriber.Output,
|
||||
input_schema=YouTubeTranscriberBlock.Input,
|
||||
output_schema=YouTubeTranscriberBlock.Output,
|
||||
test_input={"youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"},
|
||||
test_output=[
|
||||
("video_id", "dQw4w9WgXcQ"),
|
||||
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"id": "381164dd-3c91-43fd-ba93-c12a13ce8499",
|
||||
"version": 5,
|
||||
"is_active": false,
|
||||
"is_template": true,
|
||||
"name": "Discord Bot Chat To LLM",
|
||||
"description": "Simply send the bot the message \"!chat <message>\" and it will reply.",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "b8138bca-7892-42c2-9594-a845d3483413",
|
||||
"block_id": "d3f4g5h6-1i2j-3k4l-5m6n-7o8p9q0r1s2t",
|
||||
"input_default": {},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": -98.31744952152862,
|
||||
"y": 291.1279542656707
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "b667bcc4-4e17-4343-bd31-14e48d99d21d",
|
||||
"block_id": "e30a4d42-7b7d-4e6a-b36e-1f9b8e3b7d85",
|
||||
"input_default": {
|
||||
"input2": " Said: "
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 642.0641136440832,
|
||||
"y": -318.9010839696226
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "42eda7a9-fe29-45c8-9571-55222830142d",
|
||||
"block_id": "3146e4fe-2cdd-4f29-bd12-0c9d5bb4deb0",
|
||||
"input_default": {
|
||||
"pattern": "(?<=!chat ).*"
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 651.4338270731059,
|
||||
"y": 120.68871252027822
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "9049f063-5b07-4984-b211-068bc93e653a",
|
||||
"block_id": "1f292d4a-41a4-4977-9684-7c8d560b9f91",
|
||||
"input_default": {
|
||||
"model": "gpt-4o",
|
||||
"sys_prompt": "You are a nice friendly AI"
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 2099.785393180648,
|
||||
"y": -325.6642266305269
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dda2d061-2ef9-4dc5-9433-918c8395a4ac",
|
||||
"block_id": "h1i2j3k4-5l6m-7n8o-9p0q-r1s2t3u4v5w6",
|
||||
"input_default": {},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 2697.355782645,
|
||||
"y": 225.29000586164966
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3209c5e1-2da9-4cd1-bf4b-2f9488577815",
|
||||
"block_id": "1ff065e9-88e8-4358-9d82-8dc91f622ba9",
|
||||
"input_default": {
|
||||
"data": "DISCORD BOT API KEY HERE"
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": -772.5858672155341,
|
||||
"y": 26.390737439792503
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "b6411821-bd48-4543-b526-0f7138e8ffe9",
|
||||
"block_id": "1ff065e9-88e8-4358-9d82-8dc91f622ba9",
|
||||
"input_default": {
|
||||
"input": "DISCORD BOT API KEY HERE"
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": -778.4138607648867,
|
||||
"y": 422.0409097488691
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "d693cda1-973d-4d62-b549-d696b73d51d9",
|
||||
"block_id": "e30a4d42-7b7d-4e6a-b36e-1f9b8e3b7d85",
|
||||
"input_default": {},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 1325.5852307018679,
|
||||
"y": -328.95888935525124
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "80683364-c3e8-467b-a734-d5629f97cd30",
|
||||
"source_id": "b8138bca-7892-42c2-9594-a845d3483413",
|
||||
"sink_id": "42eda7a9-fe29-45c8-9571-55222830142d",
|
||||
"source_name": "message_content",
|
||||
"sink_name": "text",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "8510bd83-1444-4a70-99e3-26c3ae28d7bf",
|
||||
"source_id": "42eda7a9-fe29-45c8-9571-55222830142d",
|
||||
"sink_id": "3209c5e1-2da9-4cd1-bf4b-2f9488577815",
|
||||
"source_name": "negative",
|
||||
"sink_name": "input",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "ff48a673-1f18-4b05-b5e7-e6dcc3e65add",
|
||||
"source_id": "b8138bca-7892-42c2-9594-a845d3483413",
|
||||
"sink_id": "dda2d061-2ef9-4dc5-9433-918c8395a4ac",
|
||||
"source_name": "channel_name",
|
||||
"sink_name": "channel_name",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "aebf9b2b-ee01-41bf-9c05-6444b6e5aa44",
|
||||
"source_id": "3209c5e1-2da9-4cd1-bf4b-2f9488577815",
|
||||
"sink_id": "b8138bca-7892-42c2-9594-a845d3483413",
|
||||
"source_name": "output",
|
||||
"sink_name": "discord_bot_token",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "cdbf9290-1b63-463d-a869-a16734ebd03c",
|
||||
"source_id": "9049f063-5b07-4984-b211-068bc93e653a",
|
||||
"sink_id": "dda2d061-2ef9-4dc5-9433-918c8395a4ac",
|
||||
"source_name": "response",
|
||||
"sink_name": "message_content",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "d9a51e17-c8de-4835-bee1-c1abba457c35",
|
||||
"source_id": "dda2d061-2ef9-4dc5-9433-918c8395a4ac",
|
||||
"sink_id": "3209c5e1-2da9-4cd1-bf4b-2f9488577815",
|
||||
"source_name": "status",
|
||||
"sink_name": "input",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "7bea8f77-45d7-4884-974f-b8f5ad10a988",
|
||||
"source_id": "b6411821-bd48-4543-b526-0f7138e8ffe9",
|
||||
"sink_id": "b8138bca-7892-42c2-9594-a845d3483413",
|
||||
"source_name": "output",
|
||||
"sink_name": "discord_bot_token",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "f2427ca7-3adf-450f-8be4-b8042eb0b9a6",
|
||||
"source_id": "b8138bca-7892-42c2-9594-a845d3483413",
|
||||
"sink_id": "b667bcc4-4e17-4343-bd31-14e48d99d21d",
|
||||
"source_name": "username",
|
||||
"sink_name": "input1",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "117244bf-8c32-4096-baff-38cd0fa9cf9d",
|
||||
"source_id": "b667bcc4-4e17-4343-bd31-14e48d99d21d",
|
||||
"sink_id": "d693cda1-973d-4d62-b549-d696b73d51d9",
|
||||
"source_name": "output",
|
||||
"sink_name": "input1",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "9ee4a0a5-de27-4bf8-81a9-140db1b5e475",
|
||||
"source_id": "d693cda1-973d-4d62-b549-d696b73d51d9",
|
||||
"sink_id": "9049f063-5b07-4984-b211-068bc93e653a",
|
||||
"source_name": "output",
|
||||
"sink_name": "prompt",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "49da866a-8c13-469c-95ea-fe4685e95c75",
|
||||
"source_id": "42eda7a9-fe29-45c8-9571-55222830142d",
|
||||
"sink_id": "d693cda1-973d-4d62-b549-d696b73d51d9",
|
||||
"source_name": "positive",
|
||||
"sink_name": "input2",
|
||||
"is_static": false
|
||||
}
|
||||
]
|
||||
}
|
||||
266
rnd/autogpt_server/graph_templates/Discord Search Bot_v17.json
Normal file
266
rnd/autogpt_server/graph_templates/Discord Search Bot_v17.json
Normal file
@@ -0,0 +1,266 @@
|
||||
{
|
||||
"id": "696b4b9c-f28f-4dda-a44c-e748ac22438f",
|
||||
"version": 17,
|
||||
"is_active": false,
|
||||
"is_template": true,
|
||||
"name": "Discord Search Bot",
|
||||
"description": "This is a Discord search bot, send it the command \"!search <question>\" and it will do a web search and answer your question!",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "60ba4aac-1751-4be7-8745-1bd32191d4a2",
|
||||
"block_id": "d3f4g5h6-1i2j-3k4l-5m6n-7o8p9q0r1s2t",
|
||||
"input_default": {},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": -961.2660758713816,
|
||||
"y": 333.47185665649613
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "b09e201a-cd71-42d4-a197-22e7eebc54c9",
|
||||
"block_id": "e30a4d42-7b7d-4e6a-b36e-1f9b8e3b7d85",
|
||||
"input_default": {
|
||||
"input2": ", Here is the latest web info to answer the question : \n"
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 881.3259434267115,
|
||||
"y": -564.3287840347994
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3169d1a8-b541-43f7-97ce-ddc6aecb2080",
|
||||
"block_id": "3146e4fe-2cdd-4f29-bd12-0c9d5bb4deb0",
|
||||
"input_default": {
|
||||
"pattern": "(?<=!search ).*"
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": -284.1111358361005,
|
||||
"y": -43.71794261767991
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5658c4f7-8e67-4d30-93f2-157bdbd3ef87",
|
||||
"block_id": "b2c3d4e5-6f7g-8h9i-0j1k-l2m3n4o5p6q7",
|
||||
"input_default": {},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 319.9343851243159,
|
||||
"y": -48.49947115893917
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "b29e3831-3fb7-41bd-88d8-ce3a5dde3d69",
|
||||
"block_id": "1f292d4a-41a4-4977-9684-7c8d560b9f91",
|
||||
"input_default": {
|
||||
"model": "gpt-4o",
|
||||
"sys_prompt": "You are a question answerer and info summariser, answer the questions with the info you are provided, be sure to @ the user who asked the question in your reply like @username"
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 2085.06017081387,
|
||||
"y": -387.5334342999411
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "164bc3ea-e812-4391-a62d-bdddcf86f3cd",
|
||||
"block_id": "e30a4d42-7b7d-4e6a-b36e-1f9b8e3b7d85",
|
||||
"input_default": {},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 1469.6744442484253,
|
||||
"y": -435.0392111332514
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "10759047-6387-4ff1-9117-bbef47d24ee8",
|
||||
"block_id": "e30a4d42-7b7d-4e6a-b36e-1f9b8e3b7d85",
|
||||
"input_default": {},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 326.8949613725521,
|
||||
"y": -579.6877803706152
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "af7c5160-7bf0-4ad0-9806-04222009091f",
|
||||
"block_id": "e30a4d42-7b7d-4e6a-b36e-1f9b8e3b7d85",
|
||||
"input_default": {
|
||||
"input2": " Asked the question: "
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": -265.6965655001714,
|
||||
"y": -628.1379507780849
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4d74513d-42f7-4fd0-808a-0f4844513966",
|
||||
"block_id": "1ff065e9-88e8-4358-9d82-8dc91f622ba9",
|
||||
"input_default": {
|
||||
"input": "DISCORD BOT API KEY HERE"
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": -1532.6418163253616,
|
||||
"y": 587.6533051108552
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "f3d62f22-d193-4f04-85d2-164200fca4c0",
|
||||
"block_id": "h1i2j3k4-5l6m-7n8o-9p0q-r1s2t3u4v5w6",
|
||||
"input_default": {},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": 2814.192971071703,
|
||||
"y": 310.74654561036294
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3b2bb6a5-9c42-4189-a9a0-0e499ccb766a",
|
||||
"block_id": "1ff065e9-88e8-4358-9d82-8dc91f622ba9",
|
||||
"input_default": {
|
||||
"data": "DISCORD BOT API KEY HERE"
|
||||
},
|
||||
"metadata": {
|
||||
"position": {
|
||||
"x": -1528.6418163253616,
|
||||
"y": 119.65330511085517
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "346a8259-1093-4374-8271-904742aa6d89",
|
||||
"source_id": "b29e3831-3fb7-41bd-88d8-ce3a5dde3d69",
|
||||
"sink_id": "f3d62f22-d193-4f04-85d2-164200fca4c0",
|
||||
"source_name": "response",
|
||||
"sink_name": "message_content",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "53a8ecc6-60b6-4f4a-90c4-cb11dd1874e0",
|
||||
"source_id": "5658c4f7-8e67-4d30-93f2-157bdbd3ef87",
|
||||
"sink_id": "164bc3ea-e812-4391-a62d-bdddcf86f3cd",
|
||||
"source_name": "results",
|
||||
"sink_name": "input2",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "2b3fd279-5816-48da-b2ab-484497fe67d5",
|
||||
"source_id": "f3d62f22-d193-4f04-85d2-164200fca4c0",
|
||||
"sink_id": "3b2bb6a5-9c42-4189-a9a0-0e499ccb766a",
|
||||
"source_name": "status",
|
||||
"sink_name": "input",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "bb036c88-4031-4c6c-a70b-a82f5e50a013",
|
||||
"source_id": "4d74513d-42f7-4fd0-808a-0f4844513966",
|
||||
"sink_id": "60ba4aac-1751-4be7-8745-1bd32191d4a2",
|
||||
"source_name": "output",
|
||||
"sink_name": "discord_bot_token",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "e67befdc-59b5-47bf-9663-8baeeef026f7",
|
||||
"source_id": "3169d1a8-b541-43f7-97ce-ddc6aecb2080",
|
||||
"sink_id": "10759047-6387-4ff1-9117-bbef47d24ee8",
|
||||
"source_name": "positive",
|
||||
"sink_name": "input2",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "9c0fa608-ceea-44cd-98cf-8a2d6ed25b24",
|
||||
"source_id": "60ba4aac-1751-4be7-8745-1bd32191d4a2",
|
||||
"sink_id": "af7c5160-7bf0-4ad0-9806-04222009091f",
|
||||
"source_name": "username",
|
||||
"sink_name": "input1",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "ad5e1bd6-69bd-4846-87dc-e08d8d2e0f2b",
|
||||
"source_id": "af7c5160-7bf0-4ad0-9806-04222009091f",
|
||||
"sink_id": "10759047-6387-4ff1-9117-bbef47d24ee8",
|
||||
"source_name": "output",
|
||||
"sink_name": "input1",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "96f4b2fd-82d8-4754-9f41-f65e8e1f565a",
|
||||
"source_id": "60ba4aac-1751-4be7-8745-1bd32191d4a2",
|
||||
"sink_id": "3169d1a8-b541-43f7-97ce-ddc6aecb2080",
|
||||
"source_name": "message_content",
|
||||
"sink_name": "text",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "ec6666bc-4d54-4960-b3b1-13a0b4a872a7",
|
||||
"source_id": "3b2bb6a5-9c42-4189-a9a0-0e499ccb766a",
|
||||
"sink_id": "60ba4aac-1751-4be7-8745-1bd32191d4a2",
|
||||
"source_name": "output",
|
||||
"sink_name": "discord_bot_token",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "ccd08d1f-7ccc-42fa-882c-91f6991ad5e8",
|
||||
"source_id": "b09e201a-cd71-42d4-a197-22e7eebc54c9",
|
||||
"sink_id": "164bc3ea-e812-4391-a62d-bdddcf86f3cd",
|
||||
"source_name": "output",
|
||||
"sink_name": "input1",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "3ed20f9c-3f79-41e4-8fab-0309e92ac629",
|
||||
"source_id": "60ba4aac-1751-4be7-8745-1bd32191d4a2",
|
||||
"sink_id": "f3d62f22-d193-4f04-85d2-164200fca4c0",
|
||||
"source_name": "channel_name",
|
||||
"sink_name": "channel_name",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "89a129e5-11d2-4fac-9a15-7de182a2b806",
|
||||
"source_id": "164bc3ea-e812-4391-a62d-bdddcf86f3cd",
|
||||
"sink_id": "b29e3831-3fb7-41bd-88d8-ce3a5dde3d69",
|
||||
"source_name": "output",
|
||||
"sink_name": "prompt",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "7978ef39-d862-441d-936f-8da60fefcab6",
|
||||
"source_id": "10759047-6387-4ff1-9117-bbef47d24ee8",
|
||||
"sink_id": "b09e201a-cd71-42d4-a197-22e7eebc54c9",
|
||||
"source_name": "output",
|
||||
"sink_name": "input1",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "32e3bace-5df7-4683-97f2-7d9864878aee",
|
||||
"source_id": "3169d1a8-b541-43f7-97ce-ddc6aecb2080",
|
||||
"sink_id": "5658c4f7-8e67-4d30-93f2-157bdbd3ef87",
|
||||
"source_name": "positive",
|
||||
"sink_name": "query",
|
||||
"is_static": false
|
||||
},
|
||||
{
|
||||
"id": "0ab7dce1-84b6-4f96-9eb2-1b458fe205a5",
|
||||
"source_id": "3169d1a8-b541-43f7-97ce-ddc6aecb2080",
|
||||
"sink_id": "3b2bb6a5-9c42-4189-a9a0-0e499ccb766a",
|
||||
"source_name": "negative",
|
||||
"sink_name": "input",
|
||||
"is_static": false
|
||||
}
|
||||
]
|
||||
}
|
||||
23
rnd/infra/helm/autogpt_builder/.helmignore
Normal file
23
rnd/infra/helm/autogpt_builder/.helmignore
Normal file
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
24
rnd/infra/helm/autogpt_builder/Chart.yaml
Normal file
24
rnd/infra/helm/autogpt_builder/Chart.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
apiVersion: v2
|
||||
name: autogpt_builder
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
||||
22
rnd/infra/helm/autogpt_builder/templates/NOTES.txt
Normal file
22
rnd/infra/helm/autogpt_builder/templates/NOTES.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "autogpt_builder.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "autogpt_builder.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "autogpt_builder.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "autogpt_builder.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
||||
62
rnd/infra/helm/autogpt_builder/templates/_helpers.tpl
Normal file
62
rnd/infra/helm/autogpt_builder/templates/_helpers.tpl
Normal file
@@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "autogpt_builder.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "autogpt_builder.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "autogpt_builder.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "autogpt_builder.labels" -}}
|
||||
helm.sh/chart: {{ include "autogpt_builder.chart" . }}
|
||||
{{ include "autogpt_builder.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "autogpt_builder.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "autogpt_builder.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "autogpt_builder.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "autogpt_builder.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
68
rnd/infra/helm/autogpt_builder/templates/deployment.yaml
Normal file
68
rnd/infra/helm/autogpt_builder/templates/deployment.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "autogpt_builder.fullname" . }}
|
||||
labels:
|
||||
{{- include "autogpt_builder.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "autogpt_builder.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "autogpt_builder.labels" . | nindent 8 }}
|
||||
{{- with .Values.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "autogpt_builder.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.port }}
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
{{- toYaml .Values.livenessProbe | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml .Values.readinessProbe | nindent 12 }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.volumeMounts }}
|
||||
volumeMounts:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.volumes }}
|
||||
volumes:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
32
rnd/infra/helm/autogpt_builder/templates/hpa.yaml
Normal file
32
rnd/infra/helm/autogpt_builder/templates/hpa.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "autogpt_builder.fullname" . }}
|
||||
labels:
|
||||
{{- include "autogpt_builder.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "autogpt_builder.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
61
rnd/infra/helm/autogpt_builder/templates/ingress.yaml
Normal file
61
rnd/infra/helm/autogpt_builder/templates/ingress.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "autogpt_builder.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
|
||||
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
|
||||
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "autogpt_builder.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
|
||||
pathType: {{ .pathType }}
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- else }}
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: networking.gke.io/v1
|
||||
kind: ManagedCertificate
|
||||
metadata:
|
||||
name: {{ include "autogpt-builder.fullname" . }}-cert
|
||||
spec:
|
||||
domains:
|
||||
- {{ .Values.domain }}
|
||||
15
rnd/infra/helm/autogpt_builder/templates/service.yaml
Normal file
15
rnd/infra/helm/autogpt_builder/templates/service.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "autogpt_builder.fullname" . }}
|
||||
labels:
|
||||
{{- include "autogpt_builder.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "autogpt_builder.selectorLabels" . | nindent 4 }}
|
||||
13
rnd/infra/helm/autogpt_builder/templates/serviceaccount.yaml
Normal file
13
rnd/infra/helm/autogpt_builder/templates/serviceaccount.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "autogpt_builder.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "autogpt_builder.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "autogpt_builder.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "autogpt_builder.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "autogpt_builder.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
||||
77
rnd/infra/helm/autogpt_builder/values.dev.yaml
Normal file
77
rnd/infra/helm/autogpt_builder/values.dev.yaml
Normal file
@@ -0,0 +1,77 @@
|
||||
# dev values, overwrite base values as needed.
|
||||
|
||||
image:
|
||||
repository: us-east1-docker.pkg.dev/agpt-dev/agpt-builder-dev/agpt-builder-dev
|
||||
pullPolicy: Always
|
||||
tag: "latest"
|
||||
|
||||
serviceAccount:
|
||||
annotations:
|
||||
iam.gke.io/gcp-service-account: "dev-agpt-builder-sa@agpt-dev.iam.gserviceaccount.com"
|
||||
name: "dev-agpt-builder-sa"
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8000
|
||||
targetPort: 3000
|
||||
annotations:
|
||||
cloud.google.com/neg: '{"ingress": true}'
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: "gce"
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: gce
|
||||
kubernetes.io/ingress.global-static-ip-name: "agpt-dev-agpt-builder-ip"
|
||||
networking.gke.io/managed-certificates: "autogpt-builder-cert"
|
||||
kubernetes.io/ingress.allow-http: "true"
|
||||
hosts:
|
||||
- host: dev-builder.agpt.co
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: autogpt-builder
|
||||
port: 8000
|
||||
defaultBackend:
|
||||
service:
|
||||
name: autogpt-builder
|
||||
port:
|
||||
number: 8000
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 3000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 6
|
||||
|
||||
domain: "dev-builder.agpt.co"
|
||||
|
||||
|
||||
env:
|
||||
APP_ENV: "dev"
|
||||
NEXT_PUBLIC_AGPT_SERVER_URL: "http://agpt-server:8000/api"
|
||||
GOOGLE_CLIENT_ID: "638488734936-ka0bvq73ub3h4cb6013s3lftsl5l04nu.apps.googleusercontent.com"
|
||||
GOOGLE_CLIENT_SECRET: ""
|
||||
NEXT_PUBLIC_SUPABASE_URL: "https://adfjtextkuilwuhzdjpf.supabase.co"
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY: ""
|
||||
76
rnd/infra/helm/autogpt_builder/values.yaml
Normal file
76
rnd/infra/helm/autogpt_builder/values.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
# base values, environment specific variables should be specified/overwritten in environment values
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: us-east1-docker.pkg.dev/agpt-dev/agpt-builder-dev/agpt-builder-dev
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "latest"
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
automount: true
|
||||
annotations: {}
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
|
||||
securityContext: {}
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: ImplementationSpecific
|
||||
tls: []
|
||||
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 1
|
||||
maxReplicas: 100
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
|
||||
volumes: []
|
||||
|
||||
volumeMounts: []
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
domain: ""
|
||||
Reference in New Issue
Block a user