diff --git a/.github/changes-filter.yaml b/.github/changes-filter.yaml index 2a85290882..925f237460 100644 --- a/.github/changes-filter.yaml +++ b/.github/changes-filter.yaml @@ -28,6 +28,8 @@ starter-projects: - "src/frontend/src/pages/MainPage/**" - "src/frontend/src/utils/reactflowUtils.ts" - "src/frontend/tests/extended/features/**" + - "src/backend/base/langflow/custom/**" + - "src/backend/base/langflow/graph/**" components: - "src/frontend/src/components/**" @@ -38,7 +40,6 @@ components: - "src/frontend/src/CustomNodes/**" - "src/frontend/src/style/**" - "src/frontend/src/utils/styleUtils.ts" - - "src/backend/base/langflow/components/**" - "src/frontend/tests/core/features/**" - "src/frontend/tests/core/integrations/**" - "src/frontend/tests/core/regression/**" @@ -49,6 +50,8 @@ components: - "src/backend/base/langflow/schema/**" - "src/backend/base/langflow/graph/**" - "src/backend/base/langflow/utils/**" + - "src/backend/base/langflow/custom/**" + - "src/backend/base/langflow/components/**" workspace: - "src/backend/base/langflow/inputs/**" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca18aa0398..6c084e519f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,6 +101,12 @@ jobs: frontend-tests: ${{ steps.filter.outputs.frontend-tests }} components-changes: ${{ steps.filter.outputs.components-changes }} starter-projects-changes: ${{ steps.filter.outputs.starter-projects-changes }} + starter-projects: ${{ steps.filter.outputs.starter-projects }} + components: ${{ steps.filter.outputs.components }} + workspace: ${{ steps.filter.outputs.workspace }} + api: ${{ steps.filter.outputs.api }} + database: ${{ steps.filter.outputs.database }} + steps: - name: Checkout code uses: actions/checkout@v4 @@ -119,11 +125,10 @@ jobs: uses: ./.github/workflows/python_test.yml with: python-versions: ${{ inputs.python-versions || '["3.10"]' }} - test-frontend: needs: path-filter name: Run Frontend Tests - if: ${{ needs.path-filter.outputs.frontend == 'true' || needs.path-filter.outputs.frontend-tests == 'true' || needs.path-filter.outputs.components-changes == 'true' || needs.path-filter.outputs.starter-projects-changes == 'true' }} + if: ${{ needs.path-filter.outputs.frontend == 'true' || needs.path-filter.outputs.frontend-tests == 'true' || needs.path-filter.outputs.components-changes == 'true' || needs.path-filter.outputs.starter-projects-changes == 'true' || needs.path-filter.outputs.starter-projects == 'true' || needs.path-filter.outputs.components == 'true' || needs.path-filter.outputs.workspace == 'true' || needs.path-filter.outputs.api == 'true' || needs.path-filter.outputs.database == 'true' }} uses: ./.github/workflows/typescript_test.yml with: tests_folder: ${{ inputs.frontend-tests-folder }} diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index d0c76c7d80..f33fdd9e17 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -168,8 +168,8 @@ In the *Documentation Terminal* (from the project root directory), run the follo ```bash cd docs -npm install -npm run start +yarn install +yarn start ``` If the frontend service is running on port `3000` you might be prompted `Would you like to run the app on another port instead?`, in which case answer "yes". You will get output similar to: diff --git a/docs/css/custom.css b/docs/css/custom.css index 4f24ebc444..107fbc659f 100644 --- a/docs/css/custom.css +++ b/docs/css/custom.css @@ -205,32 +205,32 @@ body { height: auto; display: block; margin: 1rem auto; - max-height: 500px; + max-height: 600px; object-fit: contain; } /* Default size for most images */ .markdown img:not(.resized-image) { - max-width: 500px; + max-width: 600px; width: auto; } .resized-image { - width: 250px; - max-height: 400px; + width: 300px; + max-height: 500px; } /* Responsive images on mobile devices */ @media (max-width: 768px) { .markdown img:not(.resized-image) { max-width: 100%; - max-height: 400px; + max-height: 500px; } .resized-image { width: 100%; - max-width: 250px; - max-height: 300px; + max-width: 300px; + max-height: 400px; } } diff --git a/docs/docs/Components/components-data.md b/docs/docs/Components/components-data.md index 624eba31f4..d756fe31c5 100644 --- a/docs/docs/Components/components-data.md +++ b/docs/docs/Components/components-data.md @@ -210,7 +210,7 @@ curl -X POST \ To test the webhook component: 1. Add a **Webhook** component to the flow. -2. Connect the **Webhook** component's **Data** output to the **Data** input of a [Data to Message](/components-processing#parse-data-data-to-message) component. +2. Connect the **Webhook** component's **Data** output to the **Data** input of a [Data to Message](/components-processing#data-to-message) component. 3. Connect the **Data to Message** component's **Message** output to the **Text** input of a [Chat Output](/components-io#chat-output) component. 4. To send a POST request, copy the code from the **Webhook cURL** tab in the **API** pane and paste it into a terminal. 5. Send the POST request. diff --git a/docs/docs/Components/components-processing.md b/docs/docs/Components/components-processing.md index f3e26560a9..de1bf373dd 100644 --- a/docs/docs/Components/components-processing.md +++ b/docs/docs/Components/components-processing.md @@ -17,7 +17,7 @@ The component offers control over chunk size, overlap, and separator, which affe ## Alter metadata -This component modifies metadata of input objects. It can add new metadata, update existing metadata, and remove specified metadata fields. The component works with both [Message](/concepts-objects#message-object) and [Data](/concepts-objects) objects, and can also create a new Data object from user-provided text. +This component modifies metadata of input objects. It can add new metadata, update existing metadata, and remove specified metadata fields. The component works with both [Message](/concepts-objects#message-object) and [Data](/concepts-objects#data-object) objects, and can also create a new Data object from user-provided text. ### Inputs @@ -164,7 +164,7 @@ This component transforms structured data into human-readable text formats, allo This component is in **Beta** as of Langflow version 1.1.3, and is not yet fully supported. ::: -This component filters a [Data](/concepts-objects#data-objects) object based on a list of keys. +This component filters a [Data](/concepts-objects#data-object) object based on a list of keys. ### Inputs diff --git a/docs/docs/Get-Started/get-started-quickstart.md b/docs/docs/Get-Started/get-started-quickstart.md index d9dc7f0902..9b7a0b0254 100644 --- a/docs/docs/Get-Started/get-started-quickstart.md +++ b/docs/docs/Get-Started/get-started-quickstart.md @@ -123,7 +123,7 @@ The [File](/components-data#file) component loads files from your local machine. 3. Click **Processing**, select the **Split Text** component, and then drag it to the canvas. The [Split Text](/components-processing#split-text) component splits the loaded text into smaller chunks. 4. Click **Processing**, select the **Parse Data** component, and then drag it to the canvas. -The [Parse Data](/components-processing#parse-data) component converts the data from the **Astra DB** component into plain text. +The [Data to Message](/components-processing#data-to-message) component converts the data from the **Astra DB** component into plain text. 5. Click **Embeddings**, select the **OpenAI Embeddings** component, and then drag it to the canvas. The [OpenAI Embeddings](/components-embedding-models#openai-embeddings) component generates embeddings for the user's input, which are compared to the vector data in the database. 6. Connect the new components into the existing flow, so your flow looks like this: diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 6b4356edd5..4b91aff2f3 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -40,7 +40,7 @@ const config = { docs: { routeBasePath: "/", // Serve the docs at the site's root sidebarPath: require.resolve("./sidebars.js"), // Use sidebars.js file - sidebarCollapsed: true, + sidebarCollapsed: false, beforeDefaultRemarkPlugins: [ [ remarkCodeHike, @@ -258,7 +258,7 @@ const config = { }, docs: { sidebar: { - hideable: true, + hideable: false, }, }, }), diff --git a/docs/sidebars.js b/docs/sidebars.js index d3389625cc..f0705b1172 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -140,16 +140,16 @@ module.exports = { type: "category", label: "API reference", items: [ - { - type: "doc", - id: "API-Reference/api-reference-api-examples", - label: "API examples", - }, { type: "link", label: "API documentation", href: "/api", }, + { + type: "doc", + id: "API-Reference/api-reference-api-examples", + label: "API examples", + }, ], }, { diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index b43bef9f0d..1c502eaea8 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -981,17 +981,25 @@ class Component(CustomComponent): return {"repr": custom_repr, "raw": raw, "type": artifact_type} def _process_raw_result(self, result): + """Process the raw result of the component.""" if len(self.outputs) == 1: return self.status or self.extract_data(result) return self.extract_data(result) def extract_data(self, result): + """Extract the data from the result. this is where the self.status is set.""" + if isinstance(result, Message): + self.status = result.get_text() + return ( + self.status if self.status is not None else "No text available" + ) # Provide a default message if .text_key is missing if hasattr(result, "data"): return result.data if hasattr(result, "model_dump"): return result.model_dump() if isinstance(result, Data | dict | str): return result.data if isinstance(result, Data) else result + if self.status: return self.status return result diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 974a249355..557f0d373f 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -116,10 +116,10 @@ class Graph: self.top_level_vertices: list[str] = [] self.vertex_map: dict[str, Vertex] = {} - self.predecessor_map: dict[str, set[str]] = defaultdict(set) - self.successor_map: dict[str, set[str]] = defaultdict(set) + self.predecessor_map: dict[str, list[str]] = defaultdict(list) + self.successor_map: dict[str, list[str]] = defaultdict(list) self.in_degree_map: dict[str, int] = defaultdict(int) - self.parent_child_map: dict[str, set[str]] = defaultdict(set) + self.parent_child_map: dict[str, list[str]] = defaultdict(list) self._run_queue: deque[str] = deque() self._first_layer: list[str] = [] self._lock = asyncio.Lock() @@ -469,10 +469,10 @@ class Graph: self.add_edge(edge) source_id = edge["data"]["sourceHandle"]["id"] target_id = edge["data"]["targetHandle"]["id"] - self.predecessor_map[target_id].add(source_id) - self.successor_map[source_id].add(target_id) + self.predecessor_map[target_id].append(source_id) + self.successor_map[source_id].append(target_id) self.in_degree_map[target_id] += 1 - self.parent_child_map[source_id].add(target_id) + self.parent_child_map[source_id].append(target_id) def add_node(self, node: NodeData) -> None: self._vertices.append(node) @@ -1554,7 +1554,7 @@ class Graph: logger.debug("Graph processing complete") return self - def find_next_runnable_vertices(self, vertex_successors_ids: set[str]) -> list[str]: + def find_next_runnable_vertices(self, vertex_successors_ids: list[str]) -> list[str]: next_runnable_vertices = set() for v_id in sorted(vertex_successors_ids): if not self.is_vertex_runnable(v_id): @@ -2023,7 +2023,10 @@ class Graph: def build_in_degree(self, edges: list[CycleEdge]) -> dict[str, int]: in_degree: dict[str, int] = defaultdict(int) + for edge in edges: + # We don't need to count if a Component connects more than one + # time to the same vertex. in_degree[edge.target_id] += 1 for vertex in self.vertices: if vertex.id not in in_degree: @@ -2031,13 +2034,13 @@ class Graph: return in_degree @staticmethod - def build_adjacency_maps(edges: list[CycleEdge]) -> tuple[dict[str, set[str]], dict[str, set[str]]]: + def build_adjacency_maps(edges: list[CycleEdge]) -> tuple[dict[str, list[str]], dict[str, list[str]]]: """Returns the adjacency maps for the graph.""" - predecessor_map: dict[str, set[str]] = defaultdict(set) - successor_map: dict[str, set[str]] = defaultdict(set) + predecessor_map: dict[str, list[str]] = defaultdict(list) + successor_map: dict[str, list[str]] = defaultdict(list) for edge in edges: - predecessor_map[edge.target_id].add(edge.source_id) - successor_map[edge.source_id].add(edge.target_id) + predecessor_map[edge.target_id].append(edge.source_id) + successor_map[edge.source_id].append(edge.target_id) return predecessor_map, successor_map def __to_dict(self) -> dict[str, dict[str, list[str]]]: diff --git a/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py b/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py index 2b11d16fa9..92edbc70d5 100644 --- a/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py +++ b/src/backend/base/langflow/graph/graph/runnable_vertices_manager.py @@ -2,9 +2,9 @@ from collections import defaultdict class RunnableVerticesManager: - def __init__(self): + def __init__(self) -> None: self.run_map: dict[str, list[str]] = defaultdict(list) # Tracks successors of each vertex - self.run_predecessors: dict[str, set[str]] = defaultdict(set) # Tracks predecessors for each vertex + self.run_predecessors: dict[str, list[str]] = defaultdict(list) # Tracks predecessors for each vertex self.vertices_to_run: set[str] = set() # Set of vertices that are ready to run self.vertices_being_run: set[str] = set() # Set of vertices that are currently running self.cycle_vertices: set[str] = set() # Set of vertices that are in a cycle @@ -74,13 +74,14 @@ class RunnableVerticesManager: bool: True if all predecessor conditions are met, False otherwise """ # Get pending predecessors, return True if none exist - if not (pending := self.run_predecessors.get(vertex_id, set())): + pending = self.run_predecessors.get(vertex_id, []) + if not pending: return True # For cycle vertices, check if any pending predecessors are also in cycle # Using set intersection is faster than iteration if vertex_id in self.cycle_vertices: - return is_loop or not bool(pending.intersection(self.cycle_vertices)) + return is_loop or not bool(set(pending) & self.cycle_vertices) return False diff --git a/src/backend/base/langflow/graph/graph/utils.py b/src/backend/base/langflow/graph/graph/utils.py index d0a8ea174b..26650f3434 100644 --- a/src/backend/base/langflow/graph/graph/utils.py +++ b/src/backend/base/langflow/graph/graph/utils.py @@ -461,8 +461,8 @@ def find_cycle_vertices(edges): def layered_topological_sort( vertices_ids: set[str], in_degree_map: dict[str, int], - successor_map: dict[str, set[str]], - predecessor_map: dict[str, set[str]], + successor_map: dict[str, list[str]], + predecessor_map: dict[str, list[str]], start_id: str | None = None, cycle_vertices: set[str] | None = None, is_input_vertex: Callable[[str], bool] | None = None, # noqa: ARG001 @@ -780,8 +780,8 @@ def get_sorted_vertices( start_component_id: str | None = None, graph_dict: dict[str, Any] | None = None, in_degree_map: dict[str, int] | None = None, - successor_map: dict[str, set[str]] | None = None, - predecessor_map: dict[str, set[str]] | None = None, + successor_map: dict[str, list[str]] | None = None, + predecessor_map: dict[str, list[str]] | None = None, is_input_vertex: Callable[[str], bool] | None = None, get_vertex_predecessors: Callable[[str], list[str]] | None = None, get_vertex_successors: Callable[[str], list[str]] | None = None, @@ -826,18 +826,18 @@ def get_sorted_vertices( successor_map = {} for vertex_id in vertices_ids: if get_vertex_successors is not None: - successor_map[vertex_id] = set(get_vertex_successors(vertex_id)) + successor_map[vertex_id] = get_vertex_successors(vertex_id) else: - successor_map[vertex_id] = set() + successor_map[vertex_id] = [] # Build predecessor_map if not provided if predecessor_map is None: predecessor_map = {} for vertex_id in vertices_ids: if get_vertex_predecessors is not None: - predecessor_map[vertex_id] = set(get_vertex_predecessors(vertex_id)) + predecessor_map[vertex_id] = get_vertex_predecessors(vertex_id) else: - predecessor_map[vertex_id] = set() + predecessor_map[vertex_id] = [] # If we have a stop component, we need to filter out all vertices # that are not predecessors of the stop component diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index a354812e37..a40a77120c 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -79,7 +79,7 @@ class Vertex: self.built_object: Any = UnbuiltObject() self.built_result: Any = None self.built = False - self._successors_ids: set[str] | None = None + self._successors_ids: list[str] | None = None self.artifacts: dict[str, Any] = {} self.artifacts_raw: dict[str, Any] = {} self.artifacts_type: dict[str, str] = {} @@ -207,8 +207,8 @@ class Vertex: return self.graph.get_successors(self) @property - def successors_ids(self) -> set[str]: - return self.graph.successor_map.get(self.id, set()) + def successors_ids(self) -> list[str]: + return self.graph.successor_map.get(self.id, []) def __getstate__(self): state = self.__dict__.copy() diff --git a/src/backend/base/langflow/graph/vertex/vertex_types.py b/src/backend/base/langflow/graph/vertex/vertex_types.py index c6885000bc..cebc0cb9c2 100644 --- a/src/backend/base/langflow/graph/vertex/vertex_types.py +++ b/src/backend/base/langflow/graph/vertex/vertex_types.py @@ -466,7 +466,7 @@ class StateVertex(ComponentVertex): self.is_state = False @property - def successors_ids(self) -> set[str]: + def successors_ids(self) -> list[str]: if self._successors_ids is None: self.is_state = False return super().successors_ids diff --git a/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json b/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json new file mode 100644 index 0000000000..de4abf151a --- /dev/null +++ b/src/backend/base/langflow/initial_setup/starter_projects/LoopTemplate.json @@ -0,0 +1,1926 @@ +{ + "data": { + "edges": [ + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ArXivComponent", + "id": "ArXivComponent-LChQN", + "name": "papers", + "output_types": [ + "Data" + ] + }, + "targetHandle": { + "fieldName": "data", + "id": "LoopComponent-3vpc1", + "inputTypes": [ + "Data" + ], + "type": "other" + } + }, + "id": "xy-edge__ArXivComponent-LChQN{œdataTypeœ:œArXivComponentœ,œidœ:œArXivComponent-LChQNœ,œnameœ:œpapersœ,œoutput_typesœ:[œDataœ]}-LoopComponent-3vpc1{œfieldNameœ:œdataœ,œidœ:œLoopComponent-3vpc1œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "ArXivComponent-LChQN", + "sourceHandle": "{œdataTypeœ: œArXivComponentœ, œidœ: œArXivComponent-LChQNœ, œnameœ: œpapersœ, œoutput_typesœ: [œDataœ]}", + "target": "LoopComponent-3vpc1", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œLoopComponent-3vpc1œ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "LoopComponent", + "id": "LoopComponent-3vpc1", + "name": "item", + "output_types": [ + "Data" + ] + }, + "targetHandle": { + "fieldName": "data", + "id": "ParseData-Pf12J", + "inputTypes": [ + "Data" + ], + "type": "other" + } + }, + "id": "xy-edge__LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}-ParseData-Pf12J{œfieldNameœ:œdataœ,œidœ:œParseData-Pf12Jœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "LoopComponent-3vpc1", + "sourceHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œitemœ, œoutput_typesœ: [œDataœ]}", + "target": "ParseData-Pf12J", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-Pf12Jœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ParseData", + "id": "ParseData-Pf12J", + "name": "text", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "input_value", + "id": "AnthropicModel-beO6B", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ParseData-Pf12J{œdataTypeœ:œParseDataœ,œidœ:œParseData-Pf12Jœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-AnthropicModel-beO6B{œfieldNameœ:œinput_valueœ,œidœ:œAnthropicModel-beO6Bœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "ParseData-Pf12J", + "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-Pf12Jœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "AnthropicModel-beO6B", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAnthropicModel-beO6Bœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "AnthropicModel", + "id": "AnthropicModel-beO6B", + "name": "text_output", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "message", + "id": "MessagetoData-QRSBb", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__AnthropicModel-beO6B{œdataTypeœ:œAnthropicModelœ,œidœ:œAnthropicModel-beO6Bœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-MessagetoData-QRSBb{œfieldNameœ:œmessageœ,œidœ:œMessagetoData-QRSBbœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "AnthropicModel-beO6B", + "sourceHandle": "{œdataTypeœ: œAnthropicModelœ, œidœ: œAnthropicModel-beO6Bœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}", + "target": "MessagetoData-QRSBb", + "targetHandle": "{œfieldNameœ: œmessageœ, œidœ: œMessagetoData-QRSBbœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "MessagetoData", + "id": "MessagetoData-QRSBb", + "name": "data", + "output_types": [ + "Data" + ] + }, + "targetHandle": { + "dataType": "LoopComponent", + "id": "LoopComponent-3vpc1", + "name": "item", + "output_types": [ + "Data" + ] + } + }, + "id": "xy-edge__MessagetoData-QRSBb{œdataTypeœ:œMessagetoDataœ,œidœ:œMessagetoData-QRSBbœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œitemœ,œoutput_typesœ:[œDataœ]}", + "source": "MessagetoData-QRSBb", + "sourceHandle": "{œdataTypeœ: œMessagetoDataœ, œidœ: œMessagetoData-QRSBbœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}", + "target": "LoopComponent-3vpc1", + "targetHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œitemœ, œoutput_typesœ: [œDataœ]}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "LoopComponent", + "id": "LoopComponent-3vpc1", + "name": "done", + "output_types": [ + "Data" + ] + }, + "targetHandle": { + "fieldName": "data", + "id": "ParseData-igEkj", + "inputTypes": [ + "Data" + ], + "type": "other" + } + }, + "id": "xy-edge__LoopComponent-3vpc1{œdataTypeœ:œLoopComponentœ,œidœ:œLoopComponent-3vpc1œ,œnameœ:œdoneœ,œoutput_typesœ:[œDataœ]}-ParseData-igEkj{œfieldNameœ:œdataœ,œidœ:œParseData-igEkjœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}", + "source": "LoopComponent-3vpc1", + "sourceHandle": "{œdataTypeœ: œLoopComponentœ, œidœ: œLoopComponent-3vpc1œ, œnameœ: œdoneœ, œoutput_typesœ: [œDataœ]}", + "target": "ParseData-igEkj", + "targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-igEkjœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ParseData", + "id": "ParseData-igEkj", + "name": "text", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "input_value", + "id": "ChatOutput-UZgon", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ParseData-igEkj{œdataTypeœ:œParseDataœ,œidœ:œParseData-igEkjœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-UZgon{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-UZgonœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "ParseData-igEkj", + "sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-igEkjœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-UZgon", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-UZgonœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + }, + { + "animated": false, + "className": "", + "data": { + "sourceHandle": { + "dataType": "ChatInput", + "id": "ChatInput-m10vc", + "name": "message", + "output_types": [ + "Message" + ] + }, + "targetHandle": { + "fieldName": "search_query", + "id": "ArXivComponent-LChQN", + "inputTypes": [ + "Message" + ], + "type": "str" + } + }, + "id": "xy-edge__ChatInput-m10vc{œdataTypeœ:œChatInputœ,œidœ:œChatInput-m10vcœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ArXivComponent-LChQN{œfieldNameœ:œsearch_queryœ,œidœ:œArXivComponent-LChQNœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "source": "ChatInput-m10vc", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-m10vcœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "ArXivComponent-LChQN", + "targetHandle": "{œfieldNameœ: œsearch_queryœ, œidœ: œArXivComponent-LChQNœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + } + ], + "nodes": [ + { + "data": { + "id": "ArXivComponent-LChQN", + "node": { + "base_classes": [ + "Data" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": {}, + "description": "Search and retrieve papers from arXiv.org", + "display_name": "arXiv", + "documentation": "", + "edited": false, + "field_order": [ + "search_query", + "search_type", + "max_results" + ], + "frozen": false, + "icon": "arXiv", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Papers", + "method": "search_papers", + "name": "papers", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "import urllib.request\nfrom urllib.parse import urlparse\nfrom xml.etree.ElementTree import Element\n\nfrom defusedxml.ElementTree import fromstring\n\nfrom langflow.custom import Component\nfrom langflow.io import DropdownInput, IntInput, MessageTextInput, Output\nfrom langflow.schema import Data\n\n\nclass ArXivComponent(Component):\n display_name = \"arXiv\"\n description = \"Search and retrieve papers from arXiv.org\"\n icon = \"arXiv\"\n\n inputs = [\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n info=\"The search query for arXiv papers (e.g., 'quantum computing')\",\n tool_mode=True,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Field\",\n info=\"The field to search in\",\n options=[\"all\", \"title\", \"abstract\", \"author\", \"cat\"], # cat is for category\n value=\"all\",\n ),\n IntInput(\n name=\"max_results\",\n display_name=\"Max Results\",\n info=\"Maximum number of results to return\",\n value=10,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Papers\", name=\"papers\", method=\"search_papers\"),\n ]\n\n def build_query_url(self) -> str:\n \"\"\"Build the arXiv API query URL.\"\"\"\n base_url = \"http://export.arxiv.org/api/query?\"\n\n # Build the search query\n search_query = f\"{self.search_type}:{self.search_query}\"\n\n # URL parameters\n params = {\n \"search_query\": search_query,\n \"max_results\": str(self.max_results),\n }\n\n # Convert params to URL query string\n query_string = \"&\".join([f\"{k}={urllib.parse.quote(str(v))}\" for k, v in params.items()])\n\n return base_url + query_string\n\n def parse_atom_response(self, response_text: str) -> list[dict]:\n \"\"\"Parse the Atom XML response from arXiv.\"\"\"\n # Parse XML safely using defusedxml\n root = fromstring(response_text)\n\n # Define namespace dictionary for XML parsing\n ns = {\"atom\": \"http://www.w3.org/2005/Atom\", \"arxiv\": \"http://arxiv.org/schemas/atom\"}\n\n papers = []\n # Process each entry (paper)\n for entry in root.findall(\"atom:entry\", ns):\n paper = {\n \"id\": self._get_text(entry, \"atom:id\", ns),\n \"title\": self._get_text(entry, \"atom:title\", ns),\n \"summary\": self._get_text(entry, \"atom:summary\", ns),\n \"published\": self._get_text(entry, \"atom:published\", ns),\n \"updated\": self._get_text(entry, \"atom:updated\", ns),\n \"authors\": [author.find(\"atom:name\", ns).text for author in entry.findall(\"atom:author\", ns)],\n \"arxiv_url\": self._get_link(entry, \"alternate\", ns),\n \"pdf_url\": self._get_link(entry, \"related\", ns),\n \"comment\": self._get_text(entry, \"arxiv:comment\", ns),\n \"journal_ref\": self._get_text(entry, \"arxiv:journal_ref\", ns),\n \"primary_category\": self._get_category(entry, ns),\n \"categories\": [cat.get(\"term\") for cat in entry.findall(\"atom:category\", ns)],\n }\n papers.append(paper)\n\n return papers\n\n def _get_text(self, element: Element, path: str, ns: dict) -> str | None:\n \"\"\"Safely extract text from an XML element.\"\"\"\n el = element.find(path, ns)\n return el.text.strip() if el is not None and el.text else None\n\n def _get_link(self, element: Element, rel: str, ns: dict) -> str | None:\n \"\"\"Get link URL based on relation type.\"\"\"\n for link in element.findall(\"atom:link\", ns):\n if link.get(\"rel\") == rel:\n return link.get(\"href\")\n return None\n\n def _get_category(self, element: Element, ns: dict) -> str | None:\n \"\"\"Get primary category.\"\"\"\n cat = element.find(\"arxiv:primary_category\", ns)\n return cat.get(\"term\") if cat is not None else None\n\n def search_papers(self) -> list[Data]:\n \"\"\"Search arXiv and return results.\"\"\"\n try:\n # Build the query URL\n url = self.build_query_url()\n\n # Validate URL scheme and host\n parsed_url = urlparse(url)\n if parsed_url.scheme not in (\"http\", \"https\"):\n error_msg = f\"Invalid URL scheme: {parsed_url.scheme}\"\n raise ValueError(error_msg)\n if parsed_url.hostname != \"export.arxiv.org\":\n error_msg = f\"Invalid host: {parsed_url.hostname}\"\n raise ValueError(error_msg)\n\n # Create a custom opener that only allows http/https schemes\n class RestrictedHTTPHandler(urllib.request.HTTPHandler):\n def http_open(self, req):\n return super().http_open(req)\n\n class RestrictedHTTPSHandler(urllib.request.HTTPSHandler):\n def https_open(self, req):\n return super().https_open(req)\n\n # Build opener with restricted handlers\n opener = urllib.request.build_opener(RestrictedHTTPHandler, RestrictedHTTPSHandler)\n urllib.request.install_opener(opener)\n\n # Make the request with validated URL using restricted opener\n response = opener.open(url)\n response_text = response.read().decode(\"utf-8\")\n\n # Parse the response\n papers = self.parse_atom_response(response_text)\n\n # Convert to Data objects\n results = [Data(data=paper) for paper in papers]\n self.status = results\n except (urllib.error.URLError, ValueError) as e:\n error_data = Data(data={\"error\": f\"Request error: {e!s}\"})\n self.status = error_data\n return [error_data]\n else:\n return results\n" + }, + "max_results": { + "_input_type": "IntInput", + "advanced": false, + "display_name": "Max Results", + "dynamic": false, + "info": "Maximum number of results to return", + "list": false, + "list_add_label": "Add More", + "name": "max_results", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 3 + }, + "search_query": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "Search Query", + "dynamic": false, + "info": "The search query for arXiv papers (e.g., 'quantum computing')", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "search_query", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": true, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "search_type": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Search Field", + "dynamic": false, + "info": "The field to search in", + "name": "search_type", + "options": [ + "all", + "title", + "abstract", + "author", + "cat" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "all" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "ArXivComponent" + }, + "dragging": false, + "id": "ArXivComponent-LChQN", + "measured": { + "height": 395, + "width": 320 + }, + "position": { + "x": 81.59312530546094, + "y": 3.9397854556273906 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "LoopComponent-3vpc1", + "node": { + "base_classes": [ + "Data" + ], + "beta": false, + "category": "logic", + "conditional_paths": [], + "custom_fields": {}, + "description": "Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs.", + "display_name": "Loop", + "documentation": "", + "edited": false, + "field_order": [ + "data" + ], + "frozen": false, + "icon": "infinity", + "key": "LoopComponent", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": true, + "cache": true, + "display_name": "Item", + "method": "item_output", + "name": "item", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Done", + "method": "done_output", + "name": "done", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 2.220446049250313e-16, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.custom import Component\nfrom langflow.io import DataInput, Output\nfrom langflow.schema import Data\n\n\nclass LoopComponent(Component):\n display_name = \"Loop\"\n description = (\n \"Iterates over a list of Data objects, outputting one item at a time and aggregating results from loop inputs.\"\n )\n icon = \"infinity\"\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The initial list of Data objects to iterate over.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Item\", name=\"item\", method=\"item_output\", allows_loop=True),\n Output(display_name=\"Done\", name=\"done\", method=\"done_output\"),\n ]\n\n def initialize_data(self) -> None:\n \"\"\"Initialize the data list, context index, and aggregated list.\"\"\"\n if self.ctx.get(f\"{self._id}_initialized\", False):\n return\n\n # Ensure data is a list of Data objects\n data_list = self._validate_data(self.data)\n\n # Store the initial data and context variables\n self.update_ctx(\n {\n f\"{self._id}_data\": data_list,\n f\"{self._id}_index\": 0,\n f\"{self._id}_aggregated\": [],\n f\"{self._id}_initialized\": True,\n }\n )\n\n def _validate_data(self, data):\n \"\"\"Validate and return a list of Data objects.\"\"\"\n if isinstance(data, Data):\n return [data]\n if isinstance(data, list) and all(isinstance(item, Data) for item in data):\n return data\n msg = \"The 'data' input must be a list of Data objects or a single Data object.\"\n raise TypeError(msg)\n\n def evaluate_stop_loop(self) -> bool:\n \"\"\"Evaluate whether to stop item or done output.\"\"\"\n current_index = self.ctx.get(f\"{self._id}_index\", 0)\n data_length = len(self.ctx.get(f\"{self._id}_data\", []))\n return current_index > data_length\n\n def item_output(self) -> Data:\n \"\"\"Output the next item in the list or stop if done.\"\"\"\n self.initialize_data()\n current_item = Data(text=\"\")\n\n if self.evaluate_stop_loop():\n self.stop(\"item\")\n return Data(text=\"\")\n\n # Get data list and current index\n data_list, current_index = self.loop_variables()\n if current_index < len(data_list):\n # Output current item and increment index\n try:\n current_item = data_list[current_index]\n except IndexError:\n current_item = Data(text=\"\")\n self.aggregated_output()\n self.update_ctx({f\"{self._id}_index\": current_index + 1})\n return current_item\n\n def done_output(self) -> Data:\n \"\"\"Trigger the done output when iteration is complete.\"\"\"\n self.initialize_data()\n\n if self.evaluate_stop_loop():\n self.stop(\"item\")\n self.start(\"done\")\n\n return self.ctx.get(f\"{self._id}_aggregated\", [])\n self.stop(\"done\")\n return Data(text=\"\")\n\n def loop_variables(self):\n \"\"\"Retrieve loop variables from context.\"\"\"\n return (\n self.ctx.get(f\"{self._id}_data\", []),\n self.ctx.get(f\"{self._id}_index\", 0),\n )\n\n def aggregated_output(self) -> Data:\n \"\"\"Return the aggregated list once all items are processed.\"\"\"\n self.initialize_data()\n\n # Get data list and aggregated list\n data_list = self.ctx.get(f\"{self._id}_data\", [])\n aggregated = self.ctx.get(f\"{self._id}_aggregated\", [])\n\n # Check if loop input is provided and append to aggregated list\n if self.item is not None and not isinstance(self.item, str) and len(aggregated) <= len(data_list):\n aggregated.append(self.item)\n self.update_ctx({f\"{self._id}_aggregated\": aggregated})\n return aggregated\n" + }, + "data": { + "_input_type": "DataInput", + "advanced": false, + "display_name": "Data", + "dynamic": false, + "info": "The initial list of Data objects to iterate over.", + "input_types": [ + "Data" + ], + "list": false, + "list_add_label": "Add More", + "name": "data", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "other", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "LoopComponent" + }, + "dragging": false, + "id": "LoopComponent-3vpc1", + "measured": { + "height": 280, + "width": 320 + }, + "position": { + "x": 517.2087344858119, + "y": 55.497845490859035 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ParseData-Pf12J", + "node": { + "base_classes": [ + "Data", + "Message" + ], + "beta": false, + "category": "processing", + "conditional_paths": [], + "custom_fields": {}, + "description": "Convert Data objects into Messages using any {field_name} from input data.", + "display_name": "Data to Message", + "documentation": "", + "edited": false, + "field_order": [ + "data", + "template", + "sep" + ], + "frozen": false, + "icon": "message-square", + "key": "ParseData", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "parse_data", + "name": "text", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Data List", + "method": "parse_data_as_list", + "name": "data_list", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.01857804455091699, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n" + }, + "data": { + "_input_type": "DataInput", + "advanced": false, + "display_name": "Data", + "dynamic": false, + "info": "The data to convert to text.", + "input_types": [ + "Data" + ], + "list": true, + "list_add_label": "Add More", + "name": "data", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "other", + "value": "" + }, + "sep": { + "_input_type": "StrInput", + "advanced": true, + "display_name": "Separator", + "dynamic": false, + "info": "", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sep", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "\n" + }, + "template": { + "_input_type": "MultilineInput", + "advanced": false, + "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" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "template", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "{data}" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "ParseData" + }, + "dragging": false, + "id": "ParseData-Pf12J", + "measured": { + "height": 342, + "width": 320 + }, + "position": { + "x": 996.5733307455723, + "y": 35.284413822605146 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "AnthropicModel-beO6B", + "node": { + "base_classes": [ + "LanguageModel", + "Message" + ], + "beta": false, + "category": "models", + "conditional_paths": [], + "custom_fields": {}, + "description": "Generate text using Anthropic Chat&Completion LLMs with prefill support.", + "display_name": "Anthropic", + "documentation": "", + "edited": false, + "field_order": [ + "input_value", + "system_message", + "stream", + "max_tokens", + "model_name", + "api_key", + "temperature", + "base_url", + "tool_model_enabled", + "prefill" + ], + "frozen": false, + "icon": "Anthropic", + "key": "AnthropicModel", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "text_response", + "name": "text_output", + "required_inputs": [], + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Language Model", + "method": "build_model", + "name": "model_output", + "required_inputs": [ + "api_key" + ], + "selected": "LanguageModel", + "tool_mode": true, + "types": [ + "LanguageModel" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.0005851173668140926, + "template": { + "_type": "Component", + "api_key": { + "_input_type": "SecretStrInput", + "advanced": false, + "display_name": "Anthropic API Key", + "dynamic": false, + "info": "Your Anthropic API key.", + "input_types": [ + "Message" + ], + "load_from_db": true, + "name": "api_key", + "password": true, + "placeholder": "", + "real_time_refresh": true, + "required": true, + "show": true, + "title_case": false, + "type": "str", + "value": "" + }, + "base_url": { + "_input_type": "MessageTextInput", + "advanced": false, + "display_name": "Anthropic API URL", + "dynamic": false, + "info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "base_url", + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "https://api.anthropic.com" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from typing import Any\n\nimport requests\nfrom loguru import logger\n\nfrom langflow.base.models.anthropic_constants import ANTHROPIC_MODELS\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import LanguageModel\nfrom langflow.field_typing.range_spec import RangeSpec\nfrom langflow.io import BoolInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput\nfrom langflow.schema.dotdict import dotdict\n\n\nclass AnthropicModelComponent(LCModelComponent):\n display_name = \"Anthropic\"\n description = \"Generate text using Anthropic Chat&Completion LLMs with prefill support.\"\n icon = \"Anthropic\"\n name = \"AnthropicModel\"\n\n inputs = [\n *LCModelComponent._base_inputs,\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n value=4096,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=ANTHROPIC_MODELS,\n refresh_button=True,\n value=ANTHROPIC_MODELS[0],\n combobox=True,\n ),\n SecretStrInput(\n name=\"api_key\",\n display_name=\"Anthropic API Key\",\n info=\"Your Anthropic API key.\",\n value=None,\n required=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n info=\"Run inference with this temperature. Must by in the closed interval [0.0, 1.0].\",\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Anthropic API URL\",\n info=\"Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.\",\n value=\"https://api.anthropic.com\",\n real_time_refresh=True,\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Enable Tool Models\",\n info=(\n \"Select if you want to use models that can work with tools. If yes, only those models will be shown.\"\n ),\n advanced=False,\n value=False,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"prefill\", display_name=\"Prefill\", info=\"Prefill text to guide the model's response.\", advanced=True\n ),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n try:\n output = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n max_tokens_to_sample=self.max_tokens,\n temperature=self.temperature,\n anthropic_api_url=self.base_url,\n streaming=self.stream,\n )\n except Exception as e:\n msg = \"Could not connect to Anthropic API.\"\n raise ValueError(msg) from e\n\n return output\n\n def get_models(self, tool_model_enabled: bool | None = None) -> list[str]:\n try:\n import anthropic\n\n client = anthropic.Anthropic(api_key=self.api_key)\n models = client.models.list(limit=20).data\n model_ids = [model.id for model in models]\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n model_ids = ANTHROPIC_MODELS\n if tool_model_enabled:\n try:\n from langchain_anthropic.chat_models import ChatAnthropic\n except ImportError as e:\n msg = \"langchain_anthropic is not installed. Please install it with `pip install langchain_anthropic`.\"\n raise ImportError(msg) from e\n for model in model_ids:\n model_with_tool = ChatAnthropic(\n model=self.model_name,\n anthropic_api_key=self.api_key,\n anthropic_api_url=self.base_url,\n )\n if not self.supports_tool_calling(model_with_tool):\n model_ids.remove(model)\n return model_ids\n\n def _get_exception_message(self, exception: Exception) -> str | None:\n \"\"\"Get a message from an Anthropic exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n try:\n from anthropic import BadRequestError\n except ImportError:\n return None\n if isinstance(exception, BadRequestError):\n message = exception.body.get(\"error\", {}).get(\"message\")\n if message:\n return message\n return None\n\n def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):\n if field_name in (\"base_url\", \"model_name\", \"tool_model_enabled\", \"api_key\") and field_value:\n try:\n if len(self.api_key) == 0:\n ids = ANTHROPIC_MODELS\n else:\n try:\n ids = self.get_models(tool_model_enabled=self.tool_model_enabled)\n except (ImportError, ValueError, requests.exceptions.RequestException) as e:\n logger.exception(f\"Error getting model names: {e}\")\n ids = ANTHROPIC_MODELS\n build_config[\"model_name\"][\"options\"] = ids\n build_config[\"model_name\"][\"value\"] = ids[0]\n except Exception as e:\n msg = f\"Error getting model names: {e}\"\n raise ValueError(msg) from e\n return build_config\n" + }, + "input_value": { + "_input_type": "MessageInput", + "advanced": false, + "display_name": "Input", + "dynamic": false, + "info": "", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "max_tokens": { + "_input_type": "IntInput", + "advanced": true, + "display_name": "Max Tokens", + "dynamic": false, + "info": "The maximum number of tokens to generate. Set to 0 for unlimited tokens.", + "list": false, + "list_add_label": "Add More", + "name": "max_tokens", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "int", + "value": 4096 + }, + "model_name": { + "_input_type": "DropdownInput", + "advanced": false, + "combobox": true, + "dialog_inputs": {}, + "display_name": "Model Name", + "dynamic": false, + "info": "", + "name": "model_name", + "options": [ + "claude-3-5-sonnet-latest", + "claude-3-5-haiku-latest", + "claude-3-opus-latest", + "claude-3-5-sonnet-20240620", + "claude-3-5-sonnet-20241022", + "claude-3-5-haiku-20241022", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307" + ], + "options_metadata": [], + "placeholder": "", + "refresh_button": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "claude-3-5-sonnet-20241022" + }, + "prefill": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Prefill", + "dynamic": false, + "info": "Prefill text to guide the model's response.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "prefill", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "stream": { + "_input_type": "BoolInput", + "advanced": false, + "display_name": "Stream", + "dynamic": false, + "info": "Stream the response from the model. Streaming works only in Chat.", + "list": false, + "list_add_label": "Add More", + "name": "stream", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + }, + "system_message": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "System Message", + "dynamic": false, + "info": "System message to pass to the model.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "system_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "Translate to Portuguese and output in structured format\nReturn only the JSON and no additional text." + }, + "temperature": { + "_input_type": "SliderInput", + "advanced": false, + "display_name": "Temperature", + "dynamic": false, + "info": "Run inference with this temperature. Must by in the closed interval [0.0, 1.0].", + "max_label": "", + "max_label_icon": "", + "min_label": "", + "min_label_icon": "", + "name": "temperature", + "placeholder": "", + "range_spec": { + "max": 1, + "min": 0, + "step": 0.01, + "step_type": "float" + }, + "required": false, + "show": true, + "slider_buttons": false, + "slider_buttons_options": [], + "slider_input": false, + "title_case": false, + "tool_mode": false, + "type": "slider", + "value": 0.1 + }, + "tool_model_enabled": { + "_input_type": "BoolInput", + "advanced": false, + "display_name": "Enable Tool Models", + "dynamic": false, + "info": "Select if you want to use models that can work with tools. If yes, only those models will be shown.", + "list": false, + "list_add_label": "Add More", + "name": "tool_model_enabled", + "placeholder": "", + "real_time_refresh": true, + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": false + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "AnthropicModel" + }, + "dragging": false, + "id": "AnthropicModel-beO6B", + "measured": { + "height": 801, + "width": 320 + }, + "position": { + "x": 1493.0259340837038, + "y": -225.9942853677759 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "MessagetoData-QRSBb", + "node": { + "base_classes": [ + "Data" + ], + "beta": true, + "category": "processing", + "conditional_paths": [], + "custom_fields": {}, + "description": "Convert a Message object to a Data object", + "display_name": "Message to Data", + "documentation": "", + "edited": false, + "field_order": [ + "message" + ], + "frozen": false, + "icon": "message-square-share", + "key": "MessagetoData", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Data", + "method": "convert_message_to_data", + "name": "data", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.008222426499470714, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import MessageInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass MessageToDataComponent(Component):\n display_name = \"Message to Data\"\n description = \"Convert a Message object to a Data object\"\n icon = \"message-square-share\"\n beta = True\n name = \"MessagetoData\"\n\n inputs = [\n MessageInput(\n name=\"message\",\n display_name=\"Message\",\n info=\"The Message object to convert to a Data object\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"convert_message_to_data\"),\n ]\n\n def convert_message_to_data(self) -> Data:\n if isinstance(self.message, Message):\n # Convert Message to Data\n return Data(data=self.message.data)\n\n msg = \"Error converting Message to Data: Input must be a Message object\"\n logger.opt(exception=True).debug(msg)\n self.status = msg\n return Data(data={\"error\": msg})\n" + }, + "message": { + "_input_type": "MessageInput", + "advanced": false, + "display_name": "Message", + "dynamic": false, + "info": "The Message object to convert to a Data object", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "MessagetoData" + }, + "dragging": false, + "id": "MessagetoData-QRSBb", + "measured": { + "height": 230, + "width": 320 + }, + "position": { + "x": 1863.8805295746977, + "y": 440.56780240629814 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ParseData-igEkj", + "node": { + "base_classes": [ + "Data", + "Message" + ], + "beta": false, + "category": "processing", + "conditional_paths": [], + "custom_fields": {}, + "description": "Convert Data objects into Messages using any {field_name} from input data.", + "display_name": "Data to Message", + "documentation": "", + "edited": false, + "field_order": [ + "data", + "template", + "sep" + ], + "frozen": false, + "icon": "message-square", + "key": "ParseData", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": false, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "parse_data", + "name": "text", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + }, + { + "allows_loop": false, + "cache": true, + "display_name": "Data List", + "method": "parse_data_as_list", + "name": "data_list", + "selected": "Data", + "tool_mode": true, + "types": [ + "Data" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "score": 0.008664293911514902, + "template": { + "_type": "Component", + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n" + }, + "data": { + "_input_type": "DataInput", + "advanced": false, + "display_name": "Data", + "dynamic": false, + "info": "The data to convert to text.", + "input_types": [ + "Data" + ], + "list": true, + "list_add_label": "Add More", + "name": "data", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "other", + "value": "" + }, + "sep": { + "_input_type": "StrInput", + "advanced": true, + "display_name": "Separator", + "dynamic": false, + "info": "", + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sep", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "\n" + }, + "template": { + "_input_type": "MultilineInput", + "advanced": false, + "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" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "template", + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "{text}" + } + }, + "tool_mode": false + }, + "showNode": true, + "type": "ParseData" + }, + "dragging": false, + "id": "ParseData-igEkj", + "measured": { + "height": 342, + "width": 320 + }, + "position": { + "x": 982.6945056277784, + "y": 823.4439549777719 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ChatOutput-UZgon", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": {}, + "description": "Display a chat message in the Playground.", + "display_name": "Chat Output", + "documentation": "", + "edited": false, + "field_order": [ + "input_value", + "should_store_message", + "sender", + "sender_name", + "session_id", + "data_template", + "background_color", + "chat_icon", + "text_color" + ], + "frozen": false, + "icon": "MessagesSquare", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": true, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "message_response", + "name": "message", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "background_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Background Color", + "dynamic": false, + "info": "The background color of the icon.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "background_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "chat_icon": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Icon", + "dynamic": false, + "info": "The icon of the message.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "chat_icon", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import DropdownInput, MessageInput, MessageTextInput, Output\nfrom langflow.schema.message import Message\nfrom langflow.schema.properties import Source\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_AI,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatOutput\"\n minimized = True\n\n inputs = [\n MessageInput(\n name=\"input_value\",\n display_name=\"Text\",\n info=\"Message to be passed as output.\",\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_AI,\n advanced=True,\n info=\"Type of sender.\",\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_AI,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"data_template\",\n display_name=\"Data Template\",\n value=\"{text}\",\n advanced=True,\n info=\"Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.\",\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"message\",\n method=\"message_response\",\n ),\n ]\n\n def _build_source(self, id_: str | None, display_name: str | None, source: str | None) -> Source:\n source_dict = {}\n if id_:\n source_dict[\"id\"] = id_\n if display_name:\n source_dict[\"display_name\"] = display_name\n if source:\n source_dict[\"source\"] = source\n return Source(**source_dict)\n\n async def message_response(self) -> Message:\n source, icon, display_name, source_id = self.get_properties_from_source_component()\n background_color = self.background_color\n text_color = self.text_color\n if self.chat_icon:\n icon = self.chat_icon\n message = self.input_value if isinstance(self.input_value, Message) else Message(text=self.input_value)\n message.sender = self.sender\n message.sender_name = self.sender_name\n message.session_id = self.session_id\n message.flow_id = self.graph.flow_id if hasattr(self, \"graph\") else None\n message.properties.source = self._build_source(source_id, display_name, source)\n message.properties.icon = icon\n message.properties.background_color = background_color\n message.properties.text_color = text_color\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" + }, + "data_template": { + "_input_type": "MessageTextInput", + "advanced": true, + "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" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "data_template", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "{text}" + }, + "input_value": { + "_input_type": "MessageInput", + "advanced": false, + "display_name": "Text", + "dynamic": false, + "info": "Message to be passed as output.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "sender": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Sender Type", + "dynamic": false, + "info": "Type of sender.", + "name": "sender", + "options": [ + "Machine", + "User" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "Machine" + }, + "sender_name": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Sender Name", + "dynamic": false, + "info": "Name of the sender.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sender_name", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "AI" + }, + "session_id": { + "_input_type": "MessageTextInput", + "advanced": true, + "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" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "session_id", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "should_store_message": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Store Messages", + "dynamic": false, + "info": "Store the message in the history.", + "list": false, + "list_add_label": "Add More", + "name": "should_store_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "text_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Text Color", + "dynamic": false, + "info": "The text color of the name", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "text_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": false, + "type": "ChatOutput" + }, + "dragging": false, + "id": "ChatOutput-UZgon", + "measured": { + "height": 66, + "width": 192 + }, + "position": { + "x": 1498.3739454769902, + "y": 1061.1606281984073 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "ChatInput-m10vc", + "node": { + "base_classes": [ + "Message" + ], + "beta": false, + "conditional_paths": [], + "custom_fields": {}, + "description": "Get chat inputs from the Playground.", + "display_name": "Chat Input", + "documentation": "", + "edited": false, + "field_order": [ + "input_value", + "should_store_message", + "sender", + "sender_name", + "session_id", + "files", + "background_color", + "chat_icon", + "text_color" + ], + "frozen": false, + "icon": "MessagesSquare", + "legacy": false, + "lf_version": "1.1.5", + "metadata": {}, + "minimized": true, + "output_types": [], + "outputs": [ + { + "allows_loop": false, + "cache": true, + "display_name": "Message", + "method": "message_response", + "name": "message", + "selected": "Message", + "tool_mode": true, + "types": [ + "Message" + ], + "value": "__UNDEFINED__" + } + ], + "pinned": false, + "template": { + "_type": "Component", + "background_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Background Color", + "dynamic": false, + "info": "The background color of the icon.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "background_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "chat_icon": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Icon", + "dynamic": false, + "info": "The icon of the message.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "chat_icon", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "code": { + "advanced": true, + "dynamic": true, + "fileTypes": [], + "file_path": "", + "info": "", + "list": false, + "load_from_db": false, + "multiline": true, + "name": "code", + "password": false, + "placeholder": "", + "required": true, + "show": true, + "title_case": false, + "type": "code", + "value": "from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.inputs import BoolInput\nfrom langflow.io import (\n DropdownInput,\n FileInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema.message import Message\nfrom langflow.utils.constants import (\n MESSAGE_SENDER_AI,\n MESSAGE_SENDER_NAME_USER,\n MESSAGE_SENDER_USER,\n)\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Playground.\"\n icon = \"MessagesSquare\"\n name = \"ChatInput\"\n minimized = True\n\n inputs = [\n MultilineInput(\n name=\"input_value\",\n display_name=\"Text\",\n value=\"\",\n info=\"Message to be passed as input.\",\n input_types=[],\n ),\n BoolInput(\n name=\"should_store_message\",\n display_name=\"Store Messages\",\n info=\"Store the message in the history.\",\n value=True,\n advanced=True,\n ),\n DropdownInput(\n name=\"sender\",\n display_name=\"Sender Type\",\n options=[MESSAGE_SENDER_AI, MESSAGE_SENDER_USER],\n value=MESSAGE_SENDER_USER,\n info=\"Type of sender.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"sender_name\",\n display_name=\"Sender Name\",\n info=\"Name of the sender.\",\n value=MESSAGE_SENDER_NAME_USER,\n advanced=True,\n ),\n MessageTextInput(\n name=\"session_id\",\n display_name=\"Session ID\",\n info=\"The session ID of the chat. If empty, the current session ID parameter will be used.\",\n advanced=True,\n ),\n FileInput(\n name=\"files\",\n display_name=\"Files\",\n file_types=TEXT_FILE_TYPES + IMG_FILE_TYPES,\n info=\"Files to be sent with the message.\",\n advanced=True,\n is_list=True,\n ),\n MessageTextInput(\n name=\"background_color\",\n display_name=\"Background Color\",\n info=\"The background color of the icon.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"chat_icon\",\n display_name=\"Icon\",\n info=\"The icon of the message.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"text_color\",\n display_name=\"Text Color\",\n info=\"The text color of the name\",\n advanced=True,\n ),\n ]\n outputs = [\n Output(display_name=\"Message\", name=\"message\", method=\"message_response\"),\n ]\n\n async def message_response(self) -> Message:\n background_color = self.background_color\n text_color = self.text_color\n icon = self.chat_icon\n\n message = await Message.create(\n text=self.input_value,\n sender=self.sender,\n sender_name=self.sender_name,\n session_id=self.session_id,\n files=self.files,\n properties={\n \"background_color\": background_color,\n \"text_color\": text_color,\n \"icon\": icon,\n },\n )\n if self.session_id and isinstance(message, Message) and self.should_store_message:\n stored_message = await self.send_message(\n message,\n )\n self.message.value = stored_message\n message = stored_message\n\n self.status = message\n return message\n" + }, + "files": { + "_input_type": "FileInput", + "advanced": true, + "display_name": "Files", + "dynamic": false, + "fileTypes": [ + "txt", + "md", + "mdx", + "csv", + "json", + "yaml", + "yml", + "xml", + "html", + "htm", + "pdf", + "docx", + "py", + "sh", + "sql", + "js", + "ts", + "tsx", + "jpg", + "jpeg", + "png", + "bmp", + "image" + ], + "file_path": "", + "info": "Files to be sent with the message.", + "list": true, + "list_add_label": "Add More", + "name": "files", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "trace_as_metadata": true, + "type": "file", + "value": "" + }, + "input_value": { + "_input_type": "MultilineInput", + "advanced": false, + "display_name": "Text", + "dynamic": false, + "info": "Message to be passed as input.", + "input_types": [], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "multiline": true, + "name": "input_value", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "sender": { + "_input_type": "DropdownInput", + "advanced": true, + "combobox": false, + "dialog_inputs": {}, + "display_name": "Sender Type", + "dynamic": false, + "info": "Type of sender.", + "name": "sender", + "options": [ + "Machine", + "User" + ], + "options_metadata": [], + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "str", + "value": "User" + }, + "sender_name": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Sender Name", + "dynamic": false, + "info": "Name of the sender.", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "sender_name", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "User" + }, + "session_id": { + "_input_type": "MessageTextInput", + "advanced": true, + "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" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "session_id", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + }, + "should_store_message": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Store Messages", + "dynamic": false, + "info": "Store the message in the history.", + "list": false, + "list_add_label": "Add More", + "name": "should_store_message", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, + "text_color": { + "_input_type": "MessageTextInput", + "advanced": true, + "display_name": "Text Color", + "dynamic": false, + "info": "The text color of the name", + "input_types": [ + "Message" + ], + "list": false, + "list_add_label": "Add More", + "load_from_db": false, + "name": "text_color", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_input": true, + "trace_as_metadata": true, + "type": "str", + "value": "" + } + }, + "tool_mode": false + }, + "showNode": false, + "type": "ChatInput" + }, + "dragging": false, + "id": "ChatInput-m10vc", + "measured": { + "height": 66, + "width": 192 + }, + "position": { + "x": -235.49853728839307, + "y": 107.75353484470551 + }, + "selected": false, + "type": "genericNode" + }, + { + "data": { + "id": "note-g4YbJ", + "node": { + "description": "### 💡 Add your Anthropic API key here 👇", + "display_name": "", + "documentation": "", + "template": { + "backgroundColor": "transparent" + } + }, + "type": "note" + }, + "dragging": false, + "height": 324, + "id": "note-g4YbJ", + "measured": { + "height": 324, + "width": 358 + }, + "position": { + "x": 1479.4278434913201, + "y": -274.26903478612456 + }, + "resizing": false, + "selected": false, + "type": "noteNode", + "width": 359 + }, + { + "data": { + "id": "note-e6tLL", + "node": { + "description": "# **Langflow Loop Component Template - ArXiv search result Translator** \nThis template translates research paper summaries on ArXiv into Portuguese and summarizes them. \n Using **Langflow’s looping mechanism**, the template iterates through multiple research papers, translates them with the **Anthropic** model component, and outputs an aggregated version of all translated papers. \n\n# # Quickstart \n 1. Add your Anthropic API key to the **Anthropic** component. \n2. In the **Playground**, enter a query related to a research topic (for example, “Quantum Computing Advancements”). \n\n The flow fetches a list of research papers from ArXiv matching the query. Each paper in the retrieved list is processed one-by-one using the Langflow **Loop component**. \n\n The abstract of each paper is translated into Portuguese by the **Anthropic** model component. \n\n Once all papers are translated, the system aggregates them into a **single structured output**.", + "display_name": "", + "documentation": "", + "template": {} + }, + "type": "note" + }, + "dragging": false, + "height": 647, + "id": "note-e6tLL", + "measured": { + "height": 647, + "width": 577 + }, + "position": { + "x": -890.9006297459302, + "y": -233.44894493951168 + }, + "resizing": false, + "selected": false, + "type": "noteNode", + "width": 576 + } + ], + "viewport": { + "x": 440.1573282533791, + "y": 190.151319158377, + "zoom": 0.21962836650867892 + } + }, + "description": "This template iterates over search results using LoopComponent and translates each result into Portuguese automatically. 🚀", + "endpoint_name": "search_loop", + "gradient": "3", + "icon": "Infinity", + "id": "a004bc07-f6ca-4a02-b8f8-7fb8ebaed7b0", + "is_component": false, + "last_tested_version": "1.1.5", + "name": "Research Translation Loop", + "tags": [ + "chatbots", + "content-generation" + ] +} \ No newline at end of file diff --git a/src/frontend/src/customization/utils/analytics.ts b/src/frontend/src/customization/utils/analytics.ts index 03a7c92765..f40138e3eb 100644 --- a/src/frontend/src/customization/utils/analytics.ts +++ b/src/frontend/src/customization/utils/analytics.ts @@ -5,3 +5,20 @@ export const track = async ( ): Promise => { return; }; + +export const trackFlowBuild = async ( + flowName: string, + isError?: boolean, + properties?: Record, +): Promise => { + return; +}; + +export const trackDataLoaded = async ( + flowId?: string, + flowName?: string, + component?: string, + componentId?: string, +): Promise => { + return; +}; diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts index b869a348ac..8564a5c3ed 100644 --- a/src/frontend/src/stores/flowStore.ts +++ b/src/frontend/src/stores/flowStore.ts @@ -2,7 +2,12 @@ import { BROKEN_EDGES_WARNING, componentsToIgnoreUpdate, } from "@/constants/constants"; -import { track } from "@/customization/utils/analytics"; +import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags"; +import { + track, + trackDataLoaded, + trackFlowBuild, +} from "@/customization/utils/analytics"; import { brokenEdgeMessage } from "@/utils/utils"; import { EdgeChange, @@ -19,7 +24,7 @@ import { MISSED_ERROR_ALERT, } from "../constants/alerts_constants"; import { BuildStatus } from "../constants/enums"; -import { VertexBuildTypeAPI } from "../types/api"; +import { LogsLogType, VertexBuildTypeAPI } from "../types/api"; import { ChatInputType, ChatOutputType } from "../types/chat"; import { AllNodeType, @@ -699,6 +704,28 @@ const useFlowStore = create((set, get) => ({ ...get().verticesBuild!.verticesIds, ...next_vertices_ids, ]; + if ( + ENABLE_DATASTAX_LANGFLOW && + vertexBuildData?.id?.includes("AstraDB") + ) { + const search_results: LogsLogType[] = Object.values( + vertexBuildData?.data?.logs?.search_results, + ); + search_results.forEach((log) => { + if ( + log.message.includes("Adding") && + log.message.includes("documents") && + log.message.includes("Vector Store") + ) { + trackDataLoaded( + get().currentFlow?.id, + get().currentFlow?.name, + "AstraDB Vector Store", + vertexBuildData?.id, + ); + } + }); + } get().updateVerticesBuild({ verticesIds: newIds, verticesLayers: newLayers, @@ -747,6 +774,9 @@ const useFlowStore = create((set, get) => ({ false, ); get().setIsBuilding(false); + trackFlowBuild(get().currentFlow?.name ?? "Unknown", false, { + flowId: get().currentFlow?.id, + }); }, onBuildUpdate: handleBuildUpdate, onBuildError: (title: string, list: string[], elementList) => { @@ -767,6 +797,10 @@ const useFlowStore = create((set, get) => ({ setErrorData({ list, title }); get().setIsBuilding(false); get().buildController.abort(); + trackFlowBuild(get().currentFlow?.name ?? "Unknown", true, { + flowId: get().currentFlow?.id, + error: list, + }); }, onBuildStart: (elementList) => { const idList = elementList diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index 9cf131f5c8..9eef7f9209 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -266,6 +266,7 @@ export function groupByFamily( return ( template?.type && template?.show && + !template?.advanced && ((!excludeTypes.has(template.type) && baseClassesSet.has(template.type)) || (template?.input_types && diff --git a/src/frontend/tests/core/features/group.spec.ts b/src/frontend/tests/core/features/group.spec.ts index 4aa098fc48..67a88d18b0 100644 --- a/src/frontend/tests/core/features/group.spec.ts +++ b/src/frontend/tests/core/features/group.spec.ts @@ -17,16 +17,22 @@ test.describe("group node test", () => { await page.getByTestId("fit_view").first().click(); await page.getByTestId("title-OpenAI").click(); - await page.getByTestId("title-OpenAI").click({ modifiers: ["Control"] }); - await page.getByTestId("title-Prompt").click({ modifiers: ["Control"] }); - await page.getByTestId("title-OpenAI").click({ modifiers: ["Control"] }); + await page + .getByTestId("title-OpenAI") + .click({ modifiers: ["ControlOrMeta"] }); + await page + .getByTestId("title-Prompt") + .click({ modifiers: ["ControlOrMeta"] }); + await page + .getByTestId("title-OpenAI") + .click({ modifiers: ["ControlOrMeta"] }); await page.getByRole("button", { name: "Group" }).click(); await page.getByTestId("title-Group").click(); await page.getByTestId("edit-name-description-button").click(); await page.getByTestId("input-title-Group").first().fill("test"); await page.getByTestId("save-name-description-button").first().click(); - await page.keyboard.press("Control+g"); + await page.keyboard.press("ControlOrMeta+g"); await page.getByTestId("title-OpenAI").isVisible(); await page.getByTestId("title-Prompt").isVisible(); }, diff --git a/src/frontend/tests/core/integrations/Basic Prompting.spec.ts b/src/frontend/tests/core/integrations/Basic Prompting.spec.ts index d50fdee462..04a4e93b52 100644 --- a/src/frontend/tests/core/integrations/Basic Prompting.spec.ts +++ b/src/frontend/tests/core/integrations/Basic Prompting.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "Basic Prompting (Hello, World)", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Blog Writer.spec.ts b/src/frontend/tests/core/integrations/Blog Writer.spec.ts index d9b14040d4..08fcc67f9e 100644 --- a/src/frontend/tests/core/integrations/Blog Writer.spec.ts +++ b/src/frontend/tests/core/integrations/Blog Writer.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "Blog Writer", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts b/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts index 389790d5f1..235b98c322 100644 --- a/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts +++ b/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts @@ -7,7 +7,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; test( "Custom Component Generator", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.ANTHROPIC_API_KEY, diff --git a/src/frontend/tests/core/integrations/Document QA.spec.ts b/src/frontend/tests/core/integrations/Document QA.spec.ts index 2fa3bbf8a0..e1a26348c2 100644 --- a/src/frontend/tests/core/integrations/Document QA.spec.ts +++ b/src/frontend/tests/core/integrations/Document QA.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "Document Q&A", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts b/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts index 6d54a00822..133e9701ae 100644 --- a/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test.skip( "Dynamic Agent", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts b/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts index ba5ff76eec..299ac3c310 100644 --- a/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test.skip( "Hierarchical Tasks Agent", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts b/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts index 3e3f1f8226..893929df80 100644 --- a/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts +++ b/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts @@ -15,7 +15,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda test( "Image Sentiment Analysis", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts b/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts index ef72c7cebe..ac25fa8177 100644 --- a/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts +++ b/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts @@ -8,7 +8,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; test( "Instagram Copywriter", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Market Research.spec.ts b/src/frontend/tests/core/integrations/Market Research.spec.ts index 6df987b52d..a6c1a859ff 100644 --- a/src/frontend/tests/core/integrations/Market Research.spec.ts +++ b/src/frontend/tests/core/integrations/Market Research.spec.ts @@ -8,7 +8,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; test( "Market Research", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts b/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts index db0603ed3e..0d12fb09b9 100644 --- a/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts +++ b/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "Memory Chatbot", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts b/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts index be3a3e8cc6..262bcbb760 100644 --- a/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts +++ b/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts @@ -13,7 +13,7 @@ import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; test( "Prompt Chaining", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts b/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts index 96d9cb3267..b9561d37a0 100644 --- a/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts +++ b/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts @@ -13,7 +13,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda test( "SEO Keyword Generator", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts b/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts index efadb0c477..6c047f77ca 100644 --- a/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts +++ b/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts @@ -13,7 +13,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda test( "SaaS Pricing", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts b/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts index a1d6fd0d3e..2d06e8b30d 100644 --- a/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test.skip( "Sequential Task Agent", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Simple Agent.spec.ts b/src/frontend/tests/core/integrations/Simple Agent.spec.ts index e20e0444b5..3bfa8c893c 100644 --- a/src/frontend/tests/core/integrations/Simple Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Simple Agent.spec.ts @@ -6,7 +6,7 @@ import { initialGPTsetup } from "../../utils/initialGPTsetup"; test( "Simple Agent", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts b/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts index d66e3227de..fb0458f514 100644 --- a/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts @@ -11,7 +11,7 @@ import { updateOldComponents } from "../../utils/update-old-components"; test( "Travel Planning Agent", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts b/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts index bced1d125f..ba0a92815f 100644 --- a/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts +++ b/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts @@ -13,7 +13,7 @@ import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-moda test( "Twitter Thread Generator", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/Vector Store.spec.ts b/src/frontend/tests/core/integrations/Vector Store.spec.ts index 352508519d..9bff2b9f12 100644 --- a/src/frontend/tests/core/integrations/Vector Store.spec.ts +++ b/src/frontend/tests/core/integrations/Vector Store.spec.ts @@ -5,7 +5,7 @@ import { extractAndCleanCode } from "../../utils/extract-and-clean-code"; test( "Vector Store RAG", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/core/integrations/starter-projects.spec.ts b/src/frontend/tests/core/integrations/starter-projects.spec.ts index f256523d26..cdf352bd8a 100644 --- a/src/frontend/tests/core/integrations/starter-projects.spec.ts +++ b/src/frontend/tests/core/integrations/starter-projects.spec.ts @@ -3,7 +3,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; test( "vector store from starter projects should have its connections and nodes on the flow", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page, request }) => { const response = await request.get("/api/v1/starter-projects"); expect(response.status()).toBe(200); diff --git a/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts b/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts index ae53e06742..5d20b440a0 100644 --- a/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts @@ -5,7 +5,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; test( "user must be able to edit an empty prompt", - { tag: ["@release", "@starter-project"] }, + { tag: ["@release", "@starter-projects"] }, async ({ page }) => { test.skip( !process?.env?.OPENAI_API_KEY, diff --git a/src/frontend/tests/extended/features/starter-projects.spec.ts b/src/frontend/tests/extended/features/starter-projects.spec.ts index 04500f0f54..0730c99fe5 100644 --- a/src/frontend/tests/extended/features/starter-projects.spec.ts +++ b/src/frontend/tests/extended/features/starter-projects.spec.ts @@ -3,7 +3,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; test( "user must be able to interact with starter projects", - { tag: ["@release", "@starter-projects"] }, + { tag: ["@release", "@starter-projectss"] }, async ({ page, context }) => { await awaitBootstrapTest(page);