Merge branch 'main' into docs-publish-v0

This commit is contained in:
Mendon Kissling
2025-02-14 13:20:44 -05:00
committed by GitHub
41 changed files with 2082 additions and 78 deletions

View File

@ -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/**"

View File

@ -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 }}

View File

@ -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:

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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

View File

@ -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:

View File

@ -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,
},
},
}),

View File

@ -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",
},
],
},
{

View File

@ -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

View File

@ -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]]]:

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -5,3 +5,20 @@ export const track = async (
): Promise<void> => {
return;
};
export const trackFlowBuild = async (
flowName: string,
isError?: boolean,
properties?: Record<string, any>,
): Promise<void> => {
return;
};
export const trackDataLoaded = async (
flowId?: string,
flowName?: string,
component?: string,
componentId?: string,
): Promise<void> => {
return;
};

View File

@ -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<FlowStoreType>((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<FlowStoreType>((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<FlowStoreType>((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

View File

@ -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 &&

View File

@ -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();
},

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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);