mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-20 04:28:09 -05:00
Compare commits
17 Commits
make-old-w
...
toran/open
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a42ef76345 | ||
|
|
7e6c808ed6 | ||
|
|
5e3ecf547c | ||
|
|
af534c6441 | ||
|
|
ad1699eccf | ||
|
|
3e1f59814d | ||
|
|
4256ffcc78 | ||
|
|
8e201b5dc6 | ||
|
|
2bb37cc9c4 | ||
|
|
c16b8763e7 | ||
|
|
7d9976689e | ||
|
|
dda3402c1d | ||
|
|
82cfafc057 | ||
|
|
e7be6aec5a | ||
|
|
8f0f727d48 | ||
|
|
f50307b607 | ||
|
|
401c92fe75 |
@@ -1 +1,2 @@
|
||||
AGPT_SERVER_URL=http://localhost:8000/api
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=
|
||||
@@ -19,6 +19,7 @@
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@react-oauth/google": "^0.12.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
import { GoogleOAuthProvider } from '@react-oauth/google';
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -67,14 +69,16 @@ export default function RootLayout({
|
||||
defaultTheme="light"
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<div className="min-h-screen bg-gray-200 text-gray-900">
|
||||
<NavBar />
|
||||
<main className="mx-auto p-4">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
<GoogleOAuthProvider clientId={process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!}>
|
||||
<div className="min-h-screen bg-gray-200 text-gray-900">
|
||||
<NavBar />
|
||||
<main className="mx-auto p-4">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</GoogleOAuthProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, FC, memo } from 'react';
|
||||
import React, { useState, useEffect, FC, memo, useCallback } from 'react';
|
||||
import { Handle, Position, NodeProps } from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
import './customnode.css';
|
||||
@@ -8,6 +8,8 @@ import { Input } from './ui/input';
|
||||
import { BlockSchema } from '@/lib/types';
|
||||
import SchemaTooltip from './SchemaTooltip';
|
||||
import { beautifyString } from '@/lib/utils';
|
||||
import GoogleSignInButton from './GoogleSignInButton';
|
||||
import { GoogleOAuthProvider } from '@react-oauth/google';
|
||||
|
||||
type CustomNodeData = {
|
||||
blockType: string;
|
||||
@@ -32,6 +34,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
const [activeKey, setActiveKey] = useState<string | null>(null);
|
||||
const [modalValue, setModalValue] = useState<string>('');
|
||||
const [errors, setErrors] = useState<{ [key: string]: string | null }>({});
|
||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (data.output_data || data.status) {
|
||||
@@ -88,6 +91,15 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
));
|
||||
};
|
||||
|
||||
const handleTokenChange = useCallback((token: string | null) => {
|
||||
if (token !== data.hardcodedValues['access_token']) {
|
||||
data.setHardcodedValues({
|
||||
...data.hardcodedValues,
|
||||
access_token: token
|
||||
});
|
||||
}
|
||||
}, [data.hardcodedValues, data.setHardcodedValues]);
|
||||
|
||||
const handleInputChange = (key: string, value: any) => {
|
||||
const keys = key.split('.');
|
||||
const newValues = JSON.parse(JSON.stringify(data.hardcodedValues));
|
||||
@@ -386,6 +398,13 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
);
|
||||
}
|
||||
return null;
|
||||
case 'oauth2':
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
<GoogleSignInButton onTokenChange={handleTokenChange} />
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
@@ -425,30 +444,35 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
</div>
|
||||
<div className="node-content">
|
||||
<div className="input-section">
|
||||
{data.inputSchema &&
|
||||
Object.entries(data.inputSchema.properties).map(([key, schema]) => {
|
||||
const isRequired = data.inputSchema.required?.includes(key);
|
||||
return (isRequired || isAdvancedOpen) && (
|
||||
<div key={key}>
|
||||
<div className="handle-container">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={key}
|
||||
style={{ background: '#555', borderRadius: '50%', width: '10px', height: '10px' }}
|
||||
/>
|
||||
<span className="handle-label">{schema.title || beautifyString(key)}</span>
|
||||
<SchemaTooltip schema={schema} />
|
||||
{data.inputSchema &&
|
||||
Object.entries(data.inputSchema.properties).map(([key, schema]) => {
|
||||
const isRequired = data.inputSchema.required?.includes(key);
|
||||
return (isRequired || isAdvancedOpen) && (
|
||||
<div key={key}>
|
||||
<div className="handle-container">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={key}
|
||||
style={{ background: '#555', borderRadius: '50%', width: '10px', height: '10px' }}
|
||||
/>
|
||||
<span className="handle-label">{schema.title || beautifyString(key)}</span>
|
||||
<SchemaTooltip schema={schema} />
|
||||
</div>
|
||||
{renderInputField(key, schema, '', schema.title || beautifyString(key))}
|
||||
</div>
|
||||
{renderInputField(key, schema, '', schema.title || beautifyString(key))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="output-section">
|
||||
{data.outputSchema && generateHandles(data.outputSchema, 'source')}
|
||||
{data.outputSchema && generateHandles(data.outputSchema, 'source')}
|
||||
</div>
|
||||
</div>
|
||||
{data.blockType.includes('Google') && (
|
||||
<div className="google-signin-container">
|
||||
<GoogleSignInButton onTokenChange={handleTokenChange} />
|
||||
</div>
|
||||
)}
|
||||
{isOutputOpen && (
|
||||
<div className="node-output">
|
||||
<p>
|
||||
|
||||
33
rnd/autogpt_builder/src/components/GoogleSignInButton.tsx
Normal file
33
rnd/autogpt_builder/src/components/GoogleSignInButton.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { Button } from './ui/button';
|
||||
import { useGoogleAuth } from '@/hooks/useGoogleAuth';
|
||||
|
||||
interface GoogleSignInButtonProps {
|
||||
onTokenChange: (token: string | null) => void;
|
||||
}
|
||||
|
||||
const GoogleSignInButton: React.FC<GoogleSignInButtonProps> = React.memo(({ onTokenChange }) => {
|
||||
const { token, error, login, logout } = useGoogleAuth('https://www.googleapis.com/auth/spreadsheets');
|
||||
|
||||
const handleTokenChange = useCallback((newToken: string | null) => {
|
||||
onTokenChange(newToken);
|
||||
}, [onTokenChange]);
|
||||
|
||||
useEffect(() => {
|
||||
handleTokenChange(token);
|
||||
}, [token, handleTokenChange]);
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error}</div>;
|
||||
}
|
||||
|
||||
return token ? (
|
||||
<Button onClick={logout}>Sign Out of Google Sheets</Button>
|
||||
) : (
|
||||
<Button onClick={() => login()}>Sign in with Google Sheets</Button>
|
||||
);
|
||||
});
|
||||
|
||||
GoogleSignInButton.displayName = 'GoogleSignInButton';
|
||||
|
||||
export default GoogleSignInButton;
|
||||
@@ -146,6 +146,11 @@
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.custom-node .google-signin-container {
|
||||
margin-top: 10px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.array-item-add {
|
||||
background: #5bc0de;
|
||||
border: none;
|
||||
|
||||
24
rnd/autogpt_builder/src/hooks/useGoogleAuth.ts
Normal file
24
rnd/autogpt_builder/src/hooks/useGoogleAuth.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useGoogleLogin } from '@react-oauth/google';
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
export const useGoogleAuth = (scope: string) => {
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const login = useGoogleLogin({
|
||||
onSuccess: (tokenResponse) => {
|
||||
setToken(tokenResponse.access_token);
|
||||
setError(null);
|
||||
},
|
||||
onError: (errorResponse) => {
|
||||
setError(errorResponse.error_description || 'An error occurred during login');
|
||||
},
|
||||
scope: scope,
|
||||
});
|
||||
|
||||
const logout = useCallback(() => {
|
||||
setToken(null);
|
||||
}, []);
|
||||
|
||||
return { token, error, login, logout };
|
||||
};
|
||||
170
rnd/autogpt_server/autogpt_server/blocks/google-sheets-block.py
Normal file
170
rnd/autogpt_server/autogpt_server/blocks/google-sheets-block.py
Normal file
@@ -0,0 +1,170 @@
|
||||
import re
|
||||
from typing import List, Union
|
||||
|
||||
from google.oauth2.credentials import Credentials
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
from autogpt_server.data.block import Block, BlockOutput, BlockSchema
|
||||
from autogpt_server.data.model import SchemaField
|
||||
|
||||
|
||||
def extract_spreadsheet_id(url: str) -> str:
|
||||
"""Extract the spreadsheet ID from a Google Sheets URL."""
|
||||
match = re.search(
|
||||
r"/d/([a-zA-Z0-9-_]+)", url
|
||||
) # This works for clean urls and those with extra parameters at the end
|
||||
if match:
|
||||
return match.group(1)
|
||||
raise ValueError("Invalid Google Sheets URL")
|
||||
|
||||
def get_google_sheets_service(access_token: str):
|
||||
"""Create and return a Google Sheets service object."""
|
||||
credentials = Credentials(access_token)
|
||||
return build("sheets", "v4", credentials=credentials)
|
||||
|
||||
def get_spreadsheet_and_id(spreadsheet_url: str, access_token: str):
|
||||
"""Extract spreadsheet ID and create a Google Sheets service object."""
|
||||
spreadsheet_id = extract_spreadsheet_id(spreadsheet_url)
|
||||
service = get_google_sheets_service(access_token)
|
||||
return service.spreadsheets(), spreadsheet_id
|
||||
|
||||
class GoogleSheetsWriter(Block):
|
||||
class Input(BlockSchema):
|
||||
spreadsheet_url: str = SchemaField(
|
||||
description="The link to the Google Sheet to write to.",
|
||||
placeholder="https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
|
||||
)
|
||||
sheet_name: str = SchemaField(
|
||||
description="The name of the sheet to append data to",
|
||||
placeholder="Sheet1",
|
||||
default="Sheet1",
|
||||
)
|
||||
row_data: list = SchemaField(
|
||||
description="The data to append as a single row",
|
||||
placeholder="['John Doe', 'johndoe@example.com', '30']",
|
||||
)
|
||||
access_token: str = SchemaField(
|
||||
description="Google OAuth2 access token. Click 'Sign in with Google' below to automatically generate this token. Keep this secret!",
|
||||
placeholder="Sign in with Google below",
|
||||
secret=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
result: dict = SchemaField(description="The result of the append operation")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="919e684b-1c2a-4a9a-ac8b-d073de3c15b2",
|
||||
input_schema=GoogleSheetsWriter.Input,
|
||||
output_schema=GoogleSheetsWriter.Output,
|
||||
)
|
||||
|
||||
def run(self, input_data: Input) -> BlockOutput:
|
||||
try:
|
||||
sheet, spreadsheet_id = get_spreadsheet_and_id(input_data.spreadsheet_url, input_data.access_token)
|
||||
|
||||
result = (
|
||||
sheet.values()
|
||||
.get(spreadsheetId=spreadsheet_id, range=f"{input_data.sheet_name}!A:A")
|
||||
.execute()
|
||||
)
|
||||
values = result.get("values", [])
|
||||
next_row = len(values) + 1
|
||||
|
||||
# Prepare the range for appending
|
||||
append_range = f"{input_data.sheet_name}!A{next_row}"
|
||||
|
||||
body = {"values": [input_data.row_data]}
|
||||
|
||||
result = (
|
||||
sheet.values()
|
||||
.append(
|
||||
spreadsheetId=spreadsheet_id,
|
||||
range=append_range,
|
||||
valueInputOption="USER_ENTERED",
|
||||
insertDataOption="INSERT_ROWS",
|
||||
body=body,
|
||||
)
|
||||
.execute()
|
||||
)
|
||||
|
||||
yield "result", {
|
||||
"success": True,
|
||||
"updated_range": result.get("updates", {}).get("updatedRange"),
|
||||
"updated_rows": result.get("updates", {}).get("updatedRows"),
|
||||
"updated_cells": result.get("updates", {}).get("updatedCells"),
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
yield "result", {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
class GoogleSheetsReader(Block):
|
||||
class Input(BlockSchema):
|
||||
spreadsheet_url: str = SchemaField(
|
||||
description="The link to the Google Sheet to read from.",
|
||||
placeholder="https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
|
||||
)
|
||||
read_single_row: bool = SchemaField(
|
||||
description="Do you want to read just one row? Select 'True' for a single row, 'False' for a range.",
|
||||
default=False,
|
||||
)
|
||||
sheet_name: str = SchemaField(
|
||||
description="The name of the sheet you want to read from (e.g., 'Sheet1')",
|
||||
placeholder="Sheet1",
|
||||
default="Sheet1",
|
||||
)
|
||||
row_number: int = SchemaField(
|
||||
description="If reading a single row, which row number do you want? (e.g., 3 for the third row)",
|
||||
placeholder="3",
|
||||
default=1,
|
||||
)
|
||||
cell_range: str = SchemaField(
|
||||
description="If reading a range, what range of cells do you want? (e.g., 'A1:E10' for the first 10 rows of columns A to E)",
|
||||
placeholder="A1:E10",
|
||||
default="A1:Z1000",
|
||||
)
|
||||
access_token: str = SchemaField(
|
||||
placeholder="Sign in with Google below",
|
||||
description="Google OAuth2 access token. Click 'Sign in with Google' below to automatically generate this token. Keep this secret!",
|
||||
secret=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
data: Union[List[str], List[List[str]]] = SchemaField(
|
||||
description="The information read from your Google Sheet. For a single row, it's a simple list. For multiple rows, it's a list of lists."
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="460e5d53-7038-4a8c-9f75-ec0b593fb337",
|
||||
input_schema=GoogleSheetsReader.Input,
|
||||
output_schema=GoogleSheetsReader.Output,
|
||||
)
|
||||
|
||||
def run(self, input_data: Input) -> BlockOutput:
|
||||
try:
|
||||
sheet, spreadsheet_id = get_spreadsheet_and_id(input_data.spreadsheet_url, input_data.access_token)
|
||||
|
||||
if input_data.read_single_row:
|
||||
range_to_read = f"{input_data.sheet_name}!A{input_data.row_number}:ZZ{input_data.row_number}"
|
||||
else:
|
||||
range_to_read = f"{input_data.sheet_name}!{input_data.cell_range}"
|
||||
|
||||
result = sheet.values().get(spreadsheetId=spreadsheet_id, range=range_to_read).execute()
|
||||
values = result.get("values", [])
|
||||
|
||||
if not values:
|
||||
yield "data", {
|
||||
"message": "No data found in the specified range or row."
|
||||
}
|
||||
else:
|
||||
if input_data.read_single_row:
|
||||
# Return a single list for a single row
|
||||
yield "data", values[0] if values else []
|
||||
else:
|
||||
# Return a list of lists for multiple rows
|
||||
yield "data", values
|
||||
|
||||
except Exception as e:
|
||||
yield "data", {"error": f"Oops! Something went wrong: {str(e)}"}
|
||||
Reference in New Issue
Block a user