mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
feat(block): Add Google Sheets block
Update customnode.css and layout.tsx for Google Sign-In integration This commit adds styles to customnode.css to accommodate the Google Sign-In button. It also updates layout.tsx to wrap the entire application in the GoogleOAuthProvider component, enabling Google Sign-In functionality.
This commit is contained in:
@@ -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,33 @@ 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>
|
||||
<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.readonly');
|
||||
|
||||
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 };
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
from autogpt_server.data.block import Block, BlockSchema, BlockOutput
|
||||
from autogpt_server.data.model import SchemaField
|
||||
from google.oauth2.credentials import Credentials
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
class GoogleSheetsBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
spreadsheet_id: str = SchemaField(
|
||||
description="The ID of the Google Sheet to read",
|
||||
placeholder="1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
|
||||
)
|
||||
range: str = SchemaField(
|
||||
description="The A1 notation of the range to read",
|
||||
placeholder="Sheet1!A1:E10",
|
||||
default= "Sheet1!A1:Z1000"
|
||||
)
|
||||
access_token: str = SchemaField(
|
||||
description="Google OAuth2 access token",
|
||||
secret=True
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
rows: list = SchemaField(description="The rows of from the specified Google Sheet range")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="google-sheets-block",
|
||||
input_schema=GoogleSheetsBlock.Input,
|
||||
output_schema=GoogleSheetsBlock.Output,
|
||||
)
|
||||
|
||||
def run(self, input_data: Input) -> BlockOutput:
|
||||
credentials = Credentials(input_data.access_token)
|
||||
service = build("sheets", "v4", credentials=credentials)
|
||||
sheet = service.spreadsheets()
|
||||
result = sheet.values().get(spreadsheetId=input_data.spreadsheet_id, range=input_data.range).execute()
|
||||
values = result.get("values", [])
|
||||
|
||||
if not values:
|
||||
yield "data", {"error": "No data found."}
|
||||
else:
|
||||
headers = values[0]
|
||||
data = [dict(zip(headers, row)) for row in values[1:]]
|
||||
yield "rows", data
|
||||
Reference in New Issue
Block a user