mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-02 02:45:18 -05:00
Compare commits
57 Commits
test/verif
...
aarushikan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2849b4b5b | ||
|
|
1ffa261518 | ||
|
|
189eccb523 | ||
|
|
cbb81a359b | ||
|
|
4565635646 | ||
|
|
7479d18705 | ||
|
|
bfb3a45722 | ||
|
|
68f46c00cd | ||
|
|
72c633241c | ||
|
|
e676124527 | ||
|
|
03df00c4a8 | ||
|
|
2359a7d109 | ||
|
|
a15ad5cf89 | ||
|
|
e7c569668f | ||
|
|
dc3a9b402b | ||
|
|
6b89941e9c | ||
|
|
95ccc058d7 | ||
|
|
e46cfb1161 | ||
|
|
c25f32e389 | ||
|
|
94e2d0e2d3 | ||
|
|
a30b3f7d76 | ||
|
|
5617a64598 | ||
|
|
20152cf5e0 | ||
|
|
01e6fdd925 | ||
|
|
f673f8ae23 | ||
|
|
5243c9d7fd | ||
|
|
be14969ec7 | ||
|
|
db14af2833 | ||
|
|
8c212e565a | ||
|
|
0716b5967f | ||
|
|
75a5ee8207 | ||
|
|
66384c1795 | ||
|
|
9be9dbed08 | ||
|
|
2a434625ef | ||
|
|
483cd07c9e | ||
|
|
ab830b5e4c | ||
|
|
2e6d0944ae | ||
|
|
97581a709c | ||
|
|
9fe1efac68 | ||
|
|
2e3e8ac11e | ||
|
|
2c56b8519f | ||
|
|
d3c8cf0a57 | ||
|
|
fad8e30d6e | ||
|
|
d06d8a2f2c | ||
|
|
42ee23d243 | ||
|
|
a57d27a811 | ||
|
|
01cc25bfd2 | ||
|
|
50d92d164a | ||
|
|
aedb8d8823 | ||
|
|
bded7916c2 | ||
|
|
b376e84556 | ||
|
|
2f4fe3311e | ||
|
|
741e4db8b1 | ||
|
|
5969d4444d | ||
|
|
76cfeef789 | ||
|
|
7337e2fd0d | ||
|
|
48740d9a3c |
27
.github/workflows/autogpt-server-ci.yml
vendored
27
.github/workflows/autogpt-server-ci.yml
vendored
@@ -6,11 +6,13 @@ on:
|
||||
paths:
|
||||
- ".github/workflows/autogpt-server-ci.yml"
|
||||
- "rnd/autogpt_server/**"
|
||||
- "rnd/autogpt_builder/**"
|
||||
pull_request:
|
||||
branches: [master, development, release-*]
|
||||
paths:
|
||||
- ".github/workflows/autogpt-server-ci.yml"
|
||||
- "rnd/autogpt_server/**"
|
||||
- "rnd/autogpt_builder/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ format('autogpt-server-ci-{0}', github.head_ref && format('{0}-{1}', github.event_name, github.event.pull_request.number) || github.sha) }}
|
||||
@@ -144,6 +146,17 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '21'
|
||||
|
||||
- name: Install dependencies for autogpt_builder
|
||||
working-directory: rnd/autogpt_builder
|
||||
run: |
|
||||
yarn install
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
@@ -183,11 +196,25 @@ jobs:
|
||||
- name: Install Python dependencies
|
||||
run: poetry install
|
||||
|
||||
- name: Install Prisma Dependencies
|
||||
run: |
|
||||
mkdir prisma
|
||||
poetry run prisma py fetch --force
|
||||
env:
|
||||
PRISMA_BINARY_CACHE_DIR: "./prisma"
|
||||
PRISMA_HOME_DIR: "./prisma"
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: poetry run prisma generate
|
||||
env:
|
||||
PRISMA_BINARY_CACHE_DIR: "./prisma"
|
||||
PRISMA_HOME_DIR: "./prisma"
|
||||
|
||||
- name: Run Database Migrations
|
||||
run: poetry run prisma migrate dev --name updates
|
||||
env:
|
||||
PRISMA_BINARY_CACHE_DIR: "./prisma"
|
||||
PRISMA_HOME_DIR: "./prisma"
|
||||
|
||||
- name: install rpm
|
||||
if: matrix.platform-os == 'ubuntu'
|
||||
|
||||
1
rnd/.gitignore
vendored
Normal file
1
rnd/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
frontend/
|
||||
@@ -1,3 +1,10 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
"extends": [
|
||||
"next/core-web-vitals",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ dotenv.config();
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'export',
|
||||
env: {
|
||||
AGPT_SERVER_URL: process.env.AGPT_SERVER_URL,
|
||||
},
|
||||
@@ -17,6 +18,12 @@ const nextConfig = {
|
||||
},
|
||||
];
|
||||
},
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"moment": "^2.30.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"lucide-react": "^0.407.0",
|
||||
"moment": "^2.30.1",
|
||||
"next": "14.2.4",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18",
|
||||
@@ -36,7 +36,9 @@
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"eslint": "^8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.16.0",
|
||||
"@typescript-eslint/parser": "^7.16.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "14.2.4",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
|
||||
@@ -1,43 +1,54 @@
|
||||
"use client";
|
||||
import React, { Suspense } from "react";
|
||||
import Image from "next/image";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import FlowEditor from '@/components/Flow';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col items-center px-12">
|
||||
<div className="z-10 w-full items-center justify-between font-mono text-sm lg:flex">
|
||||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-600 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-900 dark:bg-zinc-900 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
||||
Get started by adding a
|
||||
<code className="font-mono font-bold">node</code>
|
||||
</p>
|
||||
<div
|
||||
className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:size-auto lg:bg-none"
|
||||
>
|
||||
<a
|
||||
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
||||
href="https://news.agpt.co/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
By{" "}
|
||||
<Image
|
||||
src="/AUTOgpt_Logo_dark.png"
|
||||
alt="AutoGPT Logo"
|
||||
width={100}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
function FlowEditorWrapper() {
|
||||
const searchParams = useSearchParams();
|
||||
const flowID = searchParams.get("flowID") ?? undefined;
|
||||
|
||||
<div className="w-full flex justify-center mt-10">
|
||||
<FlowEditor
|
||||
className="flow-container w-full min-h-[75vh] border border-gray-300 dark:border-gray-700 rounded-lg"
|
||||
flowID={useSearchParams().get("flowID") ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<FlowEditor
|
||||
className="flow-container w-full min-h-[75vh] border border-gray-300 dark:border-gray-700 rounded-lg"
|
||||
flowID={flowID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col items-center px-12">
|
||||
<div className="z-10 w-full items-center justify-between font-mono text-sm lg:flex">
|
||||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-600 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-900 dark:bg-zinc-900 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
||||
Get started by adding a
|
||||
<code className="font-mono font-bold">node</code>
|
||||
</p>
|
||||
<div
|
||||
className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:size-auto lg:bg-none"
|
||||
>
|
||||
<a
|
||||
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
||||
href="https://news.agpt.co/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
By{" "}
|
||||
<Image
|
||||
src="/AUTOgpt_Logo_dark.png"
|
||||
alt="AutoGPT Logo"
|
||||
width={100}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex justify-center mt-10">
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<FlowEditorWrapper />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ThemeProvider as NextThemeProvider } from "next-themes";
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
||||
import { Inter } from "next/font/google";
|
||||
import Link from "next/link";
|
||||
import { CubeIcon, Pencil1Icon, ReaderIcon, TimerIcon } from "@radix-ui/react-icons";
|
||||
import { Pencil1Icon, TimerIcon } from "@radix-ui/react-icons";
|
||||
|
||||
import "./globals.css";
|
||||
|
||||
|
||||
@@ -266,6 +266,7 @@ const AgentFlowList = (
|
||||
</Card>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const FlowStatusBadge = ({ status }: { status: "active" | "disabled" | "failing" }) => (
|
||||
<Badge
|
||||
variant="default"
|
||||
@@ -538,7 +539,7 @@ const FlowRunsTimeline = (
|
||||
tickFormatter={s => s > 90 ? `${Math.round(s / 60)}m` : `${s}s`}
|
||||
/>
|
||||
<Tooltip
|
||||
content={({ payload, label }) => {
|
||||
content={({ payload }) => {
|
||||
if (payload && payload.length) {
|
||||
const data: FlowRun & { time: number, _duration: number } = payload[0].payload;
|
||||
const flow = flows.find(f => f.id === data.flowID);
|
||||
|
||||
@@ -116,6 +116,16 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
setErrors((prevErrors) => ({ ...prevErrors, [key]: null }));
|
||||
};
|
||||
|
||||
const getSelectValue = (value: any): string => {
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'number') return value.toString();
|
||||
if (Array.isArray(value)) return value.join(',');
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const getValue = (key: string) => {
|
||||
const keys = key.split('.');
|
||||
return keys.reduce((acc, k) => (acc && acc[k] !== undefined) ? acc[k] : '', data.hardcodedValues);
|
||||
@@ -124,7 +134,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
const isHandleConnected = (key: string) => {
|
||||
return data.connections && data.connections.some((conn: any) => {
|
||||
if (typeof conn === 'string') {
|
||||
const [source, target] = conn.split(' -> ');
|
||||
const [ target ] = conn.split(' -> ');
|
||||
return target.includes(key) && target.includes(data.title);
|
||||
}
|
||||
return conn.target === id && conn.targetHandle === key;
|
||||
@@ -300,7 +310,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
return schema.enum ? (
|
||||
<div key={fullKey} className="input-container">
|
||||
<select
|
||||
value={value || ''}
|
||||
value={getSelectValue(value)}
|
||||
onChange={(e) => handleInputChange(fullKey, e.target.value)}
|
||||
className="select-input"
|
||||
>
|
||||
@@ -340,7 +350,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
<div key={fullKey} className="input-container">
|
||||
<input
|
||||
type="number"
|
||||
value={value || ''}
|
||||
value={["number", "string"].includes(typeof value) ? value : ''}
|
||||
onChange={(e) => handleInputChange(fullKey, parseFloat(e.target.value))}
|
||||
className="number-input"
|
||||
/>
|
||||
@@ -349,7 +359,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
);
|
||||
case 'array':
|
||||
if (schema.items && schema.items.type === 'string') {
|
||||
const arrayValues = value || [];
|
||||
const arrayValues = Array.isArray(value) ? value : [];
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{arrayValues.map((item: string, index: number) => (
|
||||
@@ -360,7 +370,11 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
onChange={(e) => handleInputChange(`${fullKey}.${index}`, e.target.value)}
|
||||
className="array-item-input"
|
||||
/>
|
||||
<Button onClick={() => handleInputChange(`${fullKey}.${index}`, '')} className="array-item-remove">
|
||||
<Button onClick={() => {
|
||||
const newArray = [...arrayValues];
|
||||
newArray.splice(index, 1);
|
||||
handleInputChange(fullKey, newArray);
|
||||
}} className="array-item-remove">
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
@@ -372,7 +386,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
return <div key={fullKey}></div>;
|
||||
default:
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
@@ -383,28 +397,6 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const validateInputs = () => {
|
||||
const newErrors: { [key: string]: string | null } = {};
|
||||
const validateRecursive = (schema: any, parentKey: string = '') => {
|
||||
Object.entries(schema.properties).forEach(([key, propSchema]: [string, any]) => {
|
||||
const fullKey = parentKey ? `${parentKey}.${key}` : key;
|
||||
const value = getValue(fullKey);
|
||||
|
||||
if (propSchema.type === 'object' && propSchema.properties) {
|
||||
validateRecursive(propSchema, fullKey);
|
||||
} else {
|
||||
if (propSchema.required && !value) {
|
||||
newErrors[fullKey] = `${fullKey} is required`;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
validateRecursive(data.inputSchema);
|
||||
setErrors(newErrors);
|
||||
return Object.values(newErrors).every((error) => error === null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`custom-node dark-theme ${data.status === 'RUNNING' ? 'running' : data.status === 'COMPLETED' ? 'completed' : data.status === 'FAILED' ? 'failed' :''}`}>
|
||||
<div className="node-header">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-nocheck
|
||||
"use client";
|
||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import ReactFlow, {
|
||||
@@ -129,6 +130,7 @@ const FlowEditor: React.FC<{ flowID?: string; className?: string }> = ({
|
||||
const onConnect: OnConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
setEdges((eds) => addEdge(connection, eds));
|
||||
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => {
|
||||
if (node.id === connection.target) {
|
||||
@@ -253,6 +255,7 @@ const FlowEditor: React.FC<{ flowID?: string; className?: string }> = ({
|
||||
})));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const prepareNodeInputData = (node: Node<CustomNodeData>, allNodes: Node<CustomNodeData>[], allEdges: Edge[]) => {
|
||||
console.log("Preparing input data for node:", node.id, node.data.blockType);
|
||||
|
||||
@@ -285,7 +288,7 @@ const FlowEditor: React.FC<{ flowID?: string; className?: string }> = ({
|
||||
return inputData;
|
||||
};
|
||||
|
||||
let inputData = getNestedData(blockSchema, node.data.hardcodedValues);
|
||||
const inputData = getNestedData(blockSchema, node.data.hardcodedValues);
|
||||
|
||||
console.log(`Final prepared input for ${node.data.blockType} (${node.id}):`, inputData);
|
||||
return inputData;
|
||||
|
||||
@@ -60,8 +60,8 @@ function Calendar({
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ ...props }) => <ChevronLeftIcon className="h-4 w-4" />,
|
||||
IconRight: ({ ...props }) => <ChevronRightIcon className="h-4 w-4" />,
|
||||
IconLeft: () => <ChevronLeftIcon className="h-4 w-4" />,
|
||||
IconRight: () => <ChevronRightIcon className="h-4 w-4" />,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
@@ -21,6 +27,13 @@
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,11 @@
|
||||
from multiprocessing import freeze_support, set_start_method
|
||||
import pathlib
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from autogpt_server.executor import ExecutionManager, ExecutionScheduler
|
||||
from autogpt_server.server import AgentServer
|
||||
from autogpt_server.util.data import get_prisma_exe_path
|
||||
from autogpt_server.util.process import AppProcess
|
||||
from autogpt_server.util.service import PyroNameServer
|
||||
|
||||
@@ -31,6 +36,17 @@ def main(**kwargs):
|
||||
set_start_method("spawn", force=True)
|
||||
freeze_support()
|
||||
|
||||
# if frozen on windows
|
||||
# if getattr(sys, "frozen", False) and sys.platform == "win32":
|
||||
# # The application is frozen
|
||||
# # copy the prisma exe from get_prisma_exe to user directory
|
||||
# query_file_location = get_prisma_exe_path()
|
||||
# # copy the prisma exe to the windows user directory
|
||||
# query_file_location_user = (
|
||||
# pathlib.Path.home() / "prisma-query-engine-windows.exe"
|
||||
# )
|
||||
# shutil.copyfile(query_file_location, query_file_location_user)
|
||||
|
||||
run_processes(
|
||||
[
|
||||
PyroNameServer(),
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import asyncio
|
||||
import mimetypes
|
||||
import os
|
||||
import uuid
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Annotated, Any, Dict
|
||||
from typing import Annotated, Any, Dict, MutableMapping
|
||||
|
||||
from starlette.responses import Response
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
import uvicorn
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
@@ -13,7 +17,7 @@ from fastapi import (
|
||||
WebSocketDisconnect,
|
||||
)
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
import autogpt_server.server.ws_api
|
||||
@@ -178,20 +182,63 @@ class AgentServer(AppService):
|
||||
methods=["POST"],
|
||||
)
|
||||
|
||||
app.add_exception_handler(500, self.handle_internal_error) # type: ignore
|
||||
app.add_exception_handler(500, self.handle_internal_error)
|
||||
|
||||
app.mount(
|
||||
path="/frontend",
|
||||
app=StaticFiles(directory=get_frontend_path(), html=True),
|
||||
name="example_files",
|
||||
)
|
||||
class SPAStaticFiles(StaticFiles):
|
||||
async def get_response(
|
||||
self, path: str, scope: MutableMapping[str, Any]
|
||||
) -> Response:
|
||||
try:
|
||||
return await super().get_response(path, scope)
|
||||
except (HTTPException, StarletteHTTPException) as ex:
|
||||
if ex.status_code == 404:
|
||||
return await super().get_response("build.html", scope)
|
||||
else:
|
||||
raise ex
|
||||
|
||||
app.include_router(router)
|
||||
|
||||
#! WARNING: THE ORDER OF THE REST OF THESE FUNCTIONS IS IMPORTANT
|
||||
#! DO NOT CHANGE THE ORDER OF THESE FUNCTIONS
|
||||
#! DO NOT ADD ANY FUNCTIONS BETWEEN THESE FUNCTIONS
|
||||
#! DO NOT REMOVE ANY OF THESE FUNCTIONS
|
||||
#! YOU WILL BREAK THE FRONTEND PACKAGED WITH THE SERVER
|
||||
|
||||
if os.path.exists(get_frontend_path() / "_next"):
|
||||
app.mount(
|
||||
path="/_next",
|
||||
app=SPAStaticFiles(directory=get_frontend_path() / "_next", html=True),
|
||||
name="frontend",
|
||||
)
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket): # type: ignore
|
||||
await self.websocket_router(websocket)
|
||||
|
||||
@app.get("/{extras_file:str}", response_class=HTMLResponse)
|
||||
async def catch_others(extras_file: str):
|
||||
# Do not allow directory traversal
|
||||
if ".." in extras_file:
|
||||
raise HTTPException(status_code=400, detail="Invalid file path")
|
||||
if extras_file.count(".") > 1:
|
||||
raise HTTPException(status_code=400, detail="Invalid file path")
|
||||
# check if the file is directly in the frontend path folder to further prevent directory traversal against the allowed files
|
||||
files = os.listdir(get_frontend_path())
|
||||
# detect the file type and return the appropriate response
|
||||
if extras_file in files:
|
||||
mime = mimetypes.guess_type(extras_file)[0]
|
||||
if mime is not None and mime.startswith("image"):
|
||||
return FileResponse(get_frontend_path() / extras_file)
|
||||
else:
|
||||
with open(get_frontend_path() / extras_file) as file:
|
||||
return str(file.read())
|
||||
raise HTTPException(status_code=400, detail="Invalid file path")
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def catch_root():
|
||||
with open(get_frontend_path() / "build.html") as file:
|
||||
return str(file.read())
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
|
||||
@property
|
||||
|
||||
@@ -14,15 +14,30 @@ def get_config_path() -> pathlib.Path:
|
||||
def get_frontend_path() -> pathlib.Path:
|
||||
if getattr(sys, "frozen", False):
|
||||
# The application is frozen
|
||||
datadir = pathlib.Path(os.path.dirname(sys.executable)) / "example_files"
|
||||
datadir = pathlib.Path(os.path.dirname(sys.executable)) / "frontend"
|
||||
else:
|
||||
# The application is not frozen
|
||||
# Change this bit to match where you store your data files:
|
||||
filedir = os.path.dirname(__file__)
|
||||
datadir = pathlib.Path(filedir).parent.parent.parent / "example_files"
|
||||
datadir = pathlib.Path(filedir).parent.parent.parent / "frontend"
|
||||
return pathlib.Path(datadir)
|
||||
|
||||
|
||||
def get_prisma_path() -> pathlib.Path:
|
||||
return get_data_path() / "prisma"
|
||||
|
||||
|
||||
def get_prisma_exe_path() -> pathlib.Path:
|
||||
if sys.platform == "win32":
|
||||
return (
|
||||
get_prisma_path() / "node_modules" / "prisma" / "query-engine-windows.exe"
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Freezing AutoGPT Server is only supported on Windows at the moment."
|
||||
)
|
||||
|
||||
|
||||
def get_data_path() -> pathlib.Path:
|
||||
if getattr(sys, "frozen", False):
|
||||
# The application is frozen
|
||||
|
||||
74
rnd/autogpt_server/poetry.lock
generated
74
rnd/autogpt_server/poetry.lock
generated
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "agpt"
|
||||
@@ -25,7 +25,7 @@ requests = "*"
|
||||
sentry-sdk = "^1.40.4"
|
||||
|
||||
[package.extras]
|
||||
benchmark = ["agbenchmark @ file:///Users/majdyz/Code/AutoGPT/benchmark"]
|
||||
benchmark = ["agbenchmark"]
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
@@ -329,7 +329,7 @@ watchdog = "4.0.0"
|
||||
webdriver-manager = "^4.0.1"
|
||||
|
||||
[package.extras]
|
||||
benchmark = ["agbenchmark @ file:///Users/majdyz/Code/AutoGPT/benchmark"]
|
||||
benchmark = ["agbenchmark"]
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
@@ -883,33 +883,69 @@ python-dateutil = "*"
|
||||
pytz = ">2021.1"
|
||||
|
||||
[[package]]
|
||||
name = "cx_Freeze"
|
||||
version = "7.1.1"
|
||||
name = "cx-freeze"
|
||||
version = "7.2.0"
|
||||
description = "Create standalone executables from Python scripts"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = []
|
||||
develop = true
|
||||
files = [
|
||||
{file = "cx_Freeze-7.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f843263c0faab92f71a5650f9676806378b0d2cfe5902cb2cefb3dce20a275ac"},
|
||||
{file = "cx_Freeze-7.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37df5c5184bde8a7512e08bb0c6ee72b7d26b7a72fdb71454d63530ea440ab84"},
|
||||
{file = "cx_Freeze-7.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa95b50e3746cb534a53023e3b7393b6b39ab039885302dcaeab4245066656b5"},
|
||||
{file = "cx_Freeze-7.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fcb9fb7016b28a969952761ed2ed730a8001941e29757379cc386f080bbe055"},
|
||||
{file = "cx_Freeze-7.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1c9d67de843ba748f2e52a5cf44f9f943e15ad8ec3bb590f767613b20ac24882"},
|
||||
{file = "cx_Freeze-7.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:11b86b7e5f307e67a76a8f164b9b8b0a3745418b495e6f59abd0fdfeaab206f7"},
|
||||
{file = "cx_Freeze-7.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9fbd82cdd0739daf9009c5ba0110f758f586391a00a624db94f81f3531178ea5"},
|
||||
{file = "cx_Freeze-7.2.0-cp310-cp310-win32.whl", hash = "sha256:ecea5246805c063a1d782e446c811a3c517918770fa04678637dc0f356449489"},
|
||||
{file = "cx_Freeze-7.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:ac48fe678f69b965b10c5f880609e0ec738777c109d45d77e139c76385ac4404"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0d518c75880a517b04a82258d935b9408874d7b27f6d8d20dc4d8818395d02e"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8437c18a129b0cf875f68697a56cd86c69698e6790a5d2c61f8b970bc512128b"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd24c7f0f402c62bed9cfbf6e7dd7d65ef2a011d2576404d9cf2d2c113c1e48"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04a06dc36bfd38088879f5faab7de15c99f3daada524b8a816addb203ec9464"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:620635e86ef46872763be8b65b7c06f5f24943151e80013e3a37f312fe070895"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e84fb083182bcaab226b7a75d2069cefcf3e3181142b09b514d99a5ae4860c8"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dd175db3069991b60a792ee17a2e2e4853bfbcfc0b1be976ebee04ed1098c101"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4030de54f227b5852155d86fc00f86a19e87d0470aa650ec4ddd06b06f270ceb"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:60eba74abdb2211b38263797342e95732ec96621a781b02e5ff909cdc9cf5be5"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-win32.whl", hash = "sha256:d727df584a9b1548dbc478f036382b8cde94471e106d38fb9c45b238bf8af4d1"},
|
||||
{file = "cx_Freeze-7.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:bb6d91342f7379ba76d8a07c263d1fba380f0044b13d3e4629e50cc8dd19b681"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f7cf98f63c85fc2f947a7f68548136d60dfd7927a464d1cd38239a75b730f7f7"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7f988be0ad19be0df357102fae21553f36cc4f83b4e881888e3512d1a2bcb8ba"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:98491338f6dcf52f8ee4d872d08aeae0c91466a47383ad19d1332a565f7c4df8"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51a2f286ff58e04510b6af87c055b6fca36045a0fb785cd966f38aa70f86c3ce"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e6c1ccaf9be9010a0e8a8c33edc7671dd0a6f8c49ab2bcd9f49ce0960089e0"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64c3f734cff3620d5ecc474d59a753cae52ba96b52e0a9ade803a70699be1bc1"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d68f2ec4c0c456c6f1ca5e623f240f27fb63ae65970a16e04689863b03a16c7e"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:77e2362f075b242a99e041afb9a49693a60c102b9b1df67f6387c87fde02e34f"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a51b97024401d5cfaa392caef8b150bf91e62c0fdcb6a68372959c6ff28349d3"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-win32.whl", hash = "sha256:19fb97c8481266931c5fd5cc380b12fb09053258413ebaf6760d5063e5ffde41"},
|
||||
{file = "cx_Freeze-7.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:e61692176a8803e0853ca115cede397942c1cee9e20c8eb5798613d0b92dd75d"},
|
||||
{file = "cx_Freeze-7.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ed93d21a7962cc2886fb6e9e7d53fec67d6da02ff8088c9007a03835adc0240"},
|
||||
{file = "cx_Freeze-7.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b00302fc33f279f7dbca764bdb60247d609e3b424ff31198512c6eeb8cdda17b"},
|
||||
{file = "cx_Freeze-7.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e97745e663450871736f06321fc732b440628cff397f8ddbef05ffbc355b2f5d"},
|
||||
{file = "cx_Freeze-7.2.0-cp38-cp38-win32.whl", hash = "sha256:b7a0c2cced3fa9cf3b0cb4b67064b3ed2db1edec3186d75270ee0c01ed31cf54"},
|
||||
{file = "cx_Freeze-7.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:96046d864e03d4241e869fa5e3ae186be1fbdc170d77125fe36185c7a0108f6f"},
|
||||
{file = "cx_Freeze-7.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:36285f3367f4d220632c572f37140bdacb2020bdf7b166ee707e3e2351338955"},
|
||||
{file = "cx_Freeze-7.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717915dfa25730b57166b25d392c83d41e3f655c8996d473ba0368aec2a9d262"},
|
||||
{file = "cx_Freeze-7.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:afc91a24eccfe14f1853c93e78656772710ed754f7cc5ecb9761b6250a540848"},
|
||||
{file = "cx_Freeze-7.2.0-cp39-cp39-win32.whl", hash = "sha256:166b93473e86b926db7ab26e7d83504e8330df07af44cc090ebcad40e3158417"},
|
||||
{file = "cx_Freeze-7.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4a91284a925df800e0dbb324c51ab4fd7bd0ea708f280d94a2a10cf564c28cbe"},
|
||||
{file = "cx_freeze-7.2.0.tar.gz", hash = "sha256:c57f7101b4d35132464b1ec88cb8948c3b7c5b4ece4bb354c16091589cb33583"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cx_Logging = {version = ">=3.1", markers = "sys_platform == \"win32\""}
|
||||
cx-Logging = {version = ">=3.1", markers = "sys_platform == \"win32\""}
|
||||
dmgbuild = {version = ">=1.6.1", markers = "sys_platform == \"darwin\""}
|
||||
filelock = {version = ">=3.12.3", markers = "sys_platform == \"linux\""}
|
||||
lief = {version = ">=0.12.0,<0.15.0", markers = "sys_platform == \"win32\""}
|
||||
patchelf = {version = ">=0.14", markers = "sys_platform == \"linux\" and (platform_machine == \"x86_64\" or platform_machine == \"i686\" or platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\")"}
|
||||
patchelf = {version = ">=0.14", markers = "sys_platform == \"linux\" and (platform_machine == \"aarch64\" or platform_machine == \"armv7l\" or platform_machine == \"i686\" or platform_machine == \"ppc64le\" or platform_machine == \"s390x\" or platform_machine == \"x86_64\")"}
|
||||
setuptools = ">=65.6.3,<71"
|
||||
wheel = ">=0.42.0,<=0.43.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["bump-my-version (==0.24.0)", "cibuildwheel (==2.19.1)", "pre-commit (==3.5.0)", "pre-commit (==3.7.1)"]
|
||||
doc = ["furo (==2024.5.6)", "myst-parser (==3.0.1)", "sphinx (==7.3.7)", "sphinx-new-tab-link (==0.4.0)", "sphinx-tabs (==3.4.5)"]
|
||||
test = ["coverage (==7.5.4)", "pluggy (==1.5.0)", "pytest (==8.2.2)", "pytest-cov (==5.0.0)", "pytest-datafiles (==3.0.0)", "pytest-mock (==3.14.0)", "pytest-timeout (==2.3.1)", "pytest-xdist[psutil] (==3.6.1)"]
|
||||
|
||||
[package.source]
|
||||
type = "git"
|
||||
url = "https://github.com/ntindle/cx_Freeze.git"
|
||||
reference = "main"
|
||||
resolved_reference = "876fe77c97db749b7b0aed93c12142a7226ee7e4"
|
||||
dev = ["bump-my-version (==0.24.2)", "cibuildwheel (==2.19.2)", "pre-commit (==3.5.0)", "pre-commit (==3.7.1)"]
|
||||
doc = ["furo (==2024.5.6)", "myst-parser (==3.0.1)", "sphinx (==7.3.7)", "sphinx-new-tab-link (==0.5.0)", "sphinx-tabs (==3.4.5)"]
|
||||
test = ["coverage (==7.6.0)", "pluggy (==1.5.0)", "pytest (==8.2.2)", "pytest-cov (==5.0.0)", "pytest-datafiles (==3.0.0)", "pytest-mock (==3.14.0)", "pytest-timeout (==2.3.1)", "pytest-xdist[psutil] (==3.6.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "cx-logging"
|
||||
@@ -6218,4 +6254,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools",
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "ac40cb89830fcc95bec5c0dfb0646503dd9bd3abc26b7258ed69403fd546bed5"
|
||||
content-hash = "d229f380acc15c3685140f45b47c7c94e77efa86394fe8d9d04be0d30f89cdb2"
|
||||
|
||||
@@ -37,11 +37,11 @@ anthropic = "^0.25.1"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
cx-freeze = { git = "https://github.com/ntindle/cx_Freeze.git", rev = "main", develop = true }
|
||||
poethepoet = "^0.26.1"
|
||||
httpx = "^0.27.0"
|
||||
pytest-watcher = "^0.4.2"
|
||||
requests = "^2.32.3"
|
||||
cx-freeze = "^7.2.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
||||
@@ -2,9 +2,13 @@ import platform
|
||||
from pathlib import Path
|
||||
from pkgutil import iter_modules
|
||||
from typing import Union
|
||||
import shutil
|
||||
import os
|
||||
|
||||
from cx_Freeze import Executable, setup # type: ignore
|
||||
|
||||
GUID = "{323a1ee5-317d-4423-8490-effd8f5bedff}"
|
||||
|
||||
packages = [
|
||||
m.name
|
||||
for m in iter_modules()
|
||||
@@ -57,6 +61,22 @@ def txt_to_rtf(input_file: Union[str, Path], output_file: Union[str, Path]) -> N
|
||||
license_file = "LICENSE.rtf"
|
||||
txt_to_rtf("../../LICENSE", license_file)
|
||||
|
||||
# call npm run build in ../autogpt_builder
|
||||
os.system("npm run build --prefix ../autogpt_builder")
|
||||
|
||||
# copy the ../autogpt_builder/out to frontend
|
||||
shutil.rmtree("../frontend", ignore_errors=True)
|
||||
shutil.copytree("../autogpt_builder/out", "../frontend")
|
||||
|
||||
include_files = [ # source, destination in the bundle
|
||||
("../frontend", "frontend"),
|
||||
("./secrets", "secrets"),
|
||||
]
|
||||
|
||||
# add the prisma directory if it exists
|
||||
if os.path.exists("./prisma"):
|
||||
include_files.append(("./prisma", "prisma"))
|
||||
|
||||
setup(
|
||||
name="AutoGPT Server",
|
||||
url="https://agpt.co",
|
||||
@@ -85,11 +105,13 @@ setup(
|
||||
],
|
||||
# Exclude the two module from readability.compat as it causes issues
|
||||
"excludes": ["readability.compat.two"],
|
||||
"include_files": [
|
||||
# source, destination in the bundle
|
||||
# (../frontend, example_files) would also work but you'd need to load the frontend differently in the data.py to correctly get the path when frozen
|
||||
("../example_files", "example_files"),
|
||||
],
|
||||
"include_files": include_files,
|
||||
# "replace_paths": [
|
||||
# (
|
||||
# "D:\a\\AutoGPT\\AutoGPT\rnd\autogpt_server\\",
|
||||
# "%AppData%\\..\\Local\\Programs\\AutoGPTServer\\",
|
||||
# ),
|
||||
# ],
|
||||
},
|
||||
# Mac .app specific options
|
||||
"bdist_mac": {
|
||||
@@ -101,7 +123,6 @@ setup(
|
||||
"applications_shortcut": True,
|
||||
"volume_label": "AutoGPTServer",
|
||||
"background": "builtin-arrow",
|
||||
|
||||
"license": {
|
||||
"default-language": "en_US",
|
||||
"licenses": {"en_US": license_file},
|
||||
@@ -123,6 +144,7 @@ setup(
|
||||
"add_to_path": True,
|
||||
"install_icon": "../../assets/gpt_dark_RGB.ico",
|
||||
"license_file": license_file,
|
||||
"upgrade_code": GUID,
|
||||
},
|
||||
# Linux .appimage specific options
|
||||
"bdist_appimage": {},
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Example Files</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Example Files</h1>
|
||||
<ul>
|
||||
<li><a href="example1.txt">Example 1</a></li>
|
||||
<li><a href="example2.txt">Example 2</a></li>
|
||||
<li><a href="example3.txt">Example 3</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
rnd/frontend/.gitkeep
Normal file
1
rnd/frontend/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Copy your frontend builds here manually if you'd like to test the serving capabilities of the fast api server\
|
||||
Reference in New Issue
Block a user