diff --git a/docs/docs/Integrations/Google/_category_.json b/docs/docs/Integrations/Google/_category_.json new file mode 100644 index 000000000..8fd84656c --- /dev/null +++ b/docs/docs/Integrations/Google/_category_.json @@ -0,0 +1 @@ +{ "position": 2, "label": "Google" } diff --git a/docs/docs/Integrations/Google/integrations-setup-google-oauth-langflow.md b/docs/docs/Integrations/Google/integrations-setup-google-oauth-langflow.md new file mode 100644 index 000000000..c1d534b12 --- /dev/null +++ b/docs/docs/Integrations/Google/integrations-setup-google-oauth-langflow.md @@ -0,0 +1,165 @@ +--- +title: "Setup Google OAuth for Langflow Integration" +slug: /integrations-setup-google-oauth-langflow +sidebar_position: 3 +description: "A comprehensive guide on creating a Google OAuth app, obtaining tokens, and integrating them with Langflow's Google components." +--- + +import TOCInline from '@theme/TOCInline'; + +# Setting Up Google OAuth for Langflow + +Quickly set up Google OAuth to integrate Google Gmail and Drive with Langflow. To do this, create an OAuth app in Google Cloud, obtain the necessary credentials and access tokens, and add them to Langflow’s Google components. + +# Overview + +Langflow supports OAuth for seamless integration with Google services. Just follow the setup steps to configure OAuth credentials, retrieve tokens, and connect your Google services to Langflow. + +--- + +## Step 1: Creating an OAuth Application in Google Cloud {#5b8981b15d86192d17b0e5725c1f95e7} + +1. **Access Google Cloud Console** + + - Go to the [Google Cloud Console](https://console.cloud.google.com/). + +2. **Create or Select a Project** + + - Click **Select a project** at the top of the page and choose an existing project or create a new one. + +![OAuth Client ID and Secret](/img/google/create-a-google-cloud-project.gif) + +3. **Enable APIs for the Project** + + - Go to **APIs & Services > Library** and enable the APIs you need (e.g., Google Drive API, Google Gmail API). + +4. **Navigate to OAuth consent screen** + - Go to **APIs & Services >** and click on **OAuth consent screen**. +5. **Set Up OAuth Consent Screen** + + - On the OAuth consent screen, set up essential app details like the application name, user support email, required [scopes](https://developers.google.com/identity/protocols/oauth2/scopes) (permissions your app needs), and authorized domains. + - Ensure you **publish** the app if it’s not restricted to internal use. + +![OAuth Consent Screen](/img/google/setup-oauth-consent-screen.png) + +:::info + +- Configuring the OAuth consent screen is crucial for obtaining user permissions. + +:::: + +6. **Create OAuth Client ID** + + - Go back to **Credentials**, select **Create Credentials > OAuth Client ID**. + - Choose **Desktop app** as the application type. + +7. **Save OAuth Client ID and Client Secret** + + - After creating, you'll receive a Client ID and Client Secret. You can either view them directly or download them as a JSON file. Please download and save this information securely, as it's essential for using Langflow. + +![OAuth Client ID and Secret](/img/google/create-oauth-client-id.png) + +--- + +## Step 2: Retrieving Access and Refresh Tokens + +With your OAuth application configured and with your Client ID created, follow these steps to obtain the tokens: + +1. **Authenticate the Application** + + - Create a new project in Langflow. + - Add a Google OAuth Token component. + - Input in the field **Credentials File** on the Google OAuth Token component, the JSON file containing the Client ID credentials you downloaded from Google in the [previous steps](#5b8981b15d86192d17b0e5725c1f95e7). + - Run the Google OAuth Token component to authenticate your application. + +:::info + +- Note that when the component is executed, a new tab maybe open in the browser so that you can authenticate using your Google Cloud account where you created the project containing the OAuth Application, the credentials and activated the API that you need to use. +- If a new tab does not open automatically, check the Langflow **Logs** for the Google authentication URL. Open this URL in your browser to complete the authentication. Only after authenticating will the JSON token be generated. + + ::: + +2. **Refresh Tokens** + + - After successful authentication, your Langflow application can request and refresh tokens for your app. These tokens will enable Langflow to interact with Google services on your behalf and execute the requests you’ve specified. + - By default, token validity is managed by Google’s servers. In Langflow, tokens refresh automatically after initial authentication. However, if your application is inactive for an extended period, the tokens may expire, requiring you to re-authenticate to resume use in Langflow. + +--- + +## Step 3: Configuring Google Components in Langflow + +In this example, we will use the Google Drive Loader component to load a text file hosted on Google Drive, translate the text in it to Spanish, and return it to a chat output. + +1. **Open Langflow and Add Google Drive Loader Component** + + - In Langflow, go to your flow editor and add a **Google Drive Loader** component. + +2. **Enter OAuth Credentials** + + - In the `JSON String of the Service Account Token` or `Token String` field of the Google Drive Loader component, enter your JSON string containing the token returned in the output of the Google OAuth Token component. Remember to convert the data output from the Google OAuth Token component to text using the `Parse Data` component. + +3. **Getting File ID from Google Drive** + + Steps to Obtain the Google Drive File ID from a URL: + + 1. **Copy the Google Drive URL:** + + - Open the document in Google Drive and copy the link from the address bar. + + 2. **Identify the Document ID:** + + - The file ID is located between `/d/` and `/edit` in the URL. Example: + + ``` + https://drive.google.com/file/d/1a2b3c4D5E6F7gHI8J9klmnopQ/edit + ``` + + Here, the ID is `1a2b3c4D5E6F7gHI8J9klmnopQ`. + + 3. **Enter the ID in the Component:** + + - In Langflow, paste the copied ID to field Document ID in Google Drive Loader component to allow the component to access the file. + +4. **Test the Connection** + + - After adding credentials and Document ID, test the component’s functionality within your flow to ensure a successful connection. + +--- + +## Step 4: Using Google Components in Your Flow + +With OAuth successfully configured, you can now use Google components in Langflow to automate tasks: + +- **Gmail Loader** + Loads emails from Gmail using the provided credentials. +- **Google Drive Loader** + Loads documents from Google Drive using provided credentials. +- **Google Drive Search** + Searches Google Drive files using provided credentials and query parameters. + +Each component will utilize your OAuth tokens to perform these actions seamlessly. + +## Flow Example + +You can use the Flow below as a starting example for your tests. + +- Flow Google Drive Docs Translations Example - + (Download link) + +--- + +## Troubleshooting Common Issues + +- **Token Expiration**: Ensure to refresh your tokens periodically if you encounter authentication errors. +- **Permission Errors**: Double-check your OAuth consent settings and scopes in your Google Cloud account as well as in your Langflow component settings to ensure you’ve granted the necessary permissions. +- **A new window for authentication did not open?**: Don't worry, you can check the Langflow Logs and look for the following text below. + + Example: + + ``` + Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=156549873216-fa86b6a74ff8ee9a69d2b98e0bc478e8.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A54899%2F&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.readonly&state=75gxTJWwpUZjSWeyWDL81BmJAzGt1Q&access_type=offline + ``` + +--- + +By following these steps, your Langflow environment will be fully integrated with Google services, providing a powerful tool for automating workflows that involve Google Gmail, Drive, and more. diff --git a/docs/static/files/Google_Drive_Docs_Translations_Example.json b/docs/static/files/Google_Drive_Docs_Translations_Example.json new file mode 100644 index 000000000..21addff74 --- /dev/null +++ b/docs/static/files/Google_Drive_Docs_Translations_Example.json @@ -0,0 +1 @@ +{"id":"d0ff7355-fae7-411d-a7a4-16f82120fdfe","data":{"nodes":[{"id":"ParseData-U2bvS","type":"genericNode","position":{"x":484.9402084930625,"y":225.83768098401197},"data":{"type":"ParseData","node":{"template":{"_type":"Component","data":{"trace_as_metadata":true,"list":false,"trace_as_input":true,"required":false,"placeholder":"","show":true,"name":"data","value":"","display_name":"Data","advanced":false,"input_types":["Data"],"dynamic":false,"info":"The data to convert to text.","title_case":false,"type":"other","_input_type":"DataInput"},"code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Parse Data\"\n description = \"Convert Data into plain text following a specified template.\"\n icon = \"braces\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\"),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(display_name=\"Text\", name=\"text\", method=\"parse_data\"),\n ]\n\n def parse_data(self) -> Message:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n\n result_string = data_to_text(template, data, sep=self.sep)\n self.status = result_string\n return Message(text=result_string)\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"sep":{"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"sep","value":"\n","display_name":"Separator","advanced":true,"dynamic":false,"info":"","title_case":false,"type":"str","_input_type":"StrInput"},"template":{"trace_as_input":true,"multiline":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"template","value":"{text}","display_name":"Template","advanced":false,"input_types":["Message"],"dynamic":false,"info":"The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.","title_case":false,"type":"str","_input_type":"MultilineInput"}},"description":"Convert Data into plain text following a specified template.","icon":"braces","base_classes":["Message"],"display_name":"Parse Data","documentation":"","custom_fields":{},"output_types":[],"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Message"],"selected":"Message","name":"text","display_name":"Text","method":"parse_data","value":"__UNDEFINED__","cache":true}],"field_order":["data","template","sep"],"beta":false,"edited":false,"metadata":{},"lf_version":"1.0.19.post1"},"id":"ParseData-U2bvS"},"selected":false,"width":384,"height":353,"positionAbsolute":{"x":484.9402084930625,"y":225.83768098401197},"dragging":false},{"id":"GoogleDriveComponent-yTOyB","type":"genericNode","position":{"x":893.2669087441287,"y":217.75489044938584},"data":{"type":"GoogleDriveComponent","node":{"template":{"_type":"Component","code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"import json\nfrom json.decoder import JSONDecodeError\n\nfrom google.auth.exceptions import RefreshError\nfrom google.oauth2.credentials import Credentials\nfrom langchain_google_community import GoogleDriveLoader\n\nfrom langflow.custom import Component\nfrom langflow.helpers.data import docs_to_data\nfrom langflow.inputs import MessageTextInput\nfrom langflow.io import SecretStrInput\nfrom langflow.schema import Data\nfrom langflow.template import Output\n\n\nclass GoogleDriveComponent(Component):\n display_name = \"Google Drive Loader\"\n description = \"Loads documents from Google Drive using provided credentials.\"\n icon = \"Google\"\n\n inputs = [\n SecretStrInput(\n name=\"json_string\",\n display_name=\"JSON String of the Service Account Token\",\n info=\"JSON string containing OAuth 2.0 access token information for service account access\",\n required=True,\n ),\n MessageTextInput(\n name=\"document_id\", display_name=\"Document ID\", info=\"Single Google Drive document ID\", required=True\n ),\n ]\n\n outputs = [\n Output(display_name=\"Loaded Documents\", name=\"docs\", method=\"load_documents\"),\n ]\n\n def load_documents(self) -> Data:\n class CustomGoogleDriveLoader(GoogleDriveLoader):\n creds: Credentials | None = None\n \"\"\"Credentials object to be passed directly.\"\"\"\n\n def _load_credentials(self):\n \"\"\"Load credentials from the provided creds attribute or fallback to the original method.\"\"\"\n if self.creds:\n return self.creds\n msg = \"No credentials provided.\"\n raise ValueError(msg)\n\n class Config:\n arbitrary_types_allowed = True\n\n json_string = self.json_string\n\n document_ids = [self.document_id]\n if len(document_ids) != 1:\n msg = \"Expected a single document ID\"\n raise ValueError(msg)\n\n # TODO: Add validation to check if the document ID is valid\n\n # Load the token information from the JSON string\n try:\n token_info = json.loads(json_string)\n except JSONDecodeError as e:\n msg = \"Invalid JSON string\"\n raise ValueError(msg) from e\n\n # Initialize the custom loader with the provided credentials and document IDs\n loader = CustomGoogleDriveLoader(\n creds=Credentials.from_authorized_user_info(token_info), document_ids=document_ids\n )\n\n # Load the documents\n try:\n docs = loader.load()\n # catch google.auth.exceptions.RefreshError\n except RefreshError as e:\n msg = \"Authentication error: Unable to refresh authentication token. Please try to reauthenticate.\"\n raise ValueError(msg) from e\n except Exception as e:\n msg = f\"Error loading documents: {e}\"\n raise ValueError(msg) from e\n\n assert len(docs) == 1, \"Expected a single document to be loaded.\"\n\n data = docs_to_data(docs)\n # Return the loaded documents\n self.status = data\n return Data(data={\"text\": data})\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"document_id":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":true,"placeholder":"","show":true,"name":"document_id","value":"YOUR-DOCUMENT-ID-HERE","display_name":"Document ID","advanced":false,"input_types":["Message"],"dynamic":false,"info":"Single Google Drive document ID","title_case":false,"type":"str","_input_type":"MessageTextInput"},"json_string":{"load_from_db":false,"required":true,"placeholder":"","show":true,"name":"json_string","value":"","display_name":"JSON String of the Service Account Token","advanced":false,"input_types":["Message"],"dynamic":false,"info":"JSON string containing OAuth 2.0 access token information for service account access","title_case":false,"password":true,"type":"str","_input_type":"SecretStrInput"}},"description":"Loads documents from Google Drive using provided credentials.","icon":"Google","base_classes":["Data"],"display_name":"Google Drive Loader","documentation":"","custom_fields":{},"output_types":[],"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Data"],"selected":"Data","name":"docs","display_name":"Loaded Documents","method":"load_documents","value":"__UNDEFINED__","cache":true}],"field_order":["json_string","document_id"],"beta":false,"edited":false,"metadata":{},"lf_version":"1.0.19.post1"},"id":"GoogleDriveComponent-yTOyB"},"selected":false,"width":384,"height":389,"positionAbsolute":{"x":893.2669087441287,"y":217.75489044938584},"dragging":false},{"id":"ParseData-Fo5CI","type":"genericNode","position":{"x":1297.9704666205964,"y":232.42701869317483},"data":{"type":"ParseData","node":{"template":{"_type":"Component","data":{"trace_as_metadata":true,"list":false,"trace_as_input":true,"required":false,"placeholder":"","show":true,"name":"data","value":"","display_name":"Data","advanced":false,"input_types":["Data"],"dynamic":false,"info":"The data to convert to text.","title_case":false,"type":"other","_input_type":"DataInput"},"code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Parse Data\"\n description = \"Convert Data into plain text following a specified template.\"\n icon = \"braces\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\"),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(display_name=\"Text\", name=\"text\", method=\"parse_data\"),\n ]\n\n def parse_data(self) -> Message:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n\n result_string = data_to_text(template, data, sep=self.sep)\n self.status = result_string\n return Message(text=result_string)\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"sep":{"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"sep","value":"\n","display_name":"Separator","advanced":true,"dynamic":false,"info":"","title_case":false,"type":"str","_input_type":"StrInput"},"template":{"trace_as_input":true,"multiline":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"template","value":"{text}","display_name":"Template","advanced":false,"input_types":["Message"],"dynamic":false,"info":"The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.","title_case":false,"type":"str","_input_type":"MultilineInput"}},"description":"Convert Data into plain text following a specified template.","icon":"braces","base_classes":["Message"],"display_name":"Parse Data","documentation":"","custom_fields":{},"output_types":[],"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Message"],"selected":"Message","name":"text","display_name":"Text","method":"parse_data","value":"__UNDEFINED__","cache":true}],"field_order":["data","template","sep"],"beta":false,"edited":false,"metadata":{},"lf_version":"1.0.19.post1"},"id":"ParseData-Fo5CI"},"selected":false,"width":384,"height":353,"positionAbsolute":{"x":1297.9704666205964,"y":232.42701869317483},"dragging":false},{"id":"OpenAIModel-oE0wj","type":"genericNode","position":{"x":2133.8172349265606,"y":182.2210159995695},"data":{"type":"OpenAIModel","node":{"template":{"_type":"Component","output_parser":{"trace_as_metadata":true,"list":false,"required":false,"placeholder":"","show":true,"name":"output_parser","value":"","display_name":"Output Parser","advanced":true,"input_types":["OutputParser"],"dynamic":false,"info":"The parser to use to parse the output of the model","title_case":false,"type":"other","_input_type":"HandleInput"},"api_key":{"load_from_db":false,"required":false,"placeholder":"","show":true,"name":"api_key","value":"","display_name":"OpenAI API Key","advanced":false,"input_types":["Message"],"dynamic":false,"info":"The OpenAI API Key to use for the OpenAI model.","title_case":false,"password":true,"type":"str","_input_type":"SecretStrInput"},"code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"import operator\nfrom functools import reduce\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import OPENAI_MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.inputs.inputs import HandleInput\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n name = \"OpenAIModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n range_spec=RangeSpec(min=0, max=128000),\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. \"\n \"You must pass the word JSON in the prompt. \"\n \"If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[0],\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. \"\n \"Defaults to https://api.openai.com/v1. \"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n HandleInput(\n name=\"output_parser\",\n display_name=\"Output Parser\",\n info=\"The parser to use to parse the output of the model\",\n advanced=True,\n input_types=[\"OutputParser\"],\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n # self.output_schema is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict) or self.json_mode\n seed = self.seed\n\n api_key = SecretStr(openai_api_key) if openai_api_key else None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature if temperature is not None else 0.1,\n seed=seed,\n )\n if json_mode:\n if output_schema_dict:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\")\n else:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return None\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\")\n if message:\n return message\n return None\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"input_value":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"input_value","value":"","display_name":"Input","advanced":false,"input_types":["Message"],"dynamic":false,"info":"","title_case":false,"type":"str","_input_type":"MessageInput"},"json_mode":{"trace_as_metadata":true,"list":false,"required":false,"placeholder":"","show":true,"name":"json_mode","value":false,"display_name":"JSON Mode","advanced":true,"dynamic":false,"info":"If True, it will output JSON regardless of passing a schema.","title_case":false,"type":"bool","_input_type":"BoolInput"},"max_tokens":{"trace_as_metadata":true,"range_spec":{"step_type":"float","min":0,"max":128000,"step":0.1},"list":false,"required":false,"placeholder":"","show":true,"name":"max_tokens","value":"","display_name":"Max Tokens","advanced":true,"dynamic":false,"info":"The maximum number of tokens to generate. Set to 0 for unlimited tokens.","title_case":false,"type":"int","_input_type":"IntInput"},"model_kwargs":{"trace_as_input":true,"list":false,"required":false,"placeholder":"","show":true,"name":"model_kwargs","value":{},"display_name":"Model Kwargs","advanced":true,"dynamic":false,"info":"","title_case":false,"type":"dict","_input_type":"DictInput"},"model_name":{"trace_as_metadata":true,"options":["gpt-4o-mini","gpt-4o","gpt-4-turbo","gpt-4-turbo-preview","gpt-4","gpt-3.5-turbo","gpt-3.5-turbo-0125"],"combobox":false,"required":false,"placeholder":"","show":true,"name":"model_name","value":"gpt-4o-mini","display_name":"Model Name","advanced":false,"dynamic":false,"info":"","title_case":false,"type":"str","_input_type":"DropdownInput"},"openai_api_base":{"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"openai_api_base","value":"","display_name":"OpenAI API Base","advanced":true,"dynamic":false,"info":"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.","title_case":false,"type":"str","_input_type":"StrInput"},"output_schema":{"trace_as_input":true,"list":true,"required":false,"placeholder":"","show":true,"name":"output_schema","value":{},"display_name":"Schema","advanced":true,"dynamic":false,"info":"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.","title_case":false,"type":"dict","_input_type":"DictInput"},"seed":{"trace_as_metadata":true,"list":false,"required":false,"placeholder":"","show":true,"name":"seed","value":1,"display_name":"Seed","advanced":true,"dynamic":false,"info":"The seed controls the reproducibility of the job.","title_case":false,"type":"int","_input_type":"IntInput"},"stream":{"trace_as_metadata":true,"list":false,"required":false,"placeholder":"","show":true,"name":"stream","value":false,"display_name":"Stream","advanced":true,"dynamic":false,"info":"Stream the response from the model. Streaming works only in Chat.","title_case":false,"type":"bool","_input_type":"BoolInput"},"system_message":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"system_message","value":"","display_name":"System Message","advanced":true,"input_types":["Message"],"dynamic":false,"info":"System message to pass to the model.","title_case":false,"type":"str","_input_type":"MessageTextInput"},"temperature":{"trace_as_metadata":true,"list":false,"required":false,"placeholder":"","show":true,"name":"temperature","value":0.1,"display_name":"Temperature","advanced":false,"dynamic":false,"info":"","title_case":false,"type":"float","_input_type":"FloatInput"}},"description":"Generates text using OpenAI LLMs.","icon":"OpenAI","base_classes":["LanguageModel","Message"],"display_name":"OpenAI","documentation":"","custom_fields":{},"output_types":[],"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Message"],"selected":"Message","name":"text_output","display_name":"Text","method":"text_response","value":"__UNDEFINED__","cache":true,"required_inputs":["input_value","stream","system_message"]},{"types":["LanguageModel"],"selected":"LanguageModel","name":"model_output","display_name":"Language Model","method":"build_model","value":"__UNDEFINED__","cache":true,"required_inputs":["api_key","json_mode","max_tokens","model_kwargs","model_name","openai_api_base","output_schema","seed","temperature"]}],"field_order":["input_value","system_message","stream","max_tokens","model_kwargs","json_mode","output_schema","model_name","openai_api_base","api_key","temperature","seed","output_parser"],"beta":false,"edited":false,"metadata":{},"lf_version":"1.0.19.post1"},"id":"OpenAIModel-oE0wj"},"selected":false,"width":384,"height":587,"positionAbsolute":{"x":2133.8172349265606,"y":182.2210159995695},"dragging":false},{"id":"Prompt-6GBpW","type":"genericNode","position":{"x":1716.4755993278068,"y":221.09946248488012},"data":{"type":"Prompt","node":{"template":{"_type":"Component","code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"from langflow.base.prompts.api_utils import process_prompt_template\nfrom langflow.custom import Component\nfrom langflow.inputs.inputs import DefaultPromptField\nfrom langflow.io import Output, PromptInput\nfrom langflow.schema.message import Message\nfrom langflow.template.utils import update_template_values\n\n\nclass PromptComponent(Component):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n trace_type = \"prompt\"\n name = \"Prompt\"\n\n inputs = [\n PromptInput(name=\"template\", display_name=\"Template\"),\n ]\n\n outputs = [\n Output(display_name=\"Prompt Message\", name=\"prompt\", method=\"build_prompt\"),\n ]\n\n async def build_prompt(\n self,\n ) -> Message:\n prompt = await Message.from_template_and_variables(**self._attributes)\n self.status = prompt.text\n return prompt\n\n def _update_template(self, frontend_node: dict):\n prompt_template = frontend_node[\"template\"][\"template\"][\"value\"]\n custom_fields = frontend_node[\"custom_fields\"]\n frontend_node_template = frontend_node[\"template\"]\n _ = process_prompt_template(\n template=prompt_template,\n name=\"template\",\n custom_fields=custom_fields,\n frontend_node_template=frontend_node_template,\n )\n return frontend_node\n\n def post_code_processing(self, new_frontend_node: dict, current_frontend_node: dict):\n \"\"\"\n This function is called after the code validation is done.\n \"\"\"\n frontend_node = super().post_code_processing(new_frontend_node, current_frontend_node)\n template = frontend_node[\"template\"][\"template\"][\"value\"]\n # Kept it duplicated for backwards compatibility\n _ = process_prompt_template(\n template=template,\n name=\"template\",\n custom_fields=frontend_node[\"custom_fields\"],\n frontend_node_template=frontend_node[\"template\"],\n )\n # Now that template is updated, we need to grab any values that were set in the current_frontend_node\n # and update the frontend_node with those values\n update_template_values(new_template=frontend_node, previous_template=current_frontend_node[\"template\"])\n return frontend_node\n\n def _get_fallback_input(self, **kwargs):\n return DefaultPromptField(**kwargs)\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"template":{"trace_as_input":true,"list":false,"required":false,"placeholder":"","show":true,"name":"template","value":"{context}\n\nTranslate the text you receive into Spanish!","display_name":"Template","advanced":false,"dynamic":false,"info":"","title_case":false,"type":"prompt","_input_type":"PromptInput"},"context":{"field_type":"str","required":false,"placeholder":"","list":false,"show":true,"multiline":true,"value":"","fileTypes":[],"file_path":"","name":"context","display_name":"context","advanced":false,"input_types":["Message","Text"],"dynamic":false,"info":"","load_from_db":false,"title_case":false,"type":"str"}},"description":"Create a prompt template with dynamic variables.","icon":"prompts","is_input":null,"is_output":null,"is_composition":null,"base_classes":["Message"],"name":"","display_name":"Prompt","documentation":"","custom_fields":{"template":["context"]},"output_types":[],"full_path":null,"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Message"],"selected":"Message","name":"prompt","hidden":null,"display_name":"Prompt Message","method":"build_prompt","value":"__UNDEFINED__","cache":true,"required_inputs":null}],"field_order":["template"],"beta":false,"error":null,"edited":false,"metadata":{},"lf_version":"1.0.19.post1"},"id":"Prompt-6GBpW"},"selected":false,"width":384,"height":391,"positionAbsolute":{"x":1716.4755993278068,"y":221.09946248488012},"dragging":false},{"id":"ChatOutput-CJyAq","type":"genericNode","position":{"x":2546.1883300096442,"y":346.06258013037495},"data":{"type":"ChatOutput","node":{"template":{"_type":"Component","code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageTextInput, Output\nfrom langflow.memory import store_message\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_NAME_AI, MESSAGE_SENDER_USER\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"ChatOutput\"\n name = \"ChatOutput\"\n\n inputs = [\n MessageTextInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n def message_response(self) -> Message:\n message = Message(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n )\n if (\n self.session_id\n and isinstance(message, Message)\n and isinstance(message.text, str)\n and self.should_store_message\n ):\n store_message(\n message,\n flow_id=self.graph.flow_id,\n )\n self.message.value = message\n\n self.status = message\n return message\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"data_template":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"data_template","value":"{text}","display_name":"Data Template","advanced":true,"input_types":["Message"],"dynamic":false,"info":"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.","title_case":false,"type":"str","_input_type":"MessageTextInput"},"input_value":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"input_value","value":"","display_name":"Text","advanced":false,"input_types":["Message"],"dynamic":false,"info":"Message to be passed as output.","title_case":false,"type":"str","_input_type":"MessageTextInput"},"sender":{"trace_as_metadata":true,"options":["Machine","User"],"combobox":false,"required":false,"placeholder":"","show":true,"name":"sender","value":"Machine","display_name":"Sender Type","advanced":true,"dynamic":false,"info":"Type of sender.","title_case":false,"type":"str","_input_type":"DropdownInput"},"sender_name":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"sender_name","value":"AI","display_name":"Sender Name","advanced":true,"input_types":["Message"],"dynamic":false,"info":"Name of the sender.","title_case":false,"type":"str","_input_type":"MessageTextInput"},"session_id":{"trace_as_input":true,"trace_as_metadata":true,"load_from_db":false,"list":false,"required":false,"placeholder":"","show":true,"name":"session_id","value":"","display_name":"Session ID","advanced":true,"input_types":["Message"],"dynamic":false,"info":"The session ID of the chat. If empty, the current session ID parameter will be used.","title_case":false,"type":"str","_input_type":"MessageTextInput"},"should_store_message":{"trace_as_metadata":true,"list":false,"required":false,"placeholder":"","show":true,"name":"should_store_message","value":true,"display_name":"Store Messages","advanced":true,"dynamic":false,"info":"Store the message in the history.","title_case":false,"type":"bool","_input_type":"BoolInput"}},"description":"Display a chat message in the Playground.","icon":"ChatOutput","base_classes":["Message"],"display_name":"Chat Output","documentation":"","custom_fields":{},"output_types":[],"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Message"],"selected":"Message","name":"message","display_name":"Message","method":"message_response","value":"__UNDEFINED__","cache":true}],"field_order":["input_value","should_store_message","sender","sender_name","session_id","data_template"],"beta":false,"edited":false,"metadata":{},"lf_version":"1.0.19.post1"},"id":"ChatOutput-CJyAq"},"selected":false,"width":384,"height":289,"positionAbsolute":{"x":2546.1883300096442,"y":346.06258013037495},"dragging":false},{"id":"note-5pHk3","type":"noteNode","position":{"x":-606.6654615534108,"y":34.012614295538015},"data":{"node":{"description":"**Google Drive Example Scopes**\n\n**Langflow - OAuth Integration Documentation**\n\n```\ndocs.langflow.org/integrations-setup-google-oauth-langflow\n```\n\n**Drive API Documentation**\n\nhttps://developers.google.com/drive/api/guides/api-specific-auth\n\n**Scope Used in This Example**\n\nPermission to view and download all your Drive files.\n\n```\nhttps://www.googleapis.com/auth/drive.readonly\n```\n\n**Example of How to Enter Scopes**\n\n```\nhttps://www.googleapis.com/auth/drive.apps.readonly, https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/drive.readonly, https://www.googleapis.com/auth/drive.activity\n```","display_name":"","documentation":"","template":{"backgroundColor":"indigo"}},"type":"note","id":"note-5pHk3"},"width":600,"height":631,"selected":false,"dragging":false,"positionAbsolute":{"x":-606.6654615534108,"y":34.012614295538015},"style":{"width":600,"height":631},"resizing":false},{"id":"GoogleOAuthJSONToken-wqXNt","type":"genericNode","position":{"x":9.33468520106726,"y":206.4834360253384},"data":{"type":"GoogleOAuthToken","node":{"template":{"_type":"Component","oauth_credentials":{"trace_as_metadata":true,"file_path":"","fileTypes":["json"],"list":false,"required":true,"placeholder":"","show":true,"name":"oauth_credentials","value":"","display_name":"Credentials File","advanced":false,"dynamic":false,"info":"Input OAuth Credentials file. (e.g. credentials.json)","title_case":false,"type":"file","_input_type":"FileInput","load_from_db":false},"code":{"type":"code","required":true,"placeholder":"","list":false,"show":true,"multiline":true,"value":"from langflow.custom import Component\nfrom langflow.io import FileInput, Output\nfrom langflow.schema import Data\nfrom google_auth_oauthlib.flow import InstalledAppFlow\nfrom google.oauth2.credentials import Credentials\nfrom google.auth.transport.requests import Request \nimport json\nimport os\nimport re\n\n\nclass GoogleOAuthToken(Component):\n display_name = \"Google OAuth Token \"\n description = \"A component to generate a json string containing your Google OAuth token.\"\n documentation: str = \"https://developers.google.com/identity/protocols/oauth2/web-server?hl=pt-br#python_1\"\n icon = \"Google\"\n name = \"GoogleOAuthToken\"\n\n inputs = [\n StrInput(\n name=\"scopes\",\n display_name=\"Scopes\",\n info=\"Input a comma-separated list of scopes with the permissions required for your application.\",\n required=True\n ),\n FileInput(\n name=\"oauth_credentials\",\n display_name=\"Credentials File\",\n info=\"Input OAuth Credentials file. (e.g. credentials.json)\",\n file_types=[\"json\"],\n required=True\n ),\n ]\n\n outputs = [\n Output(display_name=\"Output\", name=\"output\", method=\"build_output\"),\n ]\n\n def validate_scopes(self, scopes):\n pattern = (\n r\"^(https:\\/\\/(www\\.googleapis\\.com\\/auth\\/[\\w\\.\\-]+\"\n r\"|mail\\.google\\.com\\/\"\n r\"|www\\.google\\.com\\/calendar\\/feeds\"\n r\"|www\\.google\\.com\\/m8\\/feeds))\"\n r\"(,\\s*https:\\/\\/(www\\.googleapis\\.com\\/auth\\/[\\w\\.\\-]+\"\n r\"|mail\\.google\\.com\\/\"\n r\"|www\\.google\\.com\\/calendar\\/feeds\"\n r\"|www\\.google\\.com\\/m8\\/feeds))*$\"\n )\n if not re.match(pattern, scopes):\n raise ValueError(\n \"Invalid format for scopes. Please ensure scopes are comma-separated, without quotes, and without extra characters. Also, check if each URL is correct.\"\n )\n\n def build_output(self) -> Data:\n self.validate_scopes(self.scopes)\n\n\n user_scopes = [scope.strip() for scope in self.scopes.split(',')]\n if self.scopes:\n SCOPES = user_scopes \n else:\n raise ValueError(\"Incorrect Scope, check if you filled in the scopes field correctly!\")\n\n creds = None\n token_path = 'token.json' \n\n if os.path.exists(token_path):\n with open(token_path, 'r') as token_file:\n creds = Credentials.from_authorized_user_file(token_path, SCOPES)\n\n if not creds or not creds.valid:\n if creds and creds.expired and creds.refresh_token:\n creds.refresh(Request())\n else:\n if self.oauth_credentials:\n CLIENT_SECRET_FILE = self.oauth_credentials\n else:\n raise ValueError(\"Oauth 2.0 Credentials file not provided. (e.g. the credentials.json)\")\n \n flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRET_FILE, SCOPES)\n creds = flow.run_local_server(port=0)\n\n with open(token_path, 'w') as token_file:\n token_file.write(creds.to_json())\n\n return creds.to_json()\n","fileTypes":[],"file_path":"","password":false,"name":"code","advanced":true,"dynamic":true,"info":"","load_from_db":false,"title_case":false},"scopes":{"trace_as_metadata":true,"load_from_db":false,"list":false,"required":true,"placeholder":"","show":true,"name":"scopes","value":"https://www.googleapis.com/auth/drive.readonly","display_name":"Scopes","advanced":false,"dynamic":false,"info":"Input a comma-separated list of scopes with the permissions required for your application.","title_case":false,"type":"str","_input_type":"StrInput"}},"description":"A component to generate a json string containing your Google OAuth token.","icon":"Google","base_classes":["Data"],"display_name":"Google OAuth Token","documentation":"https://developers.google.com/identity/protocols/oauth2/web-server?hl=pt-br#python_1","custom_fields":{},"output_types":[],"pinned":false,"conditional_paths":[],"frozen":false,"outputs":[{"types":["Data"],"selected":"Data","name":"output","display_name":"Output","method":"build_output","value":"__UNDEFINED__","cache":true}],"field_order":["scopes","oauth_credentials"],"beta":false,"edited":true,"metadata":{}},"id":"GoogleOAuthJSONToken-wqXNt"},"selected":true,"width":384,"height":391,"positionAbsolute":{"x":9.33468520106726,"y":206.4834360253384},"dragging":false}],"edges":[{"source":"ParseData-U2bvS","sourceHandle":"{œdataTypeœ:œParseDataœ,œidœ:œParseData-U2bvSœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}","target":"GoogleDriveComponent-yTOyB","targetHandle":"{œfieldNameœ:œjson_stringœ,œidœ:œGoogleDriveComponent-yTOyBœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}","data":{"targetHandle":{"fieldName":"json_string","id":"GoogleDriveComponent-yTOyB","inputTypes":["Message"],"type":"str"},"sourceHandle":{"dataType":"ParseData","id":"ParseData-U2bvS","name":"text","output_types":["Message"]}},"id":"reactflow__edge-ParseData-U2bvS{œdataTypeœ:œParseDataœ,œidœ:œParseData-U2bvSœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-GoogleDriveComponent-yTOyB{œfieldNameœ:œjson_stringœ,œidœ:œGoogleDriveComponent-yTOyBœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}","animated":false,"className":""},{"source":"GoogleDriveComponent-yTOyB","sourceHandle":"{œdataTypeœ:œGoogleDriveComponentœ,œidœ:œGoogleDriveComponent-yTOyBœ,œnameœ:œdocsœ,œoutput_typesœ:[œDataœ]}","target":"ParseData-Fo5CI","targetHandle":"{œfieldNameœ:œdataœ,œidœ:œParseData-Fo5CIœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}","data":{"targetHandle":{"fieldName":"data","id":"ParseData-Fo5CI","inputTypes":["Data"],"type":"other"},"sourceHandle":{"dataType":"GoogleDriveComponent","id":"GoogleDriveComponent-yTOyB","name":"docs","output_types":["Data"]}},"id":"reactflow__edge-GoogleDriveComponent-yTOyB{œdataTypeœ:œGoogleDriveComponentœ,œidœ:œGoogleDriveComponent-yTOyBœ,œnameœ:œdocsœ,œoutput_typesœ:[œDataœ]}-ParseData-Fo5CI{œfieldNameœ:œdataœ,œidœ:œParseData-Fo5CIœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}","animated":false,"className":""},{"source":"Prompt-6GBpW","sourceHandle":"{œdataTypeœ:œPromptœ,œidœ:œPrompt-6GBpWœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}","target":"OpenAIModel-oE0wj","targetHandle":"{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-oE0wjœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}","data":{"targetHandle":{"fieldName":"input_value","id":"OpenAIModel-oE0wj","inputTypes":["Message"],"type":"str"},"sourceHandle":{"dataType":"Prompt","id":"Prompt-6GBpW","name":"prompt","output_types":["Message"]}},"id":"reactflow__edge-Prompt-6GBpW{œdataTypeœ:œPromptœ,œidœ:œPrompt-6GBpWœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-oE0wj{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-oE0wjœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}","animated":false,"className":""},{"source":"ParseData-Fo5CI","sourceHandle":"{œdataTypeœ:œParseDataœ,œidœ:œParseData-Fo5CIœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}","target":"Prompt-6GBpW","targetHandle":"{œfieldNameœ:œcontextœ,œidœ:œPrompt-6GBpWœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}","data":{"targetHandle":{"fieldName":"context","id":"Prompt-6GBpW","inputTypes":["Message","Text"],"type":"str"},"sourceHandle":{"dataType":"ParseData","id":"ParseData-Fo5CI","name":"text","output_types":["Message"]}},"id":"reactflow__edge-ParseData-Fo5CI{œdataTypeœ:œParseDataœ,œidœ:œParseData-Fo5CIœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-6GBpW{œfieldNameœ:œcontextœ,œidœ:œPrompt-6GBpWœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}","animated":false,"className":""},{"source":"OpenAIModel-oE0wj","sourceHandle":"{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-oE0wjœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}","target":"ChatOutput-CJyAq","targetHandle":"{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-CJyAqœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}","data":{"targetHandle":{"fieldName":"input_value","id":"ChatOutput-CJyAq","inputTypes":["Message"],"type":"str"},"sourceHandle":{"dataType":"OpenAIModel","id":"OpenAIModel-oE0wj","name":"text_output","output_types":["Message"]}},"id":"reactflow__edge-OpenAIModel-oE0wj{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-oE0wjœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-CJyAq{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-CJyAqœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}","animated":false,"className":""},{"source":"GoogleOAuthJSONToken-wqXNt","sourceHandle":"{œdataTypeœ:œGoogleOAuthTokenœ,œidœ:œGoogleOAuthJSONToken-wqXNtœ,œnameœ:œoutputœ,œoutput_typesœ:[œDataœ]}","target":"ParseData-U2bvS","targetHandle":"{œfieldNameœ:œdataœ,œidœ:œParseData-U2bvSœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}","data":{"targetHandle":{"fieldName":"data","id":"ParseData-U2bvS","inputTypes":["Data"],"type":"other"},"sourceHandle":{"dataType":"GoogleOAuthToken","id":"GoogleOAuthJSONToken-wqXNt","name":"output","output_types":["Data"]}},"id":"reactflow__edge-GoogleOAuthJSONToken-wqXNt{œdataTypeœ:œGoogleOAuthTokenœ,œidœ:œGoogleOAuthJSONToken-wqXNtœ,œnameœ:œoutputœ,œoutput_typesœ:[œDataœ]}-ParseData-U2bvS{œfieldNameœ:œdataœ,œidœ:œParseData-U2bvSœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}","animated":false,"className":""}],"viewport":{"x":362.3094941500808,"y":289.9558489915596,"zoom":0.4338730252876207}},"description":"An example of a flow that connects to Google Drive to access a text document, reads the content, translates it into the desired language, and returns the translated text in the chat, allowing for quick and efficient automation of the Google Docs file translation process.","name":"Google Drive Docs Translations Example","last_tested_version":"1.0.19.post1","endpoint_name":"google_drive_docs_example","is_component":false} \ No newline at end of file diff --git a/docs/static/img/google/create-a-google-cloud-project.gif b/docs/static/img/google/create-a-google-cloud-project.gif new file mode 100644 index 000000000..177225a6a Binary files /dev/null and b/docs/static/img/google/create-a-google-cloud-project.gif differ diff --git a/docs/static/img/google/create-oauth-client-id.png b/docs/static/img/google/create-oauth-client-id.png new file mode 100644 index 000000000..380e6bee2 Binary files /dev/null and b/docs/static/img/google/create-oauth-client-id.png differ diff --git a/docs/static/img/google/setup-oauth-consent-screen.png b/docs/static/img/google/setup-oauth-consent-screen.png new file mode 100644 index 000000000..68bc4e600 Binary files /dev/null and b/docs/static/img/google/setup-oauth-consent-screen.png differ diff --git a/src/backend/base/langflow/components/data/google_oauth_token.py b/src/backend/base/langflow/components/data/google_oauth_token.py new file mode 100644 index 000000000..b7d43a8a8 --- /dev/null +++ b/src/backend/base/langflow/components/data/google_oauth_token.py @@ -0,0 +1,89 @@ +import json +import re +from pathlib import Path + +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow + +from langflow.custom import Component +from langflow.io import FileInput, MultilineInput, Output +from langflow.schema import Data + + +class GoogleOAuthToken(Component): + display_name = "Google OAuth Token" + description = "Generates a JSON string with your Google OAuth token." + documentation: str = "https://developers.google.com/identity/protocols/oauth2/web-server?hl=pt-br#python_1" + icon = "Google" + name = "GoogleOAuthToken" + + inputs = [ + MultilineInput( + name="scopes", + display_name="Scopes", + info="Input scopes for your application.", + required=True, + ), + FileInput( + name="oauth_credentials", + display_name="Credentials File", + info="Input OAuth Credentials file (e.g. credentials.json).", + file_types=["json"], + required=True, + ), + ] + + outputs = [ + Output(display_name="Output", name="output", method="build_output"), + ] + + def validate_scopes(self, scopes): + pattern = ( + r"^(https://www\.googleapis\.com/auth/[\w\.\-]+" + r"|mail\.google\.com/" + r"|www\.google\.com/calendar/feeds" + r"|www\.google\.com/m8/feeds)" + r"(,\s*https://www\.googleapis\.com/auth/[\w\.\-]+" + r"|mail\.google\.com/" + r"|www\.google\.com/calendar/feeds" + r"|www\.google\.com/m8/feeds)*$" + ) + if not re.match(pattern, scopes): + error_message = "Invalid scope format." + raise ValueError(error_message) + + def build_output(self) -> Data: + self.validate_scopes(self.scopes) + + user_scopes = [scope.strip() for scope in self.scopes.split(",")] + if self.scopes: + scopes = user_scopes + else: + error_message = "Incorrect scope, check the scopes field." + raise ValueError(error_message) + + creds = None + token_path = Path("token.json") + + if token_path.exists(): + creds = Credentials.from_authorized_user_file(str(token_path), scopes) + + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + if self.oauth_credentials: + client_secret_file = self.oauth_credentials + else: + error_message = "OAuth 2.0 Credentials file not provided." + raise ValueError(error_message) + + flow = InstalledAppFlow.from_client_secrets_file(client_secret_file, scopes) + creds = flow.run_local_server(port=0) + + token_path.write_text(creds.to_json(), encoding="utf-8") + + creds_json = json.loads(creds.to_json()) + + return Data(data=creds_json)