feat: enhance APIRequestComponent with new output methods (#7148)
* ✨ (Research Translation Loop.spec.ts): Increase timeout value by a factor of 3 for better reliability in waiting for element to appear ✨ (chatInputOutputUser-shard-1.spec.ts): Increase timeout value by a factor of 3 for better reliability in waiting for element to appear * 🐛 (typescript_test.yml): adjust the maximum shard count to 10 to prevent exceeding the limit and optimize test execution. * 🐛 (chatInputOutputUser-shard-1.spec.ts): increase timeout for waiting for "built successfully" text to improve test reliability * ⬆️ (typescript_test.yml): increase maximum shard count to 15 for better test distribution ♻️ (Portfolio Website Code Generator.spec.ts): refactor test assertions to improve readability and maintainability * 🐛 (typescript_test.yml): adjust the maximum shard count to 10 to prevent exceeding the limit of parallel test executions * 🔧 (typescript_test.yml): Increase maximum shard count to 15 for better test distribution efficiency 🐛 (chatInputOutputUser-shard-1.spec.ts): Update timeout values for page element waits to prevent premature failures due to timing issues * templates adjustments * travel planning fix * Update Travel Planning Agents.json * fix templates * ♻️ (Youtube Analysis.spec.ts): remove unused imports and cleanup code for better readability and maintainability * json fix * fix: update simple agent template (#7081) * Update Simple Agent.json * Update Simple Agent.json * feat: update search agent template agent component (#7082) * update agent component with the latest changes * Update Search agent.json * Update Search agent.json * feat: enhance APIRequestComponent with new output methods - Refactored output methods to include `as_data` and `as_message` for better data handling. - Updated existing output method from `make_requests` to `as_data` for consistency. - Improved metadata handling by merging dictionary results and storing non-dict results as 'data'. - Added detailed docstrings for new methods to clarify their functionality. * added the updated test class * [autofix.ci] apply automated fixes * 📝 (ContentBlockDisplay.tsx): wrap headerIcon element in a span with data-testid attribute for better accessibility 📝 (DurationDisplay.tsx): add data-testid attribute to the duration display element for testing purposes 📝 (Simple Agent.spec.ts, Social Media Agent.spec.ts, generalBugs-shard-9.spec.ts): update test assertions to improve readability and accuracy 📝 (chatInputOutput.spec.ts): add a skip test annotation and a todo comment for further investigation * ✨ (typescript_test.yml): Add support for a new development suite in the test workflow ✨ (frontend): Add support for a new development suite in multiple test files * 🔧 (.github/workflows/typescript_test.yml): ensure that the SUITES variable is valid JSON format to prevent errors and improve reliability * ✨ (inputFileComponent/index.tsx): Refactor InputFileComponent to wrap Button component in a div for better structure and readability 🔧 (Vector Store.spec.ts): Add initialGPTsetup function to set up GPT environment variables for tests 🔧 (Vector Store.spec.ts): Refactor test to use initialGPTsetup function and improve readability 🔧 (add-new-api-keys.ts): Refactor addNewApiKeys function to handle multiple openai_api_key inputs 🔧 (remove-old-api-keys.ts): Refactor removeOldApiKeys function to click on the correct remove-icon-badge element * ✨ Add support for running tests in serial mode with a delay between each test 🔧 Configure tests to run with a 3-second delay between each test run 🔧 Add a 7-second delay before starting tests in userSettings.spec.ts 🔧 Add a 3-second delay before the second event delivery mode test in withEventDeliveryModes.ts * 📝 (userSettings.spec.ts): remove unnecessary console log message to improve test readability and maintainability * 📝 (deploy-dropdown.tsx): Add data-testid attribute to shareable-playground element ✨ (index.tsx): Add useGetTypes hook to fetch types data when component is fetched 🔧 (publish-flow.spec.ts): Increase timeout for page.waitForSelector and page.waitForTimeout 🔧 (Vector Store.spec.ts): Refactor code to wait for dropdown to appear and be visible 🔧 (files-page.spec.ts): Refactor tests to run serially with a delay between each test and add comments to improve readability * ✨ Add useGetTypes hook to fetch types data and support caching with checkCache option 🔧 Refactor useGetTypes hook to accept options object with checkCache property 🔧 Refactor useGetTypes hook to conditionally return cached data if available 🔧 Refactor useGetTypes hook to fetch types data with force_refresh query parameter 🔧 Refactor useGetTypes hook to handle errors and set types data 🔧 Refactor useGetTypes hook to improve query function and options handling 🔧 Refactor useGetTypes hook to optimize query function and options handling 🔧 Refactor useGetTypes hook to improve caching logic and error handling 🔧 Refactor useGetTypes hook to enhance caching mechanism and error handling 🔧 Refactor useGetTypes hook to improve data fetching and error handling 🔧 Refactor useGetTypes hook to optimize data fetching and error handling * ✨ (Vector Store.spec.ts): Remove unnecessary loadingOptions check and expectation ♻️ (withEventDeliveryModes.ts): Refactor withEventDeliveryModes function to accept a timeout parameter for better flexibility * ⚡️ (Vector Store.spec.ts): increase timeout for page.waitForTimeout from 2000ms to 10000ms to improve test stability and reliability * update pokedex agent template * ✨ (publish-flow.spec.ts): Remove unnecessary development tag from test description ✨ (Simple Agent.spec.ts, Social Media Agent.spec.ts): Remove unnecessary development tag from test description ✨ (Vector Store.spec.ts): Change withEventDeliveryModes to test for better test organization 🔧 (chatInputOutput.spec.ts): Refactor test description and remove unnecessary development tag ✨ (files-page.spec.ts): Remove unnecessary development tag from test descriptions ✨ (userSettings.spec.ts): Remove unnecessary development tag from test description * formatting json * ✨ (Vector Store.spec.ts): Add new integration test withEventDeliveryModes for Vector Store RAG 🔧 (Vector Store.spec.ts): Update timeout values in test functions to improve test performance and reliability * 📝 (Text Sentiment Analysis.json): Update JSON file to have consistent formatting and structure for output_types and inputTypes arrays 📝 (Text Sentiment Analysis.spec.ts): Refactor integration test for Text Sentiment Analysis to improve readability and maintainability * ✨ (PageComponent/index.tsx): Update minZoom and maxZoom values for better user experience 🐛 (upload-file.ts): Fix missing await keyword before clicking on an element * 🐛 (PageComponent/index.tsx): fix minZoom value to 0.2 for consistency with fitViewOptions and improve user experience * ✨ (dropdownComponent.spec.ts): add delay before checking dropdown value to ensure it has updated properly ✨ (dropdownComponent.spec.ts): add delay before interacting with more options modal to ensure it has loaded ✨ (floatComponent.spec.ts): add delay after clicking add button to wait for API request to complete --------- Co-authored-by: cristhianzl <cristhian.lousa@gmail.com> Co-authored-by: Edwin Jose <edwin.jose@datastax.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
ec78119b3a
commit
44254206b8
30 changed files with 1183 additions and 727 deletions
25
.github/workflows/typescript_test.yml
vendored
25
.github/workflows/typescript_test.yml
vendored
|
|
@ -99,6 +99,7 @@ jobs:
|
|||
# If input suites were not provided, determine based on changes
|
||||
if [[ "$SUITES" == "[]" ]]; then
|
||||
echo "No input suites provided - determining from changes"
|
||||
SUITES='[]' # Ensure we start with a valid JSON array
|
||||
TAGS=()
|
||||
# Add suites and tags based on changed files
|
||||
if [[ "${{ steps.filter.outputs.components }}" == "true" ]]; then
|
||||
|
|
@ -126,6 +127,11 @@ jobs:
|
|||
TAGS+=("@database")
|
||||
echo "Added database suite"
|
||||
fi
|
||||
if [[ "${{ steps.filter.outputs.development }}" == "true" ]]; then
|
||||
SUITES=$(echo $SUITES | jq -c '. += ["development"]')
|
||||
TAGS+=("@development")
|
||||
echo "Added development suite"
|
||||
fi
|
||||
|
||||
# Create grep pattern if we have tags
|
||||
if [ ${#TAGS[@]} -gt 0 ]; then
|
||||
|
|
@ -135,6 +141,21 @@ jobs:
|
|||
fi
|
||||
else
|
||||
# Process input suites to tags
|
||||
# First ensure SUITES is valid JSON
|
||||
if ! echo "$SUITES" | jq -e . > /dev/null 2>&1; then
|
||||
echo "Warning: Input suites is not valid JSON, attempting to fix"
|
||||
# Try to fix common issues like missing quotes
|
||||
if [[ "$SUITES" == "[development]" ]]; then
|
||||
SUITES='["development"]'
|
||||
elif [[ "$SUITES" =~ ^\[(.*)\]$ ]]; then
|
||||
# Extract items and add quotes
|
||||
ITEMS="${BASH_REMATCH[1]}"
|
||||
QUOTED_ITEMS=$(echo "$ITEMS" | sed 's/\([^,]*\)/"\1"/g')
|
||||
SUITES="[$QUOTED_ITEMS]"
|
||||
fi
|
||||
echo "Fixed suites: $SUITES"
|
||||
fi
|
||||
|
||||
TAGS=()
|
||||
if echo "$SUITES" | jq -e 'contains(["components"])' > /dev/null; then
|
||||
TAGS+=("@components")
|
||||
|
|
@ -151,6 +172,9 @@ jobs:
|
|||
if echo "$SUITES" | jq -e 'contains(["database"])' > /dev/null; then
|
||||
TAGS+=("@database")
|
||||
fi
|
||||
if echo "$SUITES" | jq -e 'contains(["development"])' > /dev/null; then
|
||||
TAGS+=("@development")
|
||||
fi
|
||||
|
||||
if [ ${#TAGS[@]} -gt 0 ]; then
|
||||
# Join tags with | for OR logic
|
||||
|
|
@ -168,6 +192,7 @@ jobs:
|
|||
# Ensure proper JSON formatting for matrix output
|
||||
echo "matrix=$(echo $SUITES | jq -c .)" >> $GITHUB_OUTPUT
|
||||
echo "test_grep=$TEST_GREP" >> $GITHUB_OUTPUT
|
||||
echo "suites=$SUITES" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
|||
|
|
@ -26,9 +26,7 @@ from langflow.io import (
|
|||
StrInput,
|
||||
TableInput,
|
||||
)
|
||||
from langflow.schema import Data
|
||||
from langflow.schema.dataframe import DataFrame
|
||||
from langflow.schema.dotdict import dotdict
|
||||
from langflow.schema import Data, DataFrame, Message, dotdict
|
||||
|
||||
|
||||
class APIRequestComponent(Component):
|
||||
|
|
@ -156,8 +154,9 @@ class APIRequestComponent(Component):
|
|||
]
|
||||
|
||||
outputs = [
|
||||
Output(display_name="Data", name="data", method="make_requests"),
|
||||
Output(display_name="Data", name="data", method="as_data"),
|
||||
Output(display_name="DataFrame", name="dataframe", method="as_dataframe"),
|
||||
Output(display_name="Message", name="message", method="as_message"),
|
||||
]
|
||||
|
||||
def _parse_json_value(self, value: Any) -> Any:
|
||||
|
|
@ -454,7 +453,12 @@ class APIRequestComponent(Component):
|
|||
self.log("Failed to decode JSON response")
|
||||
result = response.text.encode("utf-8")
|
||||
|
||||
metadata.update({"result": result})
|
||||
# If result is a dictionary, merge it with metadata
|
||||
if isinstance(result, dict):
|
||||
metadata.update(result)
|
||||
else:
|
||||
# If result is not a dict, store it as 'data'
|
||||
metadata["data"] = result
|
||||
|
||||
if include_httpx_metadata:
|
||||
metadata.update(
|
||||
|
|
@ -528,7 +532,7 @@ class APIRequestComponent(Component):
|
|||
urls = [self.add_query_params(url, query_params) for url in urls]
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
results = await asyncio.gather(
|
||||
return await asyncio.gather(
|
||||
*[
|
||||
self.make_request(
|
||||
client,
|
||||
|
|
@ -544,8 +548,6 @@ class APIRequestComponent(Component):
|
|||
for u, rec in zip(urls, bodies, strict=False)
|
||||
]
|
||||
)
|
||||
self.status = results
|
||||
return results
|
||||
|
||||
async def _response_info(
|
||||
self, response: httpx.Response, *, with_file_path: bool = False
|
||||
|
|
@ -644,6 +646,16 @@ class APIRequestComponent(Component):
|
|||
return processed_headers
|
||||
return {}
|
||||
|
||||
async def as_data(self) -> Data:
|
||||
"""Convert the API response data into a DataFrame.
|
||||
|
||||
Returns:
|
||||
DataFrame: A DataFrame containing the API response data.
|
||||
"""
|
||||
data = await self.make_requests()
|
||||
dicts = {"output": [d.data for d in data]}
|
||||
return Data(**dicts)
|
||||
|
||||
async def as_dataframe(self) -> DataFrame:
|
||||
"""Convert the API response data into a DataFrame.
|
||||
|
||||
|
|
@ -652,3 +664,12 @@ class APIRequestComponent(Component):
|
|||
"""
|
||||
data = await self.make_requests()
|
||||
return DataFrame(data)
|
||||
|
||||
async def as_message(self) -> Message:
|
||||
"""Convert the API response data into a DataFrame.
|
||||
|
||||
Returns:
|
||||
DataFrame: A DataFrame containing the API response data.
|
||||
"""
|
||||
data = await self.as_data()
|
||||
return Message(text=str(data))
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -9,16 +9,12 @@
|
|||
"dataType": "File",
|
||||
"id": "File-Ktatn",
|
||||
"name": "data",
|
||||
"output_types": [
|
||||
"Data"
|
||||
]
|
||||
"output_types": ["Data"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "data",
|
||||
"id": "ParseData-gouVC",
|
||||
"inputTypes": [
|
||||
"Data"
|
||||
],
|
||||
"inputTypes": ["Data"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
|
|
@ -37,16 +33,12 @@
|
|||
"dataType": "ParseData",
|
||||
"id": "ParseData-gouVC",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "text",
|
||||
"id": "Prompt-epiSD",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
|
|
@ -65,16 +57,12 @@
|
|||
"dataType": "Prompt",
|
||||
"id": "Prompt-epiSD",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "system_message",
|
||||
"id": "OpenAIModel-ppS3O",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
|
|
@ -93,16 +81,12 @@
|
|||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-ppS3O",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "summary",
|
||||
"id": "Prompt-l9XAo",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
|
|
@ -121,16 +105,12 @@
|
|||
"dataType": "Prompt",
|
||||
"id": "Prompt-l9XAo",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "system_message",
|
||||
"id": "OpenAIModel-DxfrQ",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
|
|
@ -149,16 +129,12 @@
|
|||
"dataType": "Prompt",
|
||||
"id": "Prompt-LKleN",
|
||||
"name": "prompt",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "system_message",
|
||||
"id": "OpenAIModel-W1vhv",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
|
|
@ -177,16 +153,12 @@
|
|||
"dataType": "ParseData",
|
||||
"id": "ParseData-gouVC",
|
||||
"name": "text",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "OpenAIModel-W1vhv",
|
||||
"inputTypes": [
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Message"],
|
||||
"type": "str"
|
||||
}
|
||||
},
|
||||
|
|
@ -205,18 +177,12 @@
|
|||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-W1vhv",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-V5ZFA",
|
||||
"inputTypes": [
|
||||
"Data",
|
||||
"DataFrame",
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Data", "DataFrame", "Message"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
|
|
@ -235,18 +201,12 @@
|
|||
"dataType": "OpenAIModel",
|
||||
"id": "OpenAIModel-DxfrQ",
|
||||
"name": "text_output",
|
||||
"output_types": [
|
||||
"Message"
|
||||
]
|
||||
"output_types": ["Message"]
|
||||
},
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
"id": "ChatOutput-8y94b",
|
||||
"inputTypes": [
|
||||
"Data",
|
||||
"DataFrame",
|
||||
"Message"
|
||||
],
|
||||
"inputTypes": ["Data", "DataFrame", "Message"],
|
||||
"type": "other"
|
||||
}
|
||||
},
|
||||
|
|
@ -263,9 +223,7 @@
|
|||
"data": {
|
||||
"id": "File-Ktatn",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data"
|
||||
],
|
||||
"base_classes": ["Data"],
|
||||
"beta": false,
|
||||
"category": "data",
|
||||
"conditional_paths": [],
|
||||
|
|
@ -302,9 +260,31 @@
|
|||
"required_inputs": [],
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
"allows_loop": false,
|
||||
"cache": true,
|
||||
"display_name": "DataFrame",
|
||||
"method": "load_dataframe",
|
||||
"name": "dataframe",
|
||||
"required_inputs": [],
|
||||
"selected": "DataFrame",
|
||||
"tool_mode": true,
|
||||
"types": ["DataFrame"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
"allows_loop": false,
|
||||
"cache": true,
|
||||
"display_name": "Message",
|
||||
"method": "load_message",
|
||||
"name": "message",
|
||||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
|
@ -372,10 +352,7 @@
|
|||
"display_name": "Server File Path",
|
||||
"dynamic": false,
|
||||
"info": "Data object with a 'file_path' property pointing to server file or a Message object with a path to the file. Supercedes 'Path' but supports same file types.",
|
||||
"input_types": [
|
||||
"Data",
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Data", "Message"],
|
||||
"list": true,
|
||||
"list_add_label": "Add More",
|
||||
"name": "file_path",
|
||||
|
|
@ -426,7 +403,7 @@
|
|||
"path": {
|
||||
"_input_type": "FileInput",
|
||||
"advanced": false,
|
||||
"display_name": "Path",
|
||||
"display_name": "Files",
|
||||
"dynamic": false,
|
||||
"fileTypes": [
|
||||
"txt",
|
||||
|
|
@ -455,7 +432,7 @@
|
|||
],
|
||||
"file_path": "43bb2a52-8dbf-4edf-a200-c54ee0e7fa1f\\2025-02-21_09-35-24_messages.json",
|
||||
"info": "Supported file extensions: txt, md, mdx, csv, json, yaml, yml, xml, html, htm, pdf, docx, py, sh, sql, js, ts, tsx; optionally bundled in file extensions: zip, tar, tgz, bz2, gz",
|
||||
"list": false,
|
||||
"list": true,
|
||||
"list_add_label": "Add More",
|
||||
"name": "path",
|
||||
"placeholder": "",
|
||||
|
|
@ -466,6 +443,25 @@
|
|||
"type": "file",
|
||||
"value": ""
|
||||
},
|
||||
"separator": {
|
||||
"_input_type": "StrInput",
|
||||
"advanced": true,
|
||||
"display_name": "Separator",
|
||||
"dynamic": false,
|
||||
"info": "Specify the separator to use between multiple outputs in Message format.",
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
"name": "separator",
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
"show": true,
|
||||
"title_case": false,
|
||||
"tool_mode": false,
|
||||
"trace_as_metadata": true,
|
||||
"type": "str",
|
||||
"value": "\n\n"
|
||||
},
|
||||
"silent_errors": {
|
||||
"_input_type": "BoolInput",
|
||||
"advanced": true,
|
||||
|
|
@ -525,10 +521,7 @@
|
|||
"data": {
|
||||
"id": "ParseData-gouVC",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Data",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Data", "Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
|
|
@ -536,11 +529,7 @@
|
|||
"display_name": "Data to Message",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"field_order": [
|
||||
"data",
|
||||
"template",
|
||||
"sep"
|
||||
],
|
||||
"field_order": ["data", "template", "sep"],
|
||||
"frozen": false,
|
||||
"icon": "message-square",
|
||||
"legacy": false,
|
||||
|
|
@ -559,9 +548,7 @@
|
|||
"name": "text",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
|
|
@ -572,9 +559,7 @@
|
|||
"name": "data_list",
|
||||
"selected": "Data",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Data"
|
||||
],
|
||||
"types": ["Data"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
|
@ -605,9 +590,7 @@
|
|||
"display_name": "Data",
|
||||
"dynamic": false,
|
||||
"info": "The data to convert to text.",
|
||||
"input_types": [
|
||||
"Data"
|
||||
],
|
||||
"input_types": ["Data"],
|
||||
"list": true,
|
||||
"list_add_label": "Add More",
|
||||
"name": "data",
|
||||
|
|
@ -646,9 +629,7 @@
|
|||
"display_name": "Template",
|
||||
"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.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -662,7 +643,7 @@
|
|||
"trace_as_input": true,
|
||||
"trace_as_metadata": true,
|
||||
"type": "str",
|
||||
"value": "list of messages details: {text}"
|
||||
"value": "list of messages details: {file_path}"
|
||||
}
|
||||
},
|
||||
"tool_mode": false
|
||||
|
|
@ -687,25 +668,18 @@
|
|||
"data": {
|
||||
"id": "Prompt-l9XAo",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
"template": [
|
||||
"summary"
|
||||
]
|
||||
"template": ["summary"]
|
||||
},
|
||||
"description": "Create a prompt template with dynamic variables.",
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"error": null,
|
||||
"field_order": [
|
||||
"template",
|
||||
"tool_placeholder"
|
||||
],
|
||||
"field_order": ["template", "tool_placeholder"],
|
||||
"frozen": false,
|
||||
"full_path": null,
|
||||
"icon": "prompts",
|
||||
|
|
@ -727,9 +701,7 @@
|
|||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
|
@ -762,9 +734,7 @@
|
|||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
|
|
@ -800,9 +770,7 @@
|
|||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -840,25 +808,18 @@
|
|||
"data": {
|
||||
"id": "Prompt-epiSD",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
"template": [
|
||||
"text"
|
||||
]
|
||||
"template": ["text"]
|
||||
},
|
||||
"description": "Create a prompt template with dynamic variables.",
|
||||
"display_name": "Prompt",
|
||||
"documentation": "",
|
||||
"edited": false,
|
||||
"error": null,
|
||||
"field_order": [
|
||||
"template",
|
||||
"tool_placeholder"
|
||||
],
|
||||
"field_order": ["template", "tool_placeholder"],
|
||||
"frozen": false,
|
||||
"full_path": null,
|
||||
"icon": "prompts",
|
||||
|
|
@ -880,9 +841,7 @@
|
|||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
|
@ -933,9 +892,7 @@
|
|||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"load_from_db": false,
|
||||
"multiline": true,
|
||||
|
|
@ -953,9 +910,7 @@
|
|||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -993,10 +948,7 @@
|
|||
"data": {
|
||||
"id": "OpenAIModel-ppS3O",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
|
|
@ -1036,9 +988,7 @@
|
|||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
|
|
@ -1047,14 +997,10 @@
|
|||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
|
@ -1067,9 +1013,7 @@
|
|||
"display_name": "OpenAI API Key",
|
||||
"dynamic": false,
|
||||
"info": "The OpenAI API Key to use for the OpenAI model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": false,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
|
|
@ -1096,7 +1040,7 @@
|
|||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from 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 BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\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(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\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 DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\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 required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\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 IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\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 = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() 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 max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\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"
|
||||
"value": "from 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 BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\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(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\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 DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\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 required=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\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 IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\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 = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() 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 max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\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"
|
||||
},
|
||||
"input_value": {
|
||||
"_input_type": "MessageInput",
|
||||
|
|
@ -1104,9 +1048,7 @@
|
|||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -1266,7 +1208,7 @@
|
|||
},
|
||||
"stream": {
|
||||
"_input_type": "BoolInput",
|
||||
"advanced": false,
|
||||
"advanced": true,
|
||||
"display_name": "Stream",
|
||||
"dynamic": false,
|
||||
"info": "Stream the response from the model. Streaming works only in Chat.",
|
||||
|
|
@ -1288,9 +1230,7 @@
|
|||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -1308,7 +1248,7 @@
|
|||
},
|
||||
"temperature": {
|
||||
"_input_type": "SliderInput",
|
||||
"advanced": false,
|
||||
"advanced": true,
|
||||
"display_name": "Temperature",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
|
|
@ -1375,10 +1315,7 @@
|
|||
"data": {
|
||||
"id": "OpenAIModel-DxfrQ",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
|
|
@ -1418,9 +1355,7 @@
|
|||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
|
|
@ -1429,14 +1364,10 @@
|
|||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
|
@ -1449,9 +1380,7 @@
|
|||
"display_name": "OpenAI API Key",
|
||||
"dynamic": false,
|
||||
"info": "The OpenAI API Key to use for the OpenAI model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": false,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
|
|
@ -1478,7 +1407,7 @@
|
|||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from 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 BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\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(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\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 DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\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 required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\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 IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\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 = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() 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 max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\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"
|
||||
"value": "from 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 BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\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(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\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 DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\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 required=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\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 IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\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 = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() 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 max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\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"
|
||||
},
|
||||
"input_value": {
|
||||
"_input_type": "MessageInput",
|
||||
|
|
@ -1486,9 +1415,7 @@
|
|||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -1648,7 +1575,7 @@
|
|||
},
|
||||
"stream": {
|
||||
"_input_type": "BoolInput",
|
||||
"advanced": false,
|
||||
"advanced": true,
|
||||
"display_name": "Stream",
|
||||
"dynamic": false,
|
||||
"info": "Stream the response from the model. Streaming works only in Chat.",
|
||||
|
|
@ -1670,9 +1597,7 @@
|
|||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -1690,7 +1615,7 @@
|
|||
},
|
||||
"temperature": {
|
||||
"_input_type": "SliderInput",
|
||||
"advanced": false,
|
||||
"advanced": true,
|
||||
"display_name": "Temperature",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
|
|
@ -1757,9 +1682,7 @@
|
|||
"data": {
|
||||
"id": "Prompt-LKleN",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {
|
||||
|
|
@ -1770,10 +1693,7 @@
|
|||
"documentation": "",
|
||||
"edited": false,
|
||||
"error": null,
|
||||
"field_order": [
|
||||
"template",
|
||||
"tool_placeholder"
|
||||
],
|
||||
"field_order": ["template", "tool_placeholder"],
|
||||
"frozen": false,
|
||||
"full_path": null,
|
||||
"icon": "prompts",
|
||||
|
|
@ -1795,9 +1715,7 @@
|
|||
"name": "prompt",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
|
@ -1846,9 +1764,7 @@
|
|||
"display_name": "Tool Placeholder",
|
||||
"dynamic": false,
|
||||
"info": "A placeholder input for tool mode.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -1886,10 +1802,7 @@
|
|||
"data": {
|
||||
"id": "OpenAIModel-W1vhv",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"LanguageModel",
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["LanguageModel", "Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
|
|
@ -1929,9 +1842,7 @@
|
|||
"required_inputs": [],
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
},
|
||||
{
|
||||
|
|
@ -1940,14 +1851,10 @@
|
|||
"display_name": "Language Model",
|
||||
"method": "build_model",
|
||||
"name": "model_output",
|
||||
"required_inputs": [
|
||||
"api_key"
|
||||
],
|
||||
"required_inputs": ["api_key"],
|
||||
"selected": "LanguageModel",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"LanguageModel"
|
||||
],
|
||||
"types": ["LanguageModel"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
|
@ -1960,9 +1867,7 @@
|
|||
"display_name": "OpenAI API Key",
|
||||
"dynamic": false,
|
||||
"info": "The OpenAI API Key to use for the OpenAI model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"load_from_db": false,
|
||||
"name": "api_key",
|
||||
"password": true,
|
||||
|
|
@ -1989,7 +1894,7 @@
|
|||
"show": true,
|
||||
"title_case": false,
|
||||
"type": "code",
|
||||
"value": "from 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 BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\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(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\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 DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\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 required=True,\n ),\n SliderInput(\n name=\"temperature\", display_name=\"Temperature\", value=0.1, range_spec=RangeSpec(min=0, max=1, step=0.01)\n ),\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 IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\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 = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() 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 max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\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"
|
||||
"value": "from 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 BoolInput, DictInput, DropdownInput, IntInput, SecretStrInput, SliderInput, StrInput\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(\n name=\"model_kwargs\",\n display_name=\"Model Kwargs\",\n advanced=True,\n info=\"Additional keyword arguments to pass to the model.\",\n ),\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 DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n advanced=False,\n options=OPENAI_MODEL_NAMES,\n value=OPENAI_MODEL_NAMES[1],\n combobox=True,\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 required=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\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 IntInput(\n name=\"max_retries\",\n display_name=\"Max Retries\",\n info=\"The maximum number of retries to make when generating.\",\n advanced=True,\n value=5,\n ),\n IntInput(\n name=\"timeout\",\n display_name=\"Timeout\",\n info=\"The timeout for requests to OpenAI completion API.\",\n advanced=True,\n value=700,\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\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 = self.json_mode\n seed = self.seed\n max_retries = self.max_retries\n timeout = self.timeout\n\n api_key = SecretStr(openai_api_key).get_secret_value() 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 max_retries=max_retries,\n request_timeout=timeout,\n )\n if json_mode:\n output = output.bind(response_format={\"type\": \"json_object\"})\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"Get a message from an OpenAI exception.\n\n Args:\n e (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\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"
|
||||
},
|
||||
"input_value": {
|
||||
"_input_type": "MessageInput",
|
||||
|
|
@ -1997,9 +1902,7 @@
|
|||
"display_name": "Input",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2159,7 +2062,7 @@
|
|||
},
|
||||
"stream": {
|
||||
"_input_type": "BoolInput",
|
||||
"advanced": false,
|
||||
"advanced": true,
|
||||
"display_name": "Stream",
|
||||
"dynamic": false,
|
||||
"info": "Stream the response from the model. Streaming works only in Chat.",
|
||||
|
|
@ -2181,9 +2084,7 @@
|
|||
"display_name": "System Message",
|
||||
"dynamic": false,
|
||||
"info": "System message to pass to the model.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2201,7 +2102,7 @@
|
|||
},
|
||||
"temperature": {
|
||||
"_input_type": "SliderInput",
|
||||
"advanced": false,
|
||||
"advanced": true,
|
||||
"display_name": "Temperature",
|
||||
"dynamic": false,
|
||||
"info": "",
|
||||
|
|
@ -2295,9 +2196,7 @@
|
|||
"data": {
|
||||
"id": "ChatOutput-V5ZFA",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
|
|
@ -2333,9 +2232,7 @@
|
|||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
|
@ -2348,9 +2245,7 @@
|
|||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2371,9 +2266,7 @@
|
|||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2430,9 +2323,7 @@
|
|||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2453,11 +2344,7 @@
|
|||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Data",
|
||||
"DataFrame",
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Data", "DataFrame", "Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"name": "input_value",
|
||||
|
|
@ -2478,10 +2365,7 @@
|
|||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"options_metadata": [],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
|
|
@ -2498,9 +2382,7 @@
|
|||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2521,9 +2403,7 @@
|
|||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2562,9 +2442,7 @@
|
|||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2602,9 +2480,7 @@
|
|||
"data": {
|
||||
"id": "ChatOutput-8y94b",
|
||||
"node": {
|
||||
"base_classes": [
|
||||
"Message"
|
||||
],
|
||||
"base_classes": ["Message"],
|
||||
"beta": false,
|
||||
"conditional_paths": [],
|
||||
"custom_fields": {},
|
||||
|
|
@ -2640,9 +2516,7 @@
|
|||
"name": "message",
|
||||
"selected": "Message",
|
||||
"tool_mode": true,
|
||||
"types": [
|
||||
"Message"
|
||||
],
|
||||
"types": ["Message"],
|
||||
"value": "__UNDEFINED__"
|
||||
}
|
||||
],
|
||||
|
|
@ -2655,9 +2529,7 @@
|
|||
"display_name": "Background Color",
|
||||
"dynamic": false,
|
||||
"info": "The background color of the icon.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2678,9 +2550,7 @@
|
|||
"display_name": "Icon",
|
||||
"dynamic": false,
|
||||
"info": "The icon of the message.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2737,9 +2607,7 @@
|
|||
"display_name": "Data Template",
|
||||
"dynamic": false,
|
||||
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2760,11 +2628,7 @@
|
|||
"display_name": "Text",
|
||||
"dynamic": false,
|
||||
"info": "Message to be passed as output.",
|
||||
"input_types": [
|
||||
"Data",
|
||||
"DataFrame",
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Data", "DataFrame", "Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"name": "input_value",
|
||||
|
|
@ -2785,10 +2649,7 @@
|
|||
"dynamic": false,
|
||||
"info": "Type of sender.",
|
||||
"name": "sender",
|
||||
"options": [
|
||||
"Machine",
|
||||
"User"
|
||||
],
|
||||
"options": ["Machine", "User"],
|
||||
"options_metadata": [],
|
||||
"placeholder": "",
|
||||
"required": false,
|
||||
|
|
@ -2805,9 +2666,7 @@
|
|||
"display_name": "Sender Name",
|
||||
"dynamic": false,
|
||||
"info": "Name of the sender.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2828,9 +2687,7 @@
|
|||
"display_name": "Session ID",
|
||||
"dynamic": false,
|
||||
"info": "The session ID of the chat. If empty, the current session ID parameter will be used.",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2869,9 +2726,7 @@
|
|||
"display_name": "Text Color",
|
||||
"dynamic": false,
|
||||
"info": "The text color of the name",
|
||||
"input_types": [
|
||||
"Message"
|
||||
],
|
||||
"input_types": ["Message"],
|
||||
"list": false,
|
||||
"list_add_label": "Add More",
|
||||
"load_from_db": false,
|
||||
|
|
@ -2999,7 +2854,5 @@
|
|||
"is_component": false,
|
||||
"last_tested_version": "1.2.0",
|
||||
"name": "Text Sentiment Analysis",
|
||||
"tags": [
|
||||
"classification"
|
||||
]
|
||||
}
|
||||
"tags": ["classification"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import patch
|
||||
|
||||
import aiofiles
|
||||
import aiofiles.os
|
||||
|
|
@ -8,172 +7,306 @@ import httpx
|
|||
import pytest
|
||||
import respx
|
||||
from httpx import Response
|
||||
from langflow.components import data
|
||||
from langflow.components.data import APIRequestComponent
|
||||
from langflow.schema import Data, DataFrame, Message
|
||||
|
||||
from tests.base import ComponentTestBaseWithoutClient
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_request():
|
||||
# This fixture provides an instance of APIRequest for each test case
|
||||
return data.APIRequestComponent()
|
||||
class TestAPIRequestComponent(ComponentTestBaseWithoutClient):
|
||||
@pytest.fixture
|
||||
def component_class(self):
|
||||
"""Return the component class to test."""
|
||||
return APIRequestComponent
|
||||
|
||||
@pytest.fixture
|
||||
def default_kwargs(self):
|
||||
"""Return the default kwargs for the component."""
|
||||
return {
|
||||
"urls": ["https://example.com/api/test"],
|
||||
"method": "GET",
|
||||
"headers": [],
|
||||
"body": [],
|
||||
"timeout": 5,
|
||||
"follow_redirects": True,
|
||||
"save_to_file": False,
|
||||
"include_httpx_metadata": False,
|
||||
"use_curl": False,
|
||||
"curl": "",
|
||||
}
|
||||
|
||||
def test_parse_curl(api_request):
|
||||
# Arrange
|
||||
field_value = (
|
||||
"curl -X GET https://example.com/api/test -H 'Content-Type: application/json' -d '{\"key\": \"value\"}'"
|
||||
)
|
||||
build_config = {
|
||||
"method": {"value": ""},
|
||||
"urls": {"value": []},
|
||||
"headers": {},
|
||||
"body": {},
|
||||
}
|
||||
# Act
|
||||
new_build_config = api_request.parse_curl(field_value, build_config.copy())
|
||||
@pytest.fixture
|
||||
def file_names_mapping(self):
|
||||
"""Return an empty list since this component doesn't have version-specific files."""
|
||||
return []
|
||||
|
||||
# Assert
|
||||
assert new_build_config["method"]["value"] == "GET"
|
||||
assert new_build_config["urls"]["value"] == ["https://example.com/api/test"]
|
||||
assert new_build_config["headers"]["value"] == [{"key": "Content-Type", "value": "application/json"}]
|
||||
assert new_build_config["body"]["value"] == [{"key": "key", "value": "value"}]
|
||||
@pytest.fixture
|
||||
async def component(self, component_class, default_kwargs):
|
||||
"""Return a component instance."""
|
||||
return component_class(**default_kwargs)
|
||||
|
||||
async def test_parse_curl(self, component):
|
||||
# Test basic curl command parsing
|
||||
curl_cmd = (
|
||||
"curl -X GET https://example.com/api/test -H 'Content-Type: application/json' -d '{\"key\": \"value\"}'"
|
||||
)
|
||||
build_config = {
|
||||
"method": {"value": ""},
|
||||
"urls": {"value": []},
|
||||
"headers": {},
|
||||
"body": {},
|
||||
}
|
||||
new_build_config = component.parse_curl(curl_cmd, build_config.copy())
|
||||
|
||||
# HTTPx Metadata testing
|
||||
@pytest.mark.parametrize(
|
||||
("include_metadata", "expected_properties"),
|
||||
[
|
||||
(False, {"source", "result"}),
|
||||
(True, {"source", "result", "headers", "status_code", "response_headers", "redirection_history"}),
|
||||
],
|
||||
)
|
||||
@respx.mock
|
||||
async def test_httpx_metadata_behavior(api_request, include_metadata, expected_properties):
|
||||
# Mocking a successful GET request with headers and a redirection
|
||||
url = "https://example.com/api/test"
|
||||
redirected_url = "https://example.com/api/redirect"
|
||||
response_content = {"key": "value"}
|
||||
respx.get(url).mock(return_value=Response(303, headers={"Location": redirected_url}))
|
||||
respx.get(redirected_url).mock(
|
||||
return_value=Response(200, json=response_content, headers={"Custom-Header": "HeaderValue"})
|
||||
)
|
||||
assert new_build_config["method"]["value"] == "GET"
|
||||
assert new_build_config["urls"]["value"] == ["https://example.com/api/test"]
|
||||
assert new_build_config["headers"]["value"] == [{"key": "Content-Type", "value": "application/json"}]
|
||||
assert new_build_config["body"]["value"] == [{"key": "key", "value": "value"}]
|
||||
|
||||
# Make the request
|
||||
result = await api_request.make_request(
|
||||
client=httpx.AsyncClient(),
|
||||
method="GET",
|
||||
url=url,
|
||||
save_to_file=False,
|
||||
include_httpx_metadata=include_metadata,
|
||||
)
|
||||
@respx.mock
|
||||
async def test_make_request_success(self, component):
|
||||
# Test successful request with JSON response
|
||||
url = "https://example.com/api/test"
|
||||
response_data = {"key": "value"}
|
||||
respx.get(url).mock(return_value=Response(200, json=response_data))
|
||||
|
||||
# Check returned metadata
|
||||
metadata = result.data
|
||||
assert set(metadata.keys()) == expected_properties, f"Unexpected properties: {set(metadata.keys())}"
|
||||
|
||||
if include_metadata:
|
||||
# Validate individual fields
|
||||
assert metadata["source"] == url
|
||||
assert metadata["headers"] is None
|
||||
assert metadata["status_code"] == 200
|
||||
assert metadata["response_headers"]["custom-header"] == "HeaderValue"
|
||||
|
||||
# Validate redirection history
|
||||
assert metadata["redirection_history"] == [{"url": redirected_url, "status_code": 303}], (
|
||||
"Redirection history is incorrect"
|
||||
result = await component.make_request(
|
||||
client=httpx.AsyncClient(),
|
||||
method="GET",
|
||||
url=url,
|
||||
)
|
||||
|
||||
# Validate result
|
||||
assert metadata["result"] == response_content, "Response content mismatch"
|
||||
assert isinstance(result, Data)
|
||||
assert result.data["source"] == url
|
||||
assert "key" in result.data
|
||||
assert result.data["key"] == "value"
|
||||
|
||||
@respx.mock
|
||||
async def test_make_request_with_metadata(self, component):
|
||||
# Test request with metadata included
|
||||
url = "https://example.com/api/test"
|
||||
headers = {"Custom-Header": "Value"}
|
||||
response_data = {"key": "value"}
|
||||
respx.get(url).mock(return_value=Response(200, json=response_data, headers=headers))
|
||||
|
||||
# Save to File testing
|
||||
@pytest.mark.parametrize(
|
||||
("save_to_file", "expected_properties"),
|
||||
[
|
||||
(False, {"source", "result"}),
|
||||
(True, {"source", "file_path"}),
|
||||
],
|
||||
)
|
||||
@respx.mock
|
||||
async def test_save_to_file_behavior(api_request, save_to_file, expected_properties):
|
||||
# Mocking a successful GET request with a response body
|
||||
url = "https://example.com/api/test"
|
||||
response_content = "Test response content"
|
||||
respx.get(url).mock(return_value=Response(200, content=response_content))
|
||||
result = await component.make_request(
|
||||
client=httpx.AsyncClient(),
|
||||
method="GET",
|
||||
url=url,
|
||||
include_httpx_metadata=True,
|
||||
)
|
||||
|
||||
# Make the request
|
||||
result = await api_request.make_request(
|
||||
client=httpx.AsyncClient(),
|
||||
method="GET",
|
||||
url=url,
|
||||
save_to_file=save_to_file,
|
||||
)
|
||||
assert isinstance(result, Data)
|
||||
assert result.data["source"] == url
|
||||
assert result.data["status_code"] == 200
|
||||
assert result.data["response_headers"]["custom-header"] == "Value"
|
||||
|
||||
# Check returned metadata
|
||||
metadata = result.data
|
||||
assert set(metadata.keys()) == expected_properties, (
|
||||
f"Unexpected properties: {set(metadata.keys())}. Raw result: {result.data}"
|
||||
)
|
||||
@respx.mock
|
||||
async def test_make_request_save_to_file(self, component):
|
||||
# Test saving response to file
|
||||
url = "https://example.com/api/test"
|
||||
content = "Test content"
|
||||
respx.get(url).mock(return_value=Response(200, text=content))
|
||||
|
||||
if save_to_file:
|
||||
# Validate that file_path exists in metadata
|
||||
assert "file_path" in metadata, "file_path is missing in metadata"
|
||||
file_path = metadata["file_path"]
|
||||
result = await component.make_request(
|
||||
client=httpx.AsyncClient(),
|
||||
method="GET",
|
||||
url=url,
|
||||
save_to_file=True,
|
||||
)
|
||||
|
||||
# Validate that the file exists and its content matches the response
|
||||
assert await aiofiles.os.path.exists(file_path), "Saved file does not exist"
|
||||
assert isinstance(result, Data)
|
||||
assert "file_path" in result.data
|
||||
file_path = Path(result.data["file_path"])
|
||||
|
||||
# Use async file operations
|
||||
assert await aiofiles.os.path.exists(file_path)
|
||||
async with aiofiles.open(file_path) as f:
|
||||
file_content = await f.read()
|
||||
assert file_content == response_content, "File content does not match response content"
|
||||
saved_content = await f.read()
|
||||
assert saved_content == content
|
||||
|
||||
# Cleanup the file
|
||||
# Cleanup using async operation
|
||||
await aiofiles.os.remove(file_path)
|
||||
else:
|
||||
# Validate that result exists in metadata
|
||||
assert "result" in metadata, "result is missing in metadata"
|
||||
assert metadata["result"] == response_content.encode("utf-8"), "Response content mismatch in metadata"
|
||||
|
||||
@respx.mock
|
||||
async def test_make_request_binary_response(self, component):
|
||||
# Test handling binary response
|
||||
url = "https://example.com/api/binary"
|
||||
binary_content = b"Binary content"
|
||||
headers = {"Content-Type": "application/octet-stream"}
|
||||
respx.get(url).mock(return_value=Response(200, content=binary_content, headers=headers))
|
||||
|
||||
async def test_response_info_binary_content(api_request):
|
||||
response = Mock()
|
||||
response.headers = {"Content-Type": "application/octet-stream"}
|
||||
is_binary, file_path = await api_request._response_info(response, with_file_path=False)
|
||||
assert is_binary is True
|
||||
assert file_path is None
|
||||
result = await component.make_request(
|
||||
client=httpx.AsyncClient(),
|
||||
method="GET",
|
||||
url=url,
|
||||
)
|
||||
|
||||
assert isinstance(result, Data)
|
||||
assert result.data["source"] == url
|
||||
assert result.data["data"] == binary_content
|
||||
|
||||
async def test_response_info_non_binary_content(api_request):
|
||||
response = Mock()
|
||||
response.headers = {"Content-Type": "text/plain"}
|
||||
is_binary, file_path = await api_request._response_info(response, with_file_path=False)
|
||||
assert is_binary is False
|
||||
assert file_path is None
|
||||
@respx.mock
|
||||
async def test_make_request_timeout(self, component):
|
||||
# Test request timeout
|
||||
url = "https://example.com/api/test"
|
||||
respx.get(url).mock(side_effect=httpx.TimeoutException("Request timed out"))
|
||||
|
||||
result = await component.make_request(
|
||||
client=httpx.AsyncClient(),
|
||||
method="GET",
|
||||
url=url,
|
||||
timeout=1,
|
||||
)
|
||||
|
||||
async def test_response_info_filename_from_content_disposition(api_request):
|
||||
response = Mock()
|
||||
response.headers = {
|
||||
"Content-Disposition": 'attachment; filename="thisfile.txt"',
|
||||
"Content-Type": "text/plain",
|
||||
}
|
||||
response.request = Mock()
|
||||
response.request.url = "https://example.com/testfile"
|
||||
assert isinstance(result, Data)
|
||||
assert result.data["status_code"] == 408
|
||||
assert result.data["error"] == "Request timed out"
|
||||
|
||||
is_binary, file_path = await api_request._response_info(response, with_file_path=True)
|
||||
@respx.mock
|
||||
async def test_make_request_with_redirects(self, component):
|
||||
# Test handling redirects
|
||||
url = "https://example.com/api/test"
|
||||
redirect_url = "https://example.com/api/redirect"
|
||||
final_data = {"key": "value"}
|
||||
|
||||
assert is_binary is False
|
||||
assert file_path.parent == Path(tempfile.gettempdir()) / "APIRequestComponent"
|
||||
assert file_path.name.endswith("thisfile.txt")
|
||||
respx.get(url).mock(return_value=Response(303, headers={"Location": redirect_url}))
|
||||
respx.get(redirect_url).mock(return_value=Response(200, json=final_data))
|
||||
|
||||
result = await component.make_request(
|
||||
client=httpx.AsyncClient(),
|
||||
method="GET",
|
||||
url=url,
|
||||
include_httpx_metadata=True,
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
async def test_response_info_default_filename(api_request):
|
||||
response = Mock()
|
||||
response.headers = {"Content-Type": "text/plain"}
|
||||
response.request = Mock()
|
||||
response.request.url = "https://example.com/testfile"
|
||||
assert isinstance(result, Data)
|
||||
assert result.data["source"] == url
|
||||
assert result.data["status_code"] == 200
|
||||
assert result.data["redirection_history"] == [{"url": redirect_url, "status_code": 303}]
|
||||
|
||||
is_binary, file_path = await api_request._response_info(response, with_file_path=True)
|
||||
async def test_process_headers(self, component):
|
||||
# Test header processing
|
||||
headers_list = [
|
||||
{"key": "Content-Type", "value": "application/json"},
|
||||
{"key": "Authorization", "value": "Bearer token"},
|
||||
]
|
||||
processed = component._process_headers(headers_list)
|
||||
assert processed == {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer token",
|
||||
}
|
||||
|
||||
assert is_binary is False
|
||||
assert file_path.parent == Path(tempfile.gettempdir()) / "APIRequestComponent"
|
||||
assert file_path.name.endswith("testfile.txt")
|
||||
# Test invalid headers
|
||||
assert component._process_headers(None) == {}
|
||||
assert component._process_headers([{"invalid": "format"}]) == {}
|
||||
|
||||
async def test_process_body(self, component):
|
||||
# Test body processing
|
||||
# Test dictionary body
|
||||
dict_body = {"key": "value", "nested": {"inner": "value"}}
|
||||
assert component._process_body(dict_body) == dict_body
|
||||
|
||||
# Test string body
|
||||
json_str = '{"key": "value"}'
|
||||
assert component._process_body(json_str) == {"key": "value"}
|
||||
|
||||
# Test list body
|
||||
list_body = [{"key": "key1", "value": "value1"}, {"key": "key2", "value": "value2"}]
|
||||
assert component._process_body(list_body) == {"key1": "value1", "key2": "value2"}
|
||||
|
||||
# Test invalid body
|
||||
assert component._process_body(None) == {}
|
||||
assert component._process_body([{"invalid": "format"}]) == {}
|
||||
|
||||
async def test_add_query_params(self, component):
|
||||
# Test query parameter handling
|
||||
url = "https://example.com/api/test"
|
||||
params = {"param1": "value1", "param2": "value2"}
|
||||
result = component.add_query_params(url, params)
|
||||
assert "param1=value1" in result
|
||||
assert "param2=value2" in result
|
||||
|
||||
# Test with existing query params
|
||||
url_with_params = "https://example.com/api/test?existing=true"
|
||||
result = component.add_query_params(url_with_params, params)
|
||||
assert "existing=true" in result
|
||||
assert "param1=value1" in result
|
||||
assert "param2=value2" in result
|
||||
|
||||
async def test_output_formats(self, component):
|
||||
# Test different output formats
|
||||
with patch.object(component, "make_requests") as mock_make_requests:
|
||||
mock_make_requests.return_value = [Data(data={"key": "value"})]
|
||||
|
||||
# Test DataFrame output
|
||||
df_result = await component.as_dataframe()
|
||||
assert isinstance(df_result, DataFrame)
|
||||
|
||||
# Test Message output
|
||||
msg_result = await component.as_message()
|
||||
assert isinstance(msg_result, Message)
|
||||
|
||||
# Test Data output
|
||||
data_result = await component.as_data()
|
||||
assert isinstance(data_result, Data)
|
||||
assert isinstance(data_result.data["output"], list)
|
||||
|
||||
async def test_invalid_urls(self, component):
|
||||
# Test invalid URL handling
|
||||
component.urls = ["not_a_valid_url"]
|
||||
with pytest.raises(ValueError, match="Invalid URLs provided"):
|
||||
await component.make_requests()
|
||||
|
||||
async def test_update_build_config(self, component):
|
||||
# Test build config updates
|
||||
build_config = {
|
||||
"method": {"value": "GET", "advanced": False},
|
||||
"urls": {"value": [], "advanced": False},
|
||||
"headers": {"value": [], "advanced": True},
|
||||
"body": {"value": [], "advanced": True},
|
||||
"use_curl": {"value": False, "advanced": False},
|
||||
"curl": {"value": "", "advanced": True},
|
||||
"timeout": {"value": 5, "advanced": True},
|
||||
"follow_redirects": {"value": True, "advanced": True},
|
||||
"save_to_file": {"value": False, "advanced": True},
|
||||
"include_httpx_metadata": {"value": False, "advanced": True},
|
||||
"query_params": {"value": {}, "advanced": True},
|
||||
}
|
||||
|
||||
# Test curl mode update
|
||||
updated = component.update_build_config(
|
||||
build_config=build_config.copy(), field_value=True, field_name="use_curl"
|
||||
)
|
||||
assert updated["curl"]["advanced"] is False
|
||||
assert updated["urls"]["advanced"] is True
|
||||
|
||||
# Test method update
|
||||
updated = component.update_build_config(
|
||||
build_config=build_config.copy(), field_value="POST", field_name="method"
|
||||
)
|
||||
assert updated["body"]["advanced"] is False
|
||||
|
||||
@respx.mock
|
||||
async def test_error_handling(self, component):
|
||||
# Test various error scenarios
|
||||
url = "https://example.com/api/test"
|
||||
|
||||
# Test connection error
|
||||
respx.get(url).mock(side_effect=httpx.ConnectError("Connection failed"))
|
||||
result = await component.make_request(
|
||||
client=httpx.AsyncClient(),
|
||||
method="GET",
|
||||
url=url,
|
||||
)
|
||||
assert result.data["status_code"] == 500
|
||||
assert "Connection failed" in result.data["error"]
|
||||
|
||||
# Test invalid method
|
||||
with pytest.raises(ValueError, match="Unsupported method"):
|
||||
await component.make_request(
|
||||
client=httpx.AsyncClient(),
|
||||
method="INVALID",
|
||||
url=url,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { ForwardedIconComponent } from "@/components/common/genericIconComponent
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs-button";
|
||||
import { useGetTypes } from "@/controllers/API/queries/flows/use-get-types";
|
||||
import BaseModal from "@/modals/baseModal";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import { useTypesStore } from "@/stores/typesStore";
|
||||
|
|
@ -53,6 +54,7 @@ export default function GlobalVariableModal({
|
|||
const { mutate: updateVariable } = usePatchGlobalVariables();
|
||||
const { data: globalVariables } = useGetGlobalVariables();
|
||||
const [availableFields, setAvailableFields] = useState<string[]>([]);
|
||||
useGetTypes({ checkCache: true, enabled: !!globalVariables });
|
||||
|
||||
useEffect(() => {
|
||||
if (globalVariables && componentFields.size > 0) {
|
||||
|
|
@ -66,6 +68,8 @@ export default function GlobalVariableModal({
|
|||
if (referenceField && fields.includes(referenceField)) {
|
||||
setFields([referenceField]);
|
||||
}
|
||||
} else {
|
||||
setAvailableFields(["System", "System Message", "System Prompt"]);
|
||||
}
|
||||
}, [globalVariables, componentFields, initialData]);
|
||||
|
||||
|
|
|
|||
|
|
@ -143,9 +143,9 @@ export default function PublishDropdown() {
|
|||
!hasIO ? "cursor-not-allowed" : "",
|
||||
"flex items-center",
|
||||
)}
|
||||
data-testid="shareable-playground"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
data-testid="shareable-playground"
|
||||
disabled={!hasIO || !isPublished}
|
||||
className="deploy-dropdown-item group flex-1"
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -56,14 +56,13 @@ const OptionBadge = ({
|
|||
className={cn("flex items-center gap-1 truncate", className)}
|
||||
>
|
||||
<div className="truncate">{option}</div>
|
||||
<div>
|
||||
<X
|
||||
className="h-3 w-3 cursor-pointer bg-transparent hover:text-destructive"
|
||||
onClick={(e) =>
|
||||
onRemove(e as unknown as React.MouseEvent<HTMLButtonElement>)
|
||||
}
|
||||
data-testid="remove-icon-badge"
|
||||
/>
|
||||
<div
|
||||
data-testid="remove-icon-badge"
|
||||
onClick={(e) =>
|
||||
onRemove(e as unknown as React.MouseEvent<HTMLButtonElement>)
|
||||
}
|
||||
>
|
||||
<X className="h-3 w-3 cursor-pointer bg-transparent hover:text-destructive" />
|
||||
</div>
|
||||
</Badge>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -246,27 +246,31 @@ export default function InputFileComponent({
|
|||
isList={isList}
|
||||
>
|
||||
{(selectedFiles.length === 0 || isList) && (
|
||||
<Button
|
||||
disabled={isDisabled}
|
||||
variant={selectedFiles.length !== 0 ? "ghost" : "default"}
|
||||
size={selectedFiles.length !== 0 ? "iconMd" : "default"}
|
||||
className={cn(
|
||||
selectedFiles.length !== 0 &&
|
||||
"hit-area-icon absolute -top-8 right-0",
|
||||
"font-semibold",
|
||||
)}
|
||||
data-testid="button_open_file_management"
|
||||
>
|
||||
{selectedFiles.length !== 0 ? (
|
||||
<ForwardedIconComponent
|
||||
name="Plus"
|
||||
className="icon-size"
|
||||
strokeWidth={ICON_STROKE_WIDTH}
|
||||
/>
|
||||
) : (
|
||||
<div>Select file{isList ? "s" : ""}</div>
|
||||
)}
|
||||
</Button>
|
||||
<div data-testid="input-file-component">
|
||||
<Button
|
||||
disabled={isDisabled}
|
||||
variant={
|
||||
selectedFiles.length !== 0 ? "ghost" : "default"
|
||||
}
|
||||
size={selectedFiles.length !== 0 ? "iconMd" : "default"}
|
||||
className={cn(
|
||||
selectedFiles.length !== 0 &&
|
||||
"hit-area-icon absolute -top-8 right-0",
|
||||
"font-semibold",
|
||||
)}
|
||||
data-testid="button_open_file_management"
|
||||
>
|
||||
{selectedFiles.length !== 0 ? (
|
||||
<ForwardedIconComponent
|
||||
name="Plus"
|
||||
className="icon-size"
|
||||
strokeWidth={ICON_STROKE_WIDTH}
|
||||
/>
|
||||
) : (
|
||||
<div>Select file{isList ? "s" : ""}</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</FileManagerModal>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,18 +5,28 @@ import { api } from "../../api";
|
|||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
export const useGetTypes: useQueryFunctionType<undefined> = (options) => {
|
||||
export const useGetTypes: useQueryFunctionType<
|
||||
undefined,
|
||||
any,
|
||||
{ checkCache?: boolean }
|
||||
> = (options) => {
|
||||
const { query } = UseRequestProcessor();
|
||||
const setLoading = useFlowsManagerStore((state) => state.setIsLoading);
|
||||
const setTypes = useTypesStore((state) => state.setTypes);
|
||||
|
||||
const getTypesFn = async () => {
|
||||
const getTypesFn = async (checkCache = false) => {
|
||||
try {
|
||||
if (checkCache) {
|
||||
const data = useTypesStore.getState().types;
|
||||
if (data && Object.keys(data).length > 0) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await api.get<APIObjectType>(
|
||||
`${getURL("ALL")}?force_refresh=true`,
|
||||
);
|
||||
const data = response?.data;
|
||||
console.log("[Types] Got types data:", data);
|
||||
setTypes(data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
|
|
@ -26,10 +36,14 @@ export const useGetTypes: useQueryFunctionType<undefined> = (options) => {
|
|||
}
|
||||
};
|
||||
|
||||
const queryResult = query(["useGetTypes"], getTypesFn, {
|
||||
refetchOnWindowFocus: false,
|
||||
...options,
|
||||
});
|
||||
const queryResult = query(
|
||||
["useGetTypes"],
|
||||
() => getTypesFn(options?.checkCache),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
...options,
|
||||
},
|
||||
);
|
||||
|
||||
return queryResult;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { useGetAutoLogin } from "@/controllers/API/queries/auth";
|
||||
import { useGetConfig } from "@/controllers/API/queries/config/use-get-config";
|
||||
import { useGetBasicExamplesQuery } from "@/controllers/API/queries/flows/use-get-basic-examples";
|
||||
import { useGetTypes } from "@/controllers/API/queries/flows/use-get-types";
|
||||
import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
|
||||
import { useGetTagsQuery } from "@/controllers/API/queries/store";
|
||||
import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
|
||||
|
|
@ -15,7 +14,6 @@ import { Outlet } from "react-router-dom";
|
|||
import { LoadingPage } from "../LoadingPage";
|
||||
|
||||
export function AppInitPage() {
|
||||
const dark = useDarkStore((state) => state.dark);
|
||||
const refreshStars = useDarkStore((state) => state.refreshStars);
|
||||
const isLoading = useFlowsManagerStore((state) => state.isLoading);
|
||||
|
||||
|
|
|
|||
|
|
@ -573,11 +573,11 @@ export default function Page({
|
|||
deleteKeyCode={[]}
|
||||
fitView={isEmptyFlow.current ? false : true}
|
||||
fitViewOptions={{
|
||||
minZoom: 0.4,
|
||||
maxZoom: 1.25,
|
||||
minZoom: 0.2,
|
||||
maxZoom: 8,
|
||||
}}
|
||||
className="theme-attribution"
|
||||
minZoom={0.4}
|
||||
minZoom={0.2}
|
||||
maxZoom={8}
|
||||
zoomOnScroll={!view}
|
||||
zoomOnPinch={!view}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@ import { FlowSidebarComponent } from "./components/flowSidebarComponent";
|
|||
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
|
||||
const types = useTypesStore((state) => state.types);
|
||||
|
||||
const { isFetched: typesLoaded } = useGetTypes({
|
||||
useGetTypes({
|
||||
enabled: Object.keys(types).length <= 0,
|
||||
});
|
||||
|
||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||
const currentFlow = useFlowStore((state) => state.currentFlow);
|
||||
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
|
|
|
|||
|
|
@ -28,13 +28,17 @@ export const EmptyPage = ({ setOpenModal }: EmptyPageProps) => {
|
|||
>
|
||||
{folders?.length > 1 ? "Empty folder" : "Start building"}
|
||||
</h3>
|
||||
<p className="pb-5 text-sm text-secondary-foreground">
|
||||
<p
|
||||
data-testid="empty-folder-description"
|
||||
className="pb-5 text-sm text-secondary-foreground"
|
||||
>
|
||||
Begin with a template, or start from scratch.
|
||||
</p>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => setOpenModal(true)}
|
||||
id="new-project-btn"
|
||||
data-testid="new_project_btn_empty_page"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Plus"
|
||||
|
|
|
|||
|
|
@ -245,13 +245,17 @@ export type ResponseErrorTypeAPI = {
|
|||
export type ResponseErrorDetailAPI = {
|
||||
response: { data: { detail: string } };
|
||||
};
|
||||
export type useQueryFunctionType<T = undefined, R = any> = T extends undefined
|
||||
export type useQueryFunctionType<
|
||||
T = undefined,
|
||||
R = any,
|
||||
O = {},
|
||||
> = T extends undefined
|
||||
? (
|
||||
options?: Omit<UseQueryOptions, "queryFn" | "queryKey">,
|
||||
options?: Omit<UseQueryOptions, "queryFn" | "queryKey"> & O,
|
||||
) => UseQueryResult<R>
|
||||
: (
|
||||
params: T,
|
||||
options?: Omit<UseQueryOptions, "queryFn" | "queryKey">,
|
||||
options?: Omit<UseQueryOptions, "queryFn" | "queryKey"> & O,
|
||||
) => UseQueryResult<R>;
|
||||
|
||||
export type QueryFunctionType = (
|
||||
|
|
|
|||
|
|
@ -6,20 +6,36 @@ test(
|
|||
"user should be able to publish a flow",
|
||||
{ tag: ["@release", "@workspace", "@api"] },
|
||||
async ({ page, context }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
|
||||
await awaitBootstrapTest(page);
|
||||
|
||||
await page.waitForSelector('[data-testid="blank-flow"]', {
|
||||
timeout: 3000,
|
||||
timeout: 5000,
|
||||
});
|
||||
const flowId = page.url().split("/").pop();
|
||||
|
||||
let flowId = "";
|
||||
let retries = 0;
|
||||
const maxRetries = 3;
|
||||
|
||||
while (flowId.length === 0 && retries < maxRetries) {
|
||||
const url = page.url();
|
||||
flowId = url.split("/").pop() || "";
|
||||
|
||||
if (flowId.length === 0) {
|
||||
console.log(
|
||||
`Empty flowId detected (attempt ${retries + 1}/${maxRetries}), waiting and retrying...`,
|
||||
);
|
||||
await page.waitForTimeout(1000);
|
||||
retries++;
|
||||
}
|
||||
}
|
||||
|
||||
expect(flowId).toBeDefined();
|
||||
expect(flowId).not.toBeNull();
|
||||
expect(flowId!.length).toBeGreaterThan(0);
|
||||
expect(flowId.length).toBeGreaterThan(0);
|
||||
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.waitForSelector('[data-testid="sidebar-search-input"]', {
|
||||
timeout: 3000,
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
await page.getByTestId("sidebar-search-input").click();
|
||||
|
|
@ -35,20 +51,35 @@ test(
|
|||
await page.getByTestId("add-component-button-chat-input").click();
|
||||
});
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await adjustScreenView(page);
|
||||
await page.getByTestId("publish-button").click();
|
||||
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await page.waitForSelector('[data-testid="shareable-playground"]', {
|
||||
timeout: 3000,
|
||||
timeout: 10000,
|
||||
});
|
||||
await expect(
|
||||
page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes(flowId!) && response.status() === 200,
|
||||
),
|
||||
).resolves.toBeTruthy();
|
||||
|
||||
try {
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await expect(page.getByTestId("publish-switch")).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error waiting for publish operation:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("publish-switch").click();
|
||||
const pagePromise = context.waitForEvent("page");
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("shareable-playground").click();
|
||||
const newPage = await pagePromise;
|
||||
await newPage.waitForTimeout(3000);
|
||||
|
|
@ -61,7 +92,6 @@ test(
|
|||
|
||||
await newPage.close();
|
||||
await page.bringToFront();
|
||||
// check if deactivate the publishworks
|
||||
await page.waitForTimeout(500);
|
||||
await page.getByTestId("publish-button").click();
|
||||
await page.waitForTimeout(500);
|
||||
|
|
@ -73,6 +103,7 @@ test(
|
|||
});
|
||||
await expect(page.getByTestId("rf__wrapper")).toBeVisible();
|
||||
await page.goto(newUrl);
|
||||
await page.waitForTimeout(2000);
|
||||
try {
|
||||
await expect(page.getByTestId("mainpage_title")).toBeVisible({
|
||||
timeout: 10000,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ withEventDeliveryModes(
|
|||
"Simple Agent",
|
||||
{ tag: ["@release", "@starter-projects"] },
|
||||
async ({ page }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
test.skip(
|
||||
!process?.env?.OPENAI_API_KEY,
|
||||
|
||||
|
|
@ -26,19 +25,21 @@ withEventDeliveryModes(
|
|||
await page.getByRole("heading", { name: "Simple Agent" }).first().click();
|
||||
await initialGPTsetup(page);
|
||||
|
||||
await page.getByTestId("textarea_str_input_value").first().fill("Hello");
|
||||
|
||||
await page.getByTestId("button_run_chat output").last().click();
|
||||
|
||||
await page.waitForSelector("text=built successfully", {
|
||||
timeout: 10000 * 60 * 3,
|
||||
});
|
||||
|
||||
await page.getByTestId("playground-btn-flow-io").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="button-send"]', {
|
||||
timeout: 3000,
|
||||
});
|
||||
await page
|
||||
.getByTestId("input-chat-playground")
|
||||
.last()
|
||||
.fill("Hello, tell me about Langflow.");
|
||||
|
||||
await page.getByTestId("button-send").last().click();
|
||||
|
||||
const stopButton = page.getByRole("button", { name: "Stop" });
|
||||
await stopButton.waitFor({ state: "visible", timeout: 30000 });
|
||||
|
||||
if (await stopButton.isVisible()) {
|
||||
await expect(stopButton).toBeHidden({ timeout: 120000 });
|
||||
}
|
||||
|
||||
const textContents = await page.getByTestId("div-chat-message").innerText();
|
||||
|
||||
|
|
@ -46,6 +47,6 @@ withEventDeliveryModes(
|
|||
expect(await page.getByTestId("duration-display").last().isVisible());
|
||||
expect(await page.getByTestId("icon-check").nth(0).isVisible());
|
||||
expect(await page.getByTestId("icon-Check").nth(0).isVisible());
|
||||
expect(textContents.length).toBeGreaterThan(10);
|
||||
expect(textContents.length).toBeGreaterThan(30);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,12 +5,58 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
|||
import { initialGPTsetup } from "../../utils/initialGPTsetup";
|
||||
import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes";
|
||||
|
||||
function getRandomSocialMediaQuery(): string {
|
||||
const companies = [
|
||||
"OpenAI",
|
||||
"Microsoft",
|
||||
"Google",
|
||||
"Tesla",
|
||||
"Netflix",
|
||||
"Spotify",
|
||||
"Adobe",
|
||||
"Amazon",
|
||||
"Meta",
|
||||
"Apple",
|
||||
];
|
||||
|
||||
const platforms = [
|
||||
"TikTok",
|
||||
"Instagram",
|
||||
"Twitter",
|
||||
"LinkedIn",
|
||||
"YouTube",
|
||||
"Facebook",
|
||||
];
|
||||
|
||||
const contentTypes = [
|
||||
"latest video",
|
||||
"recent post",
|
||||
"profile bio",
|
||||
"latest update",
|
||||
"recent activity",
|
||||
];
|
||||
|
||||
const randomCompany = companies[Math.floor(Math.random() * companies.length)];
|
||||
const randomPlatform =
|
||||
platforms[Math.floor(Math.random() * platforms.length)];
|
||||
const randomContent1 =
|
||||
contentTypes[Math.floor(Math.random() * contentTypes.length)];
|
||||
let randomContent2 =
|
||||
contentTypes[Math.floor(Math.random() * contentTypes.length)];
|
||||
|
||||
// Make sure we don't get the same content type twice
|
||||
while (randomContent1 === randomContent2) {
|
||||
randomContent2 =
|
||||
contentTypes[Math.floor(Math.random() * contentTypes.length)];
|
||||
}
|
||||
|
||||
return `Find the ${randomPlatform} profile of the company ${randomCompany} using Google search, then show me the ${randomContent1} and their ${randomContent2}.`;
|
||||
}
|
||||
|
||||
withEventDeliveryModes(
|
||||
"Social Media Agent",
|
||||
{ tag: ["@release", "@starter-projects"] },
|
||||
async ({ page }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
|
||||
test.skip(
|
||||
!process?.env?.APIFY_API_TOKEN,
|
||||
"APIFY_API_TOKEN required to run this test",
|
||||
|
|
@ -48,9 +94,7 @@ withEventDeliveryModes(
|
|||
await page
|
||||
.getByTestId("input-chat-playground")
|
||||
.last()
|
||||
.fill(
|
||||
"Find the TikTok profile of the company OpenAI using Google search, then show me the profile bio and their latest video.",
|
||||
);
|
||||
.fill(getRandomSocialMediaQuery());
|
||||
|
||||
await page.getByTestId("button-send").last().click();
|
||||
|
||||
|
|
@ -65,7 +109,7 @@ withEventDeliveryModes(
|
|||
.getByTestId("div-chat-message")
|
||||
.last()
|
||||
.innerText();
|
||||
expect(output).toContain("TikTok");
|
||||
expect(output.length).toBeGreaterThan(150);
|
||||
|
||||
expect(output.length).toBeGreaterThan(100);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -26,13 +26,18 @@ withEventDeliveryModes(
|
|||
.click();
|
||||
await initialGPTsetup(page);
|
||||
|
||||
await page.getByTestId("input-file-component").last().click();
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.getByTestId("button_upload_file").click();
|
||||
await page.getByTestId("drag-files-component").last().click();
|
||||
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(
|
||||
path.join(__dirname, "../../assets/test_file.txt"),
|
||||
);
|
||||
await page.getByText("test_file.txt").isVisible();
|
||||
await page.getByText("test_file.txt").last().isVisible();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.getByTestId("select-files-modal-button").click();
|
||||
|
||||
await page.waitForSelector('[data-testid="button_run_chat output"]', {
|
||||
timeout: 3000,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,17 @@ import { expect, test } from "@playwright/test";
|
|||
import path from "path";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
import { extractAndCleanCode } from "../../utils/extract-and-clean-code";
|
||||
import { initialGPTsetup } from "../../utils/initialGPTsetup";
|
||||
import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes";
|
||||
|
||||
// Add this line to declare Node.js global variables
|
||||
declare const process: any;
|
||||
declare const __dirname: string;
|
||||
|
||||
withEventDeliveryModes(
|
||||
"Vector Store RAG",
|
||||
{ tag: ["@release", "@starter-projects"] },
|
||||
{ tag: ["@release", "@starter-projects", "@development"] },
|
||||
async ({ page }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
test.skip(
|
||||
!process?.env?.OPENAI_API_KEY,
|
||||
"OPENAI_API_KEY required to run this test",
|
||||
|
|
@ -26,24 +30,13 @@ withEventDeliveryModes(
|
|||
.first()
|
||||
.click();
|
||||
await page.waitForSelector('[title="fit view"]', {
|
||||
timeout: 100000,
|
||||
timeout: 20000,
|
||||
});
|
||||
await page.getByTitle("fit view").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
await page.getByTitle("zoom out").click();
|
||||
let outdatedComponents = await page
|
||||
.getByTestId("icon-AlertTriangle")
|
||||
.count();
|
||||
while (outdatedComponents > 0) {
|
||||
await page.getByTestId("icon-AlertTriangle").first().click();
|
||||
outdatedComponents = await page.getByTestId("icon-AlertTriangle").count();
|
||||
}
|
||||
let filledApiKey = await page.getByTestId("remove-icon-badge").count();
|
||||
while (filledApiKey > 0) {
|
||||
await page.getByTestId("remove-icon-badge").first().click();
|
||||
filledApiKey = await page.getByTestId("remove-icon-badge").count();
|
||||
}
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
if (process?.env?.ASTRA_DB_API_ENDPOINT?.includes("astra-dev")) {
|
||||
await page.getByTestId("title-Astra DB").first().click();
|
||||
await page.getByTestId("code-button-modal").click();
|
||||
|
|
@ -72,85 +65,206 @@ withEventDeliveryModes(
|
|||
await page.locator("textarea").last().fill(cleanCode);
|
||||
await page.locator('//*[@id="checkAndSaveBtn"]').click();
|
||||
}
|
||||
const apiKeyInput = page.getByTestId("popover-anchor-input-api_key");
|
||||
const isApiKeyInputVisible = await apiKeyInput.isVisible();
|
||||
if (isApiKeyInputVisible) {
|
||||
await apiKeyInput.fill(process.env.OPENAI_API_KEY ?? "");
|
||||
}
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-api_key") // input ID without "anchor-"
|
||||
.nth(0)
|
||||
.fill(process.env.OPENAI_API_KEY ?? "");
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-openai_api_key")
|
||||
.nth(1)
|
||||
.fill(process.env.OPENAI_API_KEY ?? "");
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-openai_api_key")
|
||||
.nth(0)
|
||||
.fill(process.env.OPENAI_API_KEY ?? "");
|
||||
|
||||
await page.waitForSelector('[data-testid="title-Astra DB"]', {
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.getByTestId("fit_view").click();
|
||||
|
||||
// Astra DB tokens
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-token")
|
||||
.nth(0)
|
||||
.fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
|
||||
|
||||
await page
|
||||
.locator('[data-testid="dropdown_str_database_name"]')
|
||||
.nth(0)
|
||||
.waitFor({
|
||||
timeout: 15000,
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
let databaseDropdownCount = await page
|
||||
.locator('[data-testid="dropdown_str_database_name"]')
|
||||
.nth(0)
|
||||
.count();
|
||||
|
||||
while (databaseDropdownCount === 0) {
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-token")
|
||||
.nth(0)
|
||||
.fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.locator('[data-testid="dropdown_str_database_name"]')
|
||||
.nth(0)
|
||||
.waitFor({
|
||||
timeout: 15000,
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
databaseDropdownCount = await page
|
||||
.locator('[data-testid="dropdown_str_database_name"]')
|
||||
.nth(0)
|
||||
.count();
|
||||
}
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("dropdown_str_database_name").nth(0).click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
let langflowCount = await page
|
||||
.locator('[data-testid="langflow-0-option"]')
|
||||
.count();
|
||||
|
||||
while (langflowCount === 0) {
|
||||
await page.waitForTimeout(1000);
|
||||
await page.getByTestId("icon-RefreshCcw").click();
|
||||
|
||||
await page.getByTestId("dropdown_str_database_name").nth(0).click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
langflowCount = await page
|
||||
.locator('[data-testid="langflow-0-option"]')
|
||||
.count();
|
||||
}
|
||||
|
||||
await page.locator('[data-testid="langflow-0-option"]').nth(0).waitFor({
|
||||
timeout: 15000,
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
await page.getByTestId("langflow-0-option").nth(0).click();
|
||||
|
||||
await page
|
||||
.locator('[data-testid="dropdown_str_collection_name"]')
|
||||
.nth(0)
|
||||
.waitFor({
|
||||
timeout: 15000,
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByTestId("dropdown_str_collection_name").nth(0).click();
|
||||
|
||||
await page.locator('[data-testid="fe_tests-0-option"]').nth(0).waitFor({
|
||||
timeout: 15000,
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
await page.getByTestId("fe_tests-0-option").nth(0).click();
|
||||
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-token")
|
||||
.nth(1)
|
||||
.fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
|
||||
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-collection_name_new")
|
||||
.first()
|
||||
.fill("fe_tests");
|
||||
.locator('[data-testid="dropdown_str_database_name"]')
|
||||
.nth(1)
|
||||
.waitFor({
|
||||
timeout: 15000,
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
databaseDropdownCount = await page
|
||||
.locator('[data-testid="dropdown_str_database_name"]')
|
||||
.nth(0)
|
||||
.count();
|
||||
|
||||
while (databaseDropdownCount === 0) {
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-token")
|
||||
.nth(0)
|
||||
.fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.locator('[data-testid="dropdown_str_database_name"]')
|
||||
.nth(1)
|
||||
.waitFor({
|
||||
timeout: 15000,
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
databaseDropdownCount = await page
|
||||
.locator('[data-testid="dropdown_str_database_name"]')
|
||||
.nth(0)
|
||||
.count();
|
||||
}
|
||||
|
||||
await page.getByTestId("dropdown_str_database_name").nth(1).click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
langflowCount = await page
|
||||
.locator('[data-testid="langflow-0-option"]')
|
||||
.count();
|
||||
|
||||
while (langflowCount === 0) {
|
||||
await page.waitForTimeout(1000);
|
||||
await page.getByTestId("icon-RefreshCcw").click();
|
||||
|
||||
const loadingOptions = page.getByText("Loading options...");
|
||||
await loadingOptions.waitFor({ state: "visible", timeout: 30000 });
|
||||
|
||||
if (await loadingOptions.isVisible()) {
|
||||
await expect(loadingOptions).toBeHidden({ timeout: 120000 });
|
||||
}
|
||||
|
||||
await page.getByTestId("dropdown_str_database_name").nth(1).click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
langflowCount = await page
|
||||
.locator('[data-testid="langflow-0-option"]')
|
||||
.count();
|
||||
}
|
||||
|
||||
await page.getByTestId("langflow-0-option").nth(0).click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-collection_name_new")
|
||||
.last()
|
||||
.fill("fe_tests");
|
||||
.locator('[data-testid="dropdown_str_collection_name"]')
|
||||
.nth(1)
|
||||
.waitFor({
|
||||
timeout: 15000 * 3,
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
await page.getByTestId("dropdown_str_collection_name").nth(1).click();
|
||||
|
||||
// Click first refresh button and wait for disabled->enabled transition
|
||||
await page.getByTestId("refresh-button-api_endpoint").first().click();
|
||||
await expect(
|
||||
page.getByTestId("refresh-button-api_endpoint").first(),
|
||||
).toHaveAttribute("disabled", "");
|
||||
await expect(
|
||||
page.getByTestId("refresh-button-api_endpoint").first(),
|
||||
).not.toHaveAttribute("disabled", "");
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Click second refresh button and wait for disabled->enabled transition
|
||||
await page.getByTestId("refresh-button-api_endpoint").last().click();
|
||||
await expect(
|
||||
page.getByTestId("refresh-button-api_endpoint").last(),
|
||||
).toHaveAttribute("disabled", "");
|
||||
await expect(
|
||||
page.getByTestId("refresh-button-api_endpoint").last(),
|
||||
).not.toHaveAttribute("disabled", "");
|
||||
|
||||
await page.getByTestId("dropdown_str_api_endpoint").first().click();
|
||||
|
||||
await page.waitForSelector('[data-testid="langflow-1-option"]', {
|
||||
timeout: 100000,
|
||||
await page.locator('[data-testid="fe_tests-0-option"]').nth(0).waitFor({
|
||||
timeout: 15000,
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
await page.getByTestId("langflow-1-option").last().click();
|
||||
await page.getByTestId("fe_tests-0-option").nth(0).click();
|
||||
|
||||
await page.getByTestId("dropdown_str_api_endpoint").last().click();
|
||||
|
||||
await page.waitForSelector('[data-testid="langflow-1-option"]', {
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
await page.getByTestId("langflow-1-option").last().click();
|
||||
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.getByTestId("input-file-component").last().click();
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.getByTestId("drag-files-component").last().click();
|
||||
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(
|
||||
path.join(__dirname, "../../assets/test_file.txt"),
|
||||
);
|
||||
await page.getByText("test_file.txt").isVisible();
|
||||
await page.getByText("test_file.txt").last().isVisible();
|
||||
await page.waitForTimeout(500);
|
||||
await page.getByTestId("select-files-modal-button").click();
|
||||
await page.getByTestId("button_run_astra db").last().click();
|
||||
await page.waitForSelector("text=built successfully", {
|
||||
timeout: 60000 * 2,
|
||||
|
|
@ -168,7 +282,7 @@ withEventDeliveryModes(
|
|||
|
||||
await page.getByText("Playground", { exact: true }).last().click();
|
||||
await page.waitForSelector('[data-testid="input-chat-playground"]', {
|
||||
timeout: 100000,
|
||||
timeout: 60000,
|
||||
});
|
||||
await page.getByTestId("input-chat-playground").last().fill("hello");
|
||||
await page.getByTestId("input-chat-playground").last().click();
|
||||
|
|
@ -192,8 +306,9 @@ withEventDeliveryModes(
|
|||
await page.getByRole("combobox").click();
|
||||
await page.getByLabel("Delete").click();
|
||||
await page.waitForSelector('[data-testid="input-chat-playground"]', {
|
||||
timeout: 100000,
|
||||
timeout: 60000,
|
||||
});
|
||||
await page.getByTestId("input-chat-playground").last().isVisible();
|
||||
},
|
||||
{ timeout: 60000 },
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { expect, test } from "@playwright/test";
|
|||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
|
||||
test("chat_io_teste", { tag: ["@release", "@workspace"] }, async ({ page }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
await awaitBootstrapTest(page);
|
||||
|
||||
await page.waitForSelector('[data-testid="blank-flow"]', {
|
||||
|
|
@ -54,9 +53,7 @@ test("chat_io_teste", { tag: ["@release", "@workspace"] }, async ({ page }) => {
|
|||
await page.getByTestId("input-chat-playground").click();
|
||||
await page.getByTestId("input-chat-playground").fill("teste");
|
||||
await page.getByTestId("button-send").first().click();
|
||||
const chat_input = await page
|
||||
.getByTestId("chat-message-User-teste")
|
||||
.textContent();
|
||||
const chat_input = await page.getByTestId("div-chat-message").textContent();
|
||||
|
||||
expect(chat_input).toBe("teste");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ test(
|
|||
await page.getByTestId("dropdown_str_model_id").click();
|
||||
await page.getByText("anthropic.claude-v2").last().click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
value = await page.getByTestId("dropdown_str_model_id").innerText();
|
||||
if (value !== "anthropic.claude-v2:1") {
|
||||
expect(false).toBeTruthy();
|
||||
|
|
@ -59,6 +61,8 @@ test(
|
|||
await page.getByTestId("more-options-modal").click();
|
||||
await page.getByTestId("advanced-button-modal").click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
value = await page
|
||||
.getByTestId("value-dropdown-dropdown_str_edit_model_id")
|
||||
.innerText();
|
||||
|
|
|
|||
|
|
@ -22,9 +22,18 @@ test(
|
|||
.getByTestId("modelsNVIDIA")
|
||||
.hover()
|
||||
.then(async () => {
|
||||
// Wait for the API request to complete after clicking the add button
|
||||
const responsePromise = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes("/api/v1/custom_component/update") &&
|
||||
response.status() === 200,
|
||||
);
|
||||
await page.getByTestId("add-component-button-nvidia").click();
|
||||
await responsePromise; // Wait for the request to complete
|
||||
});
|
||||
|
||||
//add
|
||||
|
||||
await page.getByTestId("title-NVIDIA").click();
|
||||
|
||||
await page.getByTestId("edit-button-modal").click();
|
||||
|
|
|
|||
|
|
@ -1,21 +1,26 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { addFlowToTestOnEmptyLangflow } from "../../utils/add-flow-to-test-on-empty-langflow";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
import { generateRandomFilename } from "../../utils/generate-filename";
|
||||
|
||||
// Function to generate random 10-character filename
|
||||
|
||||
// Configure tests to run serially with a delay between each test
|
||||
test(
|
||||
"should navigate to files page and show empty state",
|
||||
{ tag: ["@release", "@files"] },
|
||||
async ({ page }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
|
||||
await awaitBootstrapTest(page, { skipModal: true });
|
||||
|
||||
// Wait for the sidebar to be visible
|
||||
await page.waitForSelector('[data-testid="folder-sidebar"]', {
|
||||
const firstRunLangflow = await page
|
||||
.getByTestId("empty-folder-description")
|
||||
.count();
|
||||
|
||||
if (firstRunLangflow > 0) {
|
||||
await addFlowToTestOnEmptyLangflow(page);
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="mainpage_title"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
|
|
@ -46,18 +51,24 @@ test(
|
|||
"should upload file using upload button",
|
||||
{ tag: ["@release", "@files"] },
|
||||
async ({ page }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
|
||||
const fileName = generateRandomFilename();
|
||||
const testFilePath = path.join(__dirname, "../../assets/test-file.txt");
|
||||
const fileContent = fs.readFileSync(testFilePath);
|
||||
|
||||
await awaitBootstrapTest(page, { skipModal: true });
|
||||
|
||||
// Navigate to files page
|
||||
await page.waitForSelector('[data-testid="folder-sidebar"]', {
|
||||
const firstRunLangflow = await page
|
||||
.getByTestId("empty-folder-description")
|
||||
.count();
|
||||
|
||||
if (firstRunLangflow > 0) {
|
||||
await addFlowToTestOnEmptyLangflow(page);
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="mainpage_title"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByText("My Files").first().click();
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.getByTestId("upload-file-btn").click();
|
||||
|
|
@ -85,16 +96,22 @@ test(
|
|||
"should upload file using drag and drop",
|
||||
{ tag: ["@release", "@files"] },
|
||||
async ({ page }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
|
||||
const fileName = generateRandomFilename();
|
||||
|
||||
await awaitBootstrapTest(page, { skipModal: true });
|
||||
|
||||
// Navigate to files page
|
||||
await page.waitForSelector('[data-testid="folder-sidebar"]', {
|
||||
const firstRunLangflow = await page
|
||||
.getByTestId("empty-folder-description")
|
||||
.count();
|
||||
|
||||
if (firstRunLangflow > 0) {
|
||||
await addFlowToTestOnEmptyLangflow(page);
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="mainpage_title"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByText("My Files").first().click();
|
||||
|
||||
// Create DataTransfer object and file
|
||||
|
|
@ -135,8 +152,6 @@ test(
|
|||
"should upload multiple files with different types",
|
||||
{ tag: ["@release", "@files"] },
|
||||
async ({ page }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
|
||||
const fileNames = {
|
||||
txt: generateRandomFilename(),
|
||||
json: generateRandomFilename(),
|
||||
|
|
@ -153,10 +168,18 @@ test(
|
|||
|
||||
await awaitBootstrapTest(page, { skipModal: true });
|
||||
|
||||
// Navigate to files page
|
||||
await page.waitForSelector('[data-testid="folder-sidebar"]', {
|
||||
const firstRunLangflow = await page
|
||||
.getByTestId("empty-folder-description")
|
||||
.count();
|
||||
|
||||
if (firstRunLangflow > 0) {
|
||||
await addFlowToTestOnEmptyLangflow(page);
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="mainpage_title"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByText("My Files").first().click();
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.getByTestId("upload-file-btn").click();
|
||||
|
|
@ -201,8 +224,6 @@ test(
|
|||
"should search uploaded files",
|
||||
{ tag: ["@release", "@files"] },
|
||||
async ({ page }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
|
||||
const fileNames = {
|
||||
txt: generateRandomFilename(),
|
||||
json: generateRandomFilename(),
|
||||
|
|
@ -219,10 +240,18 @@ test(
|
|||
|
||||
await awaitBootstrapTest(page, { skipModal: true });
|
||||
|
||||
// Navigate to files page
|
||||
await page.waitForSelector('[data-testid="folder-sidebar"]', {
|
||||
const firstRunLangflow = await page
|
||||
.getByTestId("empty-folder-description")
|
||||
.count();
|
||||
|
||||
if (firstRunLangflow > 0) {
|
||||
await addFlowToTestOnEmptyLangflow(page);
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="mainpage_title"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByText("My Files").first().click();
|
||||
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||
await page.getByTestId("upload-file-btn").click();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 7000));
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 7000));
|
||||
});
|
||||
|
||||
test(
|
||||
"should see general profile gradient",
|
||||
{ tag: ["@release"] },
|
||||
|
|
@ -29,7 +37,6 @@ test(
|
|||
{ tag: ["@release", "@workspace", "@api"] },
|
||||
|
||||
async ({ page }) => {
|
||||
test.skip(); //@TODO understand this behavior
|
||||
const randomName = Math.random().toString(36).substring(2);
|
||||
const randomName2 = Math.random().toString(36).substring(2);
|
||||
const randomName3 = Math.random().toString(36).substring(2);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import { Page } from "playwright/test";
|
||||
|
||||
export const addFlowToTestOnEmptyLangflow = async (page: Page) => {
|
||||
await page.getByTestId("new_project_btn_empty_page").click();
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("icon-ChevronLeft").click();
|
||||
};
|
||||
|
|
@ -2,11 +2,22 @@ import { Page } from "playwright/test";
|
|||
|
||||
export async function addNewApiKeys(page: Page) {
|
||||
const apiKeyInput = page.getByTestId("popover-anchor-input-api_key");
|
||||
const openaiApiKeyInput = page.getByTestId(
|
||||
"popover-anchor-input-openai_api_key",
|
||||
);
|
||||
|
||||
const isApiKeyInputVisible = await apiKeyInput.count();
|
||||
const isOpenaiApiKeyInputVisible = await openaiApiKeyInput.count();
|
||||
|
||||
if (isApiKeyInputVisible > 0) {
|
||||
for (let i = 0; i < isApiKeyInputVisible; i++) {
|
||||
await apiKeyInput.nth(i).fill(process.env.OPENAI_API_KEY ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
if (isOpenaiApiKeyInputVisible > 0) {
|
||||
for (let i = 0; i < isOpenaiApiKeyInputVisible; i++) {
|
||||
await openaiApiKeyInput.nth(i).fill(process.env.OPENAI_API_KEY ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ import { Page } from "playwright/test";
|
|||
export async function removeOldApiKeys(page: Page) {
|
||||
let filledApiKey = await page.getByTestId("remove-icon-badge").count();
|
||||
while (filledApiKey > 0) {
|
||||
await page.getByTestId("remove-icon-badge").first().click();
|
||||
await page
|
||||
.getByTestId("remove-icon-badge")
|
||||
.nth(filledApiKey - 1)
|
||||
.click();
|
||||
await page.waitForTimeout(1000);
|
||||
filledApiKey = await page.getByTestId("remove-icon-badge").count();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ function getMimeType(extension: string): string {
|
|||
}
|
||||
|
||||
export async function uploadFile(page: Page, fileName: string) {
|
||||
await page.getByTestId("fit_view").click();
|
||||
const fileManagement = await page
|
||||
.getByTestId("button_open_file_management")
|
||||
?.isVisible();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ type TestConfig = Parameters<typeof test>[1];
|
|||
|
||||
/**
|
||||
* Wraps a test function to run it with both streaming and polling event delivery modes.
|
||||
* Adds a 3-second delay between test runs to ensure proper separation.
|
||||
*
|
||||
* @param title The test title
|
||||
* @param config The test configuration (tags, etc)
|
||||
|
|
@ -14,11 +15,16 @@ export function withEventDeliveryModes(
|
|||
title: string,
|
||||
config: TestConfig,
|
||||
testFn: TestFunction,
|
||||
{ timeout = 10000 }: { timeout?: number } = {},
|
||||
) {
|
||||
const eventDeliveryModes = ["streaming", "polling"] as const;
|
||||
|
||||
for (const eventDelivery of eventDeliveryModes) {
|
||||
for (const [index, eventDelivery] of eventDeliveryModes.entries()) {
|
||||
test(`${title} - ${eventDelivery}`, config, async ({ page }) => {
|
||||
if (index === 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, timeout));
|
||||
}
|
||||
|
||||
// Intercept the config request and modify the event_delivery setting
|
||||
await page.route("**/api/v1/config", async (route) => {
|
||||
const response = await route.fetch();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue