Merge branch 'main' into docs-publish-v0

This commit is contained in:
Mendon Kissling
2025-02-19 11:19:11 -05:00
committed by GitHub
76 changed files with 2783 additions and 1969 deletions

View File

@ -18,13 +18,13 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm
cache-dependency-path: ./docs/package-lock.json
cache: yarn
cache-dependency-path: ./docs/yarn.lock
- name: Install dependencies
run: cd docs && npm install --legacy-peer-deps
run: cd docs && yarn install
- name: Build website
run: cd docs && npm run build
run: cd docs && yarn build
# Popular action to deploy to GitHub Pages:
# Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus

View File

@ -91,6 +91,21 @@ To access the **Logs** pane, click your **Flow Name**, and then select **Logs**.
![](/img/logs.png)
Langflow stores logs at the location specified in the `LANGFLOW_CONFIG_DIR` environment variable.
This directory's default location depends on your operating system.
* **Linux/WSL**: `~/.cache/langflow/`
* **macOS**: `/Users/<username>/Library/Caches/langflow/`
* **Windows**: `%LOCALAPPDATA%\langflow\langflow\Cache`
To modify the location of your log file:
1. Add `LANGFLOW_LOG_FILE=path/to/logfile.log` in your `.env.` file.
2. To start Langflow with the values from your `.env` file, start Langflow with `uv run langflow run --env-file .env`.
An example `.env` file is available in the [project repository](https://github.com/langflow-ai/langflow/blob/main/.env.example).
## Projects and folders
The **My Projects** page displays all the flows and components you've created in the Langflow workspace.

View File

@ -20,9 +20,6 @@ const config = {
projectName: "langflow",
trailingSlash: false,
staticDirectories: ["static"],
customFields: {
mendableAnonKey: "b7f52734-297c-41dc-8737-edbd13196394", // Mendable Anon Client-side key, safe to expose to the public
},
i18n: {
defaultLocale: "en",
locales: ["en"],
@ -261,6 +258,15 @@ const config = {
hideable: false,
},
},
algolia: {
appId: 'UZK6BDPCVY',
// public key, safe to commit
apiKey: 'adbd7686dceb1cd510d5ce20d04bf74c',
indexName: 'langflow',
contextualSearch: true,
searchParameters: {},
searchPagePath: 'search',
},
}),
};

View File

@ -12,10 +12,10 @@
},
"dependencies": {
"@code-hike/mdx": "^0.9.0",
"@docusaurus/core": "^3.7.0",
"@docusaurus/core": "3.7.0",
"@docusaurus/plugin-client-redirects": "^3.7.0",
"@docusaurus/plugin-google-tag-manager": "^3.7.0",
"@docusaurus/preset-classic": "^3.7.0",
"@docusaurus/preset-classic": "3.7.0",
"@easyops-cn/docusaurus-search-local": "^0.45.0",
"@mdx-js/react": "^3.0.1",
"@mendable/search": "^0.0.206",

View File

@ -1,52 +1,77 @@
import React from "react";
import React, { useState } from "react";
import Footer from "@theme-original/Footer";
import { MendableFloatingButton } from "@mendable/search";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { useDocSearchKeyboardEvents } from '@docsearch/react';
export default function FooterWrapper(props) {
const iconSpan1 = React.createElement(
"img",
{
src: "/img/langflow-icon-black-transparent.svg",
srcDark: "",
style: { width: "40px" },
},
null
);
const [isHovered, setIsHovered] = useState(false);
const searchButtonRef = React.useRef(null);
const icon = React.createElement(
"div",
{
style: {
color: "#000000",
fontSize: "22px",
width: "48px",
height: "48px",
margin: "0px",
padding: "0px",
display: "flex",
alignItems: "center",
justifyContent: "center",
textAlign: "center",
},
useDocSearchKeyboardEvents({
isOpen: false,
onOpen: () => {
searchButtonRef.current?.click();
},
[iconSpan1]
);
const {
siteConfig: { customFields },
} = useDocusaurusContext();
const mendableFloatingButton = React.createElement(MendableFloatingButton, {
floatingButtonStyle: { color: "#000000", backgroundColor: "#f6f6f6" },
anon_key: customFields.mendableAnonKey,
showSimpleSearch: true,
icon: icon,
});
const searchButton = (
<div
ref={searchButtonRef}
onClick={() => {
// This will trigger Docusaurus's default search modal
document.querySelector('.DocSearch-Button')?.click();
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
position: 'fixed',
right: '20px',
bottom: '20px',
zIndex: 100,
display: 'flex',
alignItems: 'center',
gap: '10px',
cursor: 'pointer',
}}
>
{isHovered && (
<div
style={{
backgroundColor: "#f6f6f6",
padding: '8px 16px',
borderRadius: '20px',
color: '#000',
fontSize: '14px',
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
}}
>
Hi, how can I help you?
</div>
)}
<div
style={{
backgroundColor: "#f6f6f6",
borderRadius: "50%",
width: "48px",
height: "48px",
display: "flex",
alignItems: "center",
justifyContent: "center",
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
}}
>
<img
src="/img/langflow-icon-black-transparent.svg"
style={{ width: "40px" }}
alt="Search"
/>
</div>
</div>
);
return (
<>
<Footer />
{mendableFloatingButton}
<Footer {...props} />
{searchButton}
</>
);
}

View File

@ -1,23 +0,0 @@
// By default, the classic theme does not provide any SearchBar implementation
// If you swizzled this, it is your responsibility to provide an implementation
// Tip: swizzle the SearchBar from the Algolia theme for inspiration:
// npm run swizzle @docusaurus/theme-search-algolia SearchBar
import React from 'react'
import { MendableSearchBar } from '@mendable/search'
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
export default function SearchBarWrapper() {
const {
siteConfig: { customFields },
} = useDocusaurusContext()
return (
<div className="mendable-search">
<MendableSearchBar
anon_key={customFields.mendableAnonKey}
placeholder="Search..."
dialogPlaceholder="How to deploy my application?"
showSimpleSearch
/>
</div>
)
}

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ export class EcrRepository extends Construct {
super(scope, id)
const imagePlatform = props.arch == ecs.CpuArchitecture.ARM64 ? Platform.LINUX_ARM64 : Platform.LINUX_AMD64
const backendPath = path.join(__dirname, "../../../../../", "langflow")
const backendPath = path.join(__dirname, "../../../../", "docker")
const excludeDir = ['node_modules','.git', 'cdk.out']
const LifecycleRule = {
tagStatus: ecr.TagStatus.ANY,

View File

@ -22,7 +22,7 @@
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^20.12.12",
"aws-cdk": "^2.141.0",
"aws-cdk": "^2.1000.2",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
@ -43,19 +43,49 @@
}
},
"node_modules/@aws-cdk/asset-awscli-v1": {
"version": "2.2.202",
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz",
"integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg=="
},
"node_modules/@aws-cdk/asset-kubectl-v20": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz",
"integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg=="
"version": "2.2.224",
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.224.tgz",
"integrity": "sha512-4CQP+y0rLq4IWzOlTqBhe8IxBU3Tul9KcmHxiAqztQRWLIl5HAVGCOWdLzHMLgbpFWNNMlIJxB8GwBEV0pWtfQ==",
"license": "Apache-2.0"
},
"node_modules/@aws-cdk/asset-node-proxy-agent-v6": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.3.tgz",
"integrity": "sha512-twhuEG+JPOYCYPx/xy5uH2+VUsIEhPTzDY0F1KuB+ocjWWB/KEDiOVL19nHvbPCB6fhWnkykXEMJ4HHcKvjtvg=="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz",
"integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==",
"license": "Apache-2.0"
},
"node_modules/@aws-cdk/cloud-assembly-schema": {
"version": "39.2.20",
"resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-39.2.20.tgz",
"integrity": "sha512-RI7S8jphGA8mak154ElnEJQPNTTV4PZmA7jgqnBBHQGyOPJIXxtACubNQ5m4YgjpkK3UJHsWT+/cOAfM/Au/Wg==",
"bundleDependencies": [
"jsonschema",
"semver"
],
"license": "Apache-2.0",
"dependencies": {
"jsonschema": "~1.4.1",
"semver": "^7.7.1"
}
},
"node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": {
"version": "1.4.1",
"inBundle": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": {
"version": "7.7.1",
"inBundle": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@aws-cdk/integ-tests-alpha": {
"version": "2.138.0-alpha.0",
@ -1451,24 +1481,25 @@
}
},
"node_modules/aws-cdk": {
"version": "2.141.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.141.0.tgz",
"integrity": "sha512-RM9uDiETBEKCHemItaRGVjOLwoZ5iqXnejpyXY7+YF75c2c0Ui7HSZI8QD0stDg3S/2UbLcKv2RA9dBsjrWUGA==",
"version": "2.1000.2",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1000.2.tgz",
"integrity": "sha512-QsXqJhGWjHNqP7etgE3sHOTiDBXItmSKdFKgsm1qPMBabCMyFfmWZnEeUxfZ4sMaIoxvLpr3sqoWSNeLuUk4sg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"cdk": "bin/cdk"
},
"engines": {
"node": ">= 14.15.0"
"node": ">= 16.0.0"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/aws-cdk-lib": {
"version": "2.141.0",
"resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.141.0.tgz",
"integrity": "sha512-xda56Lfwpdqg9MssnFdXrAKTmeeNjfrfFCaWwqGqToG6cfGY2W+6wyyoObX60/MeZGhhs3Lhdb/K94ulLJ4X/A==",
"version": "2.179.0",
"resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.179.0.tgz",
"integrity": "sha512-fwkoEzvh3TXYj+GPkyq/GSQt63JJV1dBgDKwr3xRdb8lQaPntSclmisa+ARO8tjPfkru1NqxAFQTtiqtAE83cA==",
"bundleDependencies": [
"@balena/dockerignore",
"case",
@ -1482,19 +1513,20 @@
"yaml",
"mime-types"
],
"license": "Apache-2.0",
"dependencies": {
"@aws-cdk/asset-awscli-v1": "^2.2.202",
"@aws-cdk/asset-kubectl-v20": "^2.1.2",
"@aws-cdk/asset-node-proxy-agent-v6": "^2.0.3",
"@aws-cdk/asset-awscli-v1": "^2.2.208",
"@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0",
"@aws-cdk/cloud-assembly-schema": "^39.2.0",
"@balena/dockerignore": "^1.0.2",
"case": "1.6.3",
"fs-extra": "^11.2.0",
"ignore": "^5.3.1",
"ignore": "^5.3.2",
"jsonschema": "^1.4.1",
"mime-types": "^2.1.35",
"minimatch": "^3.1.2",
"punycode": "^2.3.1",
"semver": "^7.6.0",
"semver": "^7.6.3",
"table": "^6.8.2",
"yaml": "1.10.2"
},
@ -1511,14 +1543,14 @@
"license": "Apache-2.0"
},
"node_modules/aws-cdk-lib/node_modules/ajv": {
"version": "8.13.0",
"version": "8.17.1",
"inBundle": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.4.1"
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
@ -1608,8 +1640,23 @@
"inBundle": true,
"license": "MIT"
},
"node_modules/aws-cdk-lib/node_modules/fast-uri": {
"version": "3.0.6",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"inBundle": true,
"license": "BSD-3-Clause"
},
"node_modules/aws-cdk-lib/node_modules/fs-extra": {
"version": "11.2.0",
"version": "11.3.0",
"inBundle": true,
"license": "MIT",
"dependencies": {
@ -1627,7 +1674,7 @@
"license": "ISC"
},
"node_modules/aws-cdk-lib/node_modules/ignore": {
"version": "5.3.1",
"version": "5.3.2",
"inBundle": true,
"license": "MIT",
"engines": {
@ -1659,7 +1706,7 @@
}
},
"node_modules/aws-cdk-lib/node_modules/jsonschema": {
"version": "1.4.1",
"version": "1.5.0",
"inBundle": true,
"license": "MIT",
"engines": {
@ -1671,17 +1718,6 @@
"inBundle": true,
"license": "MIT"
},
"node_modules/aws-cdk-lib/node_modules/lru-cache": {
"version": "6.0.0",
"inBundle": true,
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/aws-cdk-lib/node_modules/mime-db": {
"version": "1.52.0",
"inBundle": true,
@ -1729,12 +1765,9 @@
}
},
"node_modules/aws-cdk-lib/node_modules/semver": {
"version": "7.6.0",
"version": "7.6.3",
"inBundle": true,
"license": "ISC",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
@ -1783,7 +1816,7 @@
}
},
"node_modules/aws-cdk-lib/node_modules/table": {
"version": "6.8.2",
"version": "6.9.0",
"inBundle": true,
"license": "BSD-3-Clause",
"dependencies": {
@ -1805,19 +1838,6 @@
"node": ">= 10.0.0"
}
},
"node_modules/aws-cdk-lib/node_modules/uri-js": {
"version": "4.4.1",
"inBundle": true,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/aws-cdk-lib/node_modules/yallist": {
"version": "4.0.0",
"inBundle": true,
"license": "ISC"
},
"node_modules/aws-cdk-lib/node_modules/yaml": {
"version": "1.10.2",
"inBundle": true,
@ -2535,10 +2555,11 @@
"dev": true
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@ -3837,12 +3858,13 @@
"dev": true
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.2",
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {

View File

@ -13,7 +13,7 @@
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^20.12.12",
"aws-cdk": "^2.141.0",
"aws-cdk": "^2.1000.2",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",

View File

@ -19,6 +19,7 @@ from .openrouter import OpenRouterComponent
from .perplexity import PerplexityComponent
from .sambanova import SambaNovaComponent
from .vertexai import ChatVertexAIComponent
from .xai import XAIModelComponent
__all__ = [
"AIMLModelComponent",
@ -42,4 +43,5 @@ __all__ = [
"PerplexityComponent",
"QianfanChatEndpointComponent",
"SambaNovaComponent",
"XAIModelComponent",
]

View File

@ -0,0 +1,155 @@
import requests
from langchain_openai import ChatOpenAI
from pydantic.v1 import SecretStr
from typing_extensions import override
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import LanguageModel
from langflow.field_typing.range_spec import RangeSpec
from langflow.inputs import BoolInput, DictInput, DropdownInput, IntInput, MessageTextInput, SecretStrInput, SliderInput
XAI_DEFAULT_MODELS = ["grok-2-latest"]
class XAIModelComponent(LCModelComponent):
display_name = "xAI"
description = "Generates text using xAI models like Grok."
icon = "xAI"
name = "xAIModel"
inputs = [
*LCModelComponent._base_inputs,
IntInput(
name="max_tokens",
display_name="Max Tokens",
advanced=True,
info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
range_spec=RangeSpec(min=0, max=128000),
),
DictInput(
name="model_kwargs",
display_name="Model Kwargs",
advanced=True,
info="Additional keyword arguments to pass to the model.",
),
BoolInput(
name="json_mode",
display_name="JSON Mode",
advanced=True,
info="If True, it will output JSON regardless of passing a schema.",
),
DropdownInput(
name="model_name",
display_name="Model Name",
advanced=False,
options=XAI_DEFAULT_MODELS,
value=XAI_DEFAULT_MODELS[0],
refresh_button=True,
combobox=True,
info="The xAI model to use",
),
MessageTextInput(
name="base_url",
display_name="xAI API Base",
advanced=True,
info="The base URL of the xAI API. Defaults to https://api.x.ai/v1",
value="https://api.x.ai/v1",
),
SecretStrInput(
name="api_key",
display_name="xAI API Key",
info="The xAI API Key to use for the model.",
advanced=False,
value="XAI_API_KEY",
required=True,
),
SliderInput(
name="temperature", display_name="Temperature", value=0.1, range_spec=RangeSpec(min=0, max=2, step=0.01)
),
IntInput(
name="seed",
display_name="Seed",
info="The seed controls the reproducibility of the job.",
advanced=True,
value=1,
),
]
def get_models(self) -> list[str]:
"""Fetch available models from xAI API."""
if not self.api_key:
return XAI_DEFAULT_MODELS
base_url = self.base_url or "https://api.x.ai/v1"
url = f"{base_url}/language-models"
headers = {"Authorization": f"Bearer {self.api_key}", "Accept": "application/json"}
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
data = response.json()
# Extract model IDs and any aliases
models = set()
for model in data.get("models", []):
models.add(model["id"])
models.update(model.get("aliases", []))
return sorted(models) if models else XAI_DEFAULT_MODELS
except requests.RequestException as e:
self.status = f"Error fetching models: {e}"
return XAI_DEFAULT_MODELS
@override
def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):
"""Update build configuration with fresh model list when key fields change."""
if field_name in {"api_key", "base_url", "model_name"}:
models = self.get_models()
build_config["model_name"]["options"] = models
return build_config
def build_model(self) -> LanguageModel:
api_key = self.api_key
temperature = self.temperature
model_name: str = self.model_name
max_tokens = self.max_tokens
model_kwargs = self.model_kwargs or {}
base_url = self.base_url or "https://api.x.ai/v1"
json_mode = self.json_mode
seed = self.seed
api_key = SecretStr(api_key).get_secret_value() if api_key else None
output = ChatOpenAI(
max_tokens=max_tokens or None,
model_kwargs=model_kwargs,
model=model_name,
base_url=base_url,
api_key=api_key,
temperature=temperature if temperature is not None else 0.1,
seed=seed,
)
if json_mode:
output = output.bind(response_format={"type": "json_object"})
return output
def _get_exception_message(self, e: Exception):
"""Get a message from an xAI exception.
Args:
e (Exception): The exception to get the message from.
Returns:
str: The message from the exception.
"""
try:
from openai import BadRequestError
except ImportError:
return None
if isinstance(e, BadRequestError):
message = e.body.get("message")
if message:
return message
return None

View File

@ -82,6 +82,7 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
"new_collection_name",
"embedding_generation_provider",
"embedding_generation_model",
"dimension",
],
"template": {
"new_collection_name": StrInput(
@ -330,7 +331,7 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
embedding_generation_model: str | None = None,
):
# Create the data API client
client = DataAPIClient(token=token)
client = DataAPIClient(token=token, environment=environment)
# Get the database object
database = client.get_async_database(api_endpoint=api_endpoint, token=token)
@ -554,6 +555,9 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
api_endpoint=build_config["api_endpoint"]["value"],
)
# Append a special case for Bring your own
vectorize_providers["Bring your own"] = [None, ["Bring your own"]]
# If the collection is set, allow user to see embedding options
build_config["collection_name"]["dialog_inputs"]["fields"]["data"]["node"]["template"][
"embedding_generation_provider"
@ -714,7 +718,11 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
generation_provider = field_value["embedding_generation_provider"]
provider = generation_provider if generation_provider != "Bring your own" else None
generation_model = field_value["embedding_generation_model"]
model = generation_model if generation_model else None
model = generation_model if generation_model and generation_model != "Bring your own" else None
# Set the embedding choice
build_config["embedding_choice"]["value"] = "Astra Vectorize" if provider else "Embedding Model"
build_config["embedding_model"]["advanced"] = bool(provider)
# Add the new collection to the list of options
icon = "NVIDIA" if provider == "Nvidia" else "vectorstores"
@ -788,6 +796,10 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent):
# Ensure that autodetect collection is set to False, since its a new collection
build_config["autodetect_collection"]["value"] = False
# If nothing is selected, can't detect provider - return
if not field_value:
return build_config
# Find the position of the selected collection to align with metadata
index_of_name = build_config["collection_name"]["options"].index(field_value)
value_of_provider = build_config["collection_name"]["options_metadata"][index_of_name]["provider"]

View File

@ -960,7 +960,9 @@
"key": "ParseData",
"legacy": false,
"lf_version": "1.1.5",
"metadata": {},
"metadata": {
"legacy_name": "Parse Data"
},
"minimized": false,
"output_types": [],
"outputs": [
@ -1011,7 +1013,7 @@
"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"
"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 metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\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",

View File

@ -452,7 +452,9 @@
"icon": "message-square",
"legacy": false,
"lf_version": "1.1.5",
"metadata": {},
"metadata": {
"legacy_name": "Parse Data"
},
"minimized": false,
"output_types": [],
"outputs": [
@ -502,7 +504,7 @@
"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"
"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 metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\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",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -124,6 +124,15 @@ class DatabaseService(Service):
# if the user specifies an empty dict, we allow it.
kwargs = self._build_connection_kwargs()
poolclass_key = kwargs.get("poolclass")
if poolclass_key is not None:
pool_class = getattr(sa, poolclass_key, None)
if pool_class and isinstance(pool_class(), sa.pool.Pool):
logger.debug(f"Using poolclass: {poolclass_key}.")
kwargs["poolclass"] = pool_class
else:
logger.error(f"Invalid poolclass '{poolclass_key}' specified. Using default pool class.")
return create_async_engine(
self.database_url,
connect_args=self._get_connect_args(),
@ -136,16 +145,18 @@ class DatabaseService(Service):
return self._create_engine()
def _get_connect_args(self):
if self.settings_service.settings.database_url and self.settings_service.settings.database_url.startswith(
"sqlite"
):
connect_args = {
settings = self.settings_service.settings
if settings.db_driver_connection_settings is not None:
return settings.db_driver_connection_settings
if settings.database_url and settings.database_url.startswith("sqlite"):
return {
"check_same_thread": False,
"timeout": self.settings_service.settings.db_connect_timeout,
"timeout": settings.db_connect_timeout,
}
else:
connect_args = {}
return connect_args
return {}
def on_connection(self, dbapi_connection, _connection_record) -> None:
if isinstance(dbapi_connection, sqlite3.Connection | dialect_sqlite.aiosqlite.AsyncAdapt_aiosqlite_connection):

View File

@ -90,6 +90,9 @@ class Settings(BaseSettings):
sqlite_pragmas: dict | None = {"synchronous": "NORMAL", "journal_mode": "WAL"}
"""SQLite pragmas to use when connecting to the database."""
db_driver_connection_settings: dict | None = None
"""Database driver connection settings."""
db_connection_settings: dict | None = {
"pool_size": 20, # Match the pool_size above
"max_overflow": 30, # Match the max_overflow above

View File

@ -0,0 +1,198 @@
from unittest.mock import MagicMock, patch
import pytest
from langflow.components.models import XAIModelComponent
from langflow.custom import Component
from langflow.custom.utils import build_custom_component_template
from langflow.inputs import (
BoolInput,
DictInput,
DropdownInput,
IntInput,
MessageTextInput,
SecretStrInput,
SliderInput,
)
from tests.base import ComponentTestBaseWithoutClient
class TestXAIComponent(ComponentTestBaseWithoutClient):
@pytest.fixture
def component_class(self):
return XAIModelComponent
@pytest.fixture
def default_kwargs(self):
return {
"temperature": 0.1,
"max_tokens": 50,
"api_key": "dummy-key",
"model_name": "grok-2-latest",
"model_kwargs": {},
"base_url": "https://api.x.ai/v1",
"seed": 42,
}
@pytest.fixture
def file_names_mapping(self):
return []
def test_initialization(self, component_class):
component = component_class()
assert component.display_name == "xAI"
assert component.description == "Generates text using xAI models like Grok."
assert component.icon == "xAI"
assert component.name == "xAIModel"
def test_template(self, default_kwargs):
component = XAIModelComponent(**default_kwargs)
comp = Component(_code=component._code)
frontend_node, _ = build_custom_component_template(comp)
assert isinstance(frontend_node, dict)
assert "template" in frontend_node
input_names = [inp["name"] for inp in frontend_node["template"].values() if isinstance(inp, dict)]
expected_inputs = [
"max_tokens",
"model_kwargs",
"json_mode",
"model_name",
"base_url",
"api_key",
"temperature",
"seed",
]
for input_name in expected_inputs:
assert input_name in input_names
def test_inputs(self):
component = XAIModelComponent()
inputs = component.inputs
expected_inputs = {
"max_tokens": IntInput,
"model_kwargs": DictInput,
"json_mode": BoolInput,
"model_name": DropdownInput,
"base_url": MessageTextInput,
"api_key": SecretStrInput,
"temperature": SliderInput,
"seed": IntInput,
}
for name, input_type in expected_inputs.items():
matching_inputs = [inp for inp in inputs if isinstance(inp, input_type) and inp.name == name]
assert matching_inputs, f"Missing or incorrect input: {name}"
if name == "model_name":
input_field = matching_inputs[0]
assert input_field.value == "grok-2-latest"
assert input_field.refresh_button is True
elif name == "temperature":
input_field = matching_inputs[0]
assert input_field.value == 0.1
assert input_field.range_spec.min == 0
assert input_field.range_spec.max == 2
def test_build_model(self, component_class, default_kwargs, mocker):
component = component_class(**default_kwargs)
component.temperature = 0.7
component.max_tokens = 100
component.api_key = "test-key"
component.model_name = "grok-2-latest"
component.model_kwargs = {}
component.base_url = "https://api.x.ai/v1"
component.seed = 1
mock_chat_openai = mocker.patch("langflow.components.models.xai.ChatOpenAI", return_value=MagicMock())
model = component.build_model()
mock_chat_openai.assert_called_once_with(
max_tokens=100,
model_kwargs={},
model="grok-2-latest",
base_url="https://api.x.ai/v1",
api_key="test-key",
temperature=0.7,
seed=1,
)
assert model == mock_chat_openai.return_value
def test_get_models(self):
component = XAIModelComponent()
with patch("requests.get") as mock_get:
mock_response = MagicMock()
mock_response.json.return_value = {
"models": [
{"id": "grok-2-latest", "aliases": ["grok-2"]},
{"id": "grok-1", "aliases": []},
]
}
mock_get.return_value = mock_response
component.api_key = "test-key"
models = component.get_models()
assert sorted(models) == ["grok-1", "grok-2", "grok-2-latest"]
mock_get.assert_called_once_with(
"https://api.x.ai/v1/language-models",
headers={
"Authorization": "Bearer test-key",
"Accept": "application/json",
},
timeout=10,
)
def test_get_models_no_api_key(self):
component = XAIModelComponent(api_key=None)
models = component.get_models()
assert models == ["grok-2-latest"]
def test_build_model_error(self, component_class, mocker):
from openai import BadRequestError
component = component_class()
component.api_key = "invalid-key"
component.model_name = "grok-2-latest"
component.temperature = 0.7
component.max_tokens = 100
component.model_kwargs = {}
component.base_url = "https://api.x.ai/v1"
component.seed = 1
mocker.patch(
"langflow.components.models.xai.ChatOpenAI",
side_effect=BadRequestError(
message="Invalid API key",
response=MagicMock(),
body={"message": "Invalid API key provided"},
),
)
with pytest.raises(BadRequestError) as exc_info:
component.build_model()
assert exc_info.value.body["message"] == "Invalid API key provided"
def test_json_mode(self, component_class, mocker):
component = component_class()
component.api_key = "test-key"
component.json_mode = True
component.temperature = 0.7
component.max_tokens = 100
component.model_name = "grok-2-latest"
component.model_kwargs = {}
component.base_url = "https://api.x.ai/v1"
component.seed = 1
mock_instance = MagicMock()
mock_bound_instance = MagicMock()
mock_instance.bind.return_value = mock_bound_instance
mocker.patch("langflow.components.models.xai.ChatOpenAI", return_value=mock_instance)
model = component.build_model()
mock_instance.bind.assert_called_once_with(response_format={"type": "json_object"})
assert model == mock_bound_instance
def test_update_build_config(self):
component = XAIModelComponent()
build_config = {"model_name": {"options": []}}
updated_config = component.update_build_config(build_config, "test-key", "api_key")
assert "model_name" in updated_config
updated_config = component.update_build_config(build_config, "grok-2-latest", "model_name")
assert "model_name" in updated_config

View File

@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import { useMemo } from "react";
import { getNodeOutputColors } from "../../../helpers/get-node-output-colors";
import { getNodeOutputColorsName } from "../../../helpers/get-node-output-colors-name";

View File

@ -1,9 +1,6 @@
import ForwardedIconComponent from "../../components/common/genericIconComponent";
import Checkmark from "../../components/ui/checkmark";
import Loading from "../../components/ui/loading";
import Xmark from "../../components/ui/xmark";
import { BuildStatus } from "../../constants/enums";
import { VertexBuildTypeAPI } from "../../types/api";
const useIconStatus = (buildStatus: BuildStatus | undefined) => {
const conditionError = buildStatus === BuildStatus.ERROR;

View File

@ -18,7 +18,6 @@ import {
CardHeader,
CardTitle,
} from "../../ui/card";
import Loading from "../../ui/loading";
import IconComponent from "../genericIconComponent";
import ShadTooltip from "../shadTooltipComponent";
import useDataEffect from "./hooks/use-data-effect";

View File

@ -1,7 +1,6 @@
import BaseModal from "../../../modals/baseModal";
import { fetchErrorComponentType } from "../../../types/components";
import Loading from "../../ui/loading";
import IconComponent from "../genericIconComponent";
export default function TimeoutErrorComponent({
message,

View File

@ -1,4 +1,3 @@
import ForwardedIconComponent from "@/components/common/genericIconComponent";
import { useLogout } from "@/controllers/API/queries/auth";
import { CustomFeedbackDialog } from "@/customization/components/custom-feedback-dialog";
import { CustomHeaderMenuItemsTitle } from "@/customization/components/custom-header-menu-items-title";

View File

@ -1,5 +1,4 @@
import { FlowType } from "@/types/flow";
import { storeComponent } from "../../../../../types/store";
import { cn } from "../../../../../utils/utils";
import ForwardedIconComponent from "../../../../common/genericIconComponent";
import { Card, CardHeader, CardTitle } from "../../../../ui/card";

View File

@ -2,7 +2,6 @@ import { FlowType } from "@/types/flow";
import { useCallback } from "react";
import { createRoot } from "react-dom/client";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { storeComponent } from "../../../../types/store";
import DragCardComponent from "../components/dragCardComponent";
const useDragStart = (data: FlowType) => {

View File

@ -8,7 +8,6 @@ import { getInputsAndOutputs } from "../../../utils/storeUtils";
import { cn } from "../../../utils/utils";
import IconComponent from "../../common/genericIconComponent";
import ShadTooltip from "../../common/shadTooltipComponent";
import { Button } from "../../ui/button";
import {
Card,
CardDescription,

View File

@ -1,7 +1,6 @@
import { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { tomorrow } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { useDarkStore } from "../../../stores/darkStore";
import IconComponent from "../../common/genericIconComponent";
import { Button } from "../../ui/button";

View File

@ -65,10 +65,12 @@ export default function TableNodeComponent({
};
}, []);
const [selectedNodes, setSelectedNodes] = useState<Array<any>>([]);
const [tempValue, setTempValue] = useState<any[]>(cloneDeep(value));
const [isModalOpen, setIsModalOpen] = useState(false);
const agGrid = useRef<AgGridReact>(null);
const componentColumns = columns
? columns
: generateBackendColumnsFromValue(value ?? [], table_options);
: generateBackendColumnsFromValue(tempValue ?? [], table_options);
let AgColumns = FormatColumns(componentColumns);
// add info to each column
AgColumns = AgColumns.map((col) => {
@ -93,7 +95,7 @@ export default function TableNodeComponent({
if (agGrid.current && !agGrid.current.api.isDestroyed()) {
const rows: any = [];
agGrid.current.api.forEachNode((node) => rows.push(node.data));
handleOnNewValue({ value: rows });
setTempValue(rows);
}
}
function deleteRow() {
@ -109,8 +111,7 @@ export default function TableNodeComponent({
if (agGrid.current && selectedNodes.length > 0) {
const toDuplicate = selectedNodes.map((node) => cloneDeep(node.data));
setSelectedNodes([]);
const rows: any = [];
handleOnNewValue({ value: [...value, ...toDuplicate] });
setTempValue([...tempValue, ...toDuplicate]);
}
}
function addRow() {
@ -118,12 +119,23 @@ export default function TableNodeComponent({
componentColumns.forEach((column) => {
newRow[column.name] = column.default ?? null; // Use the default value if available
});
handleOnNewValue({ value: [...value, newRow] });
setTempValue([...tempValue, newRow]);
}
function updateComponent() {
setAllRows();
}
function handleSave() {
handleOnNewValue({ value: tempValue });
setIsModalOpen(false);
}
function handleCancel() {
setTempValue(cloneDeep(value));
setIsModalOpen(false);
}
const editable = componentColumns
.map((column) => {
const isCustomEdit =
@ -149,6 +161,8 @@ export default function TableNodeComponent({
>
<div className="flex w-full items-center gap-3" data-testid={"div-" + id}>
<TableModal
open={isModalOpen}
setOpen={setIsModalOpen}
stopEditingWhenCellsLoseFocus={true}
tableIcon={table_icon}
tableOptions={table_options}
@ -169,8 +183,10 @@ export default function TableNodeComponent({
displayEmptyAlert={false}
className="h-full w-full"
columnDefs={AgColumns}
rowData={value}
rowData={tempValue}
context={{ field_parsers: table_options?.field_parsers }}
onSave={handleSave}
onCancel={handleCancel}
>
<Button
disabled={disabled}

View File

@ -4,7 +4,6 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "../../../../ui/input";
import { ButtonInputList } from "./components/button-input-list";
import { DropdownMenuInputList } from "./components/dropdown-menu";
import { GRADIENT_CLASS } from "@/constants/constants";
import { cn } from "../../../../../utils/utils";
@ -139,7 +138,7 @@ export default function InputListComponent({
</div>
)}
{/*
{/*
We will add this back in a future release
{!disabled && (
<DropdownMenuInputList

View File

@ -7,7 +7,7 @@ import {
} from "@/utils/reactflowUtils";
import { cn } from "@/utils/utils";
import { cloneDeep } from "lodash";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import IconComponent from "../../../../common/genericIconComponent";
const KeypairListComponent = ({

View File

@ -1,4 +1,3 @@
import { PopoverAnchor } from "@radix-ui/react-popover";
import Fuse from "fuse.js";
import { useEffect, useRef, useState } from "react";
import { cn } from "../../../../../utils/utils";

View File

@ -13,7 +13,6 @@ import {
memo,
useCallback,
useContext,
useEffect,
useId,
useMemo,
} from "react";

View File

@ -6,7 +6,7 @@ import {
TargetAndTransition,
Variants,
} from "framer-motion";
import React, { ReactElement } from "react";
import React from "react";
type PresetType = "blur" | "shake" | "scale" | "fade" | "slide";

View File

@ -3,13 +3,11 @@ import { AxiosRequestConfig, AxiosResponse } from "axios";
import { BASE_URL_API } from "../../constants/constants";
import { api } from "../../controllers/API/api";
import {
Component,
VertexBuildTypeAPI,
VerticesOrderTypeAPI,
} from "../../types/api/index";
import { FlowStyleType, FlowType } from "../../types/flow";
import { StoreComponentResponse } from "../../types/store";
import { FlowPoolType } from "../../types/zustand/flow";
const GITHUB_API_URL = "https://api.github.com";

View File

@ -1,8 +1,4 @@
import {
changeUser,
resetPasswordType,
useMutationFunctionType,
} from "@/types/api";
import { resetPasswordType, useMutationFunctionType } from "@/types/api";
import { UseMutationResult } from "@tanstack/react-query";
import { api } from "../../api";
import { getURL } from "../../helpers/constants";

View File

@ -1,4 +1,4 @@
import { LoginType, changeUser, useMutationFunctionType } from "@/types/api";
import { LoginType, useMutationFunctionType } from "@/types/api";
import { UseMutationResult } from "@tanstack/react-query";
import { api } from "../../api";
import { getURL } from "../../helpers/constants";

View File

@ -1,9 +1,4 @@
import { keepPreviousData } from "@tanstack/react-query";
import {
useMutationFunctionType,
useQueryFunctionType,
} from "../../../../types/api";
import { api } from "../../api";
import { useMutationFunctionType } from "../../../../types/api";
import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor";

View File

@ -2,7 +2,7 @@ import { useGlobalVariablesStore } from "@/stores/globalVariablesStore/globalVar
import getUnavailableFields from "@/stores/globalVariablesStore/utils/get-unavailable-fields";
import { useMutationFunctionType } from "@/types/api";
import { GlobalVariable } from "@/types/global_variables";
import { UseMutationOptions, UseMutationResult } from "@tanstack/react-query";
import { UseMutationResult } from "@tanstack/react-query";
import { api } from "../../api";
import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor";

View File

@ -1,5 +1,4 @@
import { useMutationFunctionType } from "@/types/api";
import { GlobalVariable } from "@/types/global_variables";
import { UseMutationResult } from "@tanstack/react-query";
import { api } from "../../api";
import { getURL } from "../../helpers/constants";

View File

@ -1,4 +1,3 @@
import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows";
import { createFileUpload } from "@/helpers/create-file-upload";
import { getObjectsFromFilelist } from "@/helpers/get-objects-from-filelist";
import useFlowStore from "@/stores/flowStore";

View File

@ -1,5 +1,3 @@
import React from "react";
const ArXivIcon = (props) => {
return (
<svg

View File

@ -1,5 +1,3 @@
import React from "react";
const UpstashIcon = (props) => (
<svg
width="256px"

View File

@ -1,5 +1,3 @@
import React from "react";
const YouTubeIcon = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -0,0 +1,10 @@
import { useDarkStore } from "@/stores/darkStore";
import React, { forwardRef } from "react";
import XAISVG from "./xAIIcon.jsx";
export const XAIIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
(props, ref) => {
const isdark = useDarkStore((state) => state.dark).toString();
return <XAISVG ref={ref} isdark={isdark} {...props} />;
},
);

View File

@ -0,0 +1,19 @@
import { stringToBool } from "@/utils/utils";
const XAISVG = (props) => (
<svg
{...props}
fill={stringToBool(props.isdark) ? "#ffffff" : "#0A0A0A"}
fillRule="evenodd"
height="1em"
style={{ flex: "none", lineHeight: 1 }}
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<title>Grok</title>
<path d="M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z" />
</svg>
);
export default XAISVG;

View File

@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z"></path></svg>

After

Width:  |  Height:  |  Size: 372 B

View File

@ -5,7 +5,7 @@ import useFileSizeValidator from "@/shared/hooks/use-file-size-validator";
import useAlertStore from "@/stores/alertStore";
import useFlowStore from "@/stores/flowStore";
import { useUtilityStore } from "@/stores/utilityStore";
import { useEffect, useRef, useState } from "react";
import { useEffect, useRef } from "react";
import ShortUniqueId from "short-unique-id";
import {
ALLOWED_IMAGE_INPUT_EXTENSIONS,

View File

@ -1,4 +1,3 @@
import { useState } from "react";
import IconComponent, {
ForwardedIconComponent,
} from "../../../../../../components/common/genericIconComponent";

View File

@ -10,7 +10,6 @@ import JsonView from "react18-json-view";
import "react18-json-view/src/dark.css";
import "react18-json-view/src/style.css";
import IconComponent from "../../components/common/genericIconComponent";
import { CODE_DICT_DIALOG_SUBTITLE } from "../../constants/constants";
import { useDarkStore } from "../../stores/darkStore";
import BaseModal from "../baseModal";

View File

@ -1,7 +1,6 @@
import { useNavigate } from "react-router-dom";
import { track } from "../../../../customization/utils/analytics";
import useAddFlow from "../../../../hooks/flows/use-add-flow";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { FlowType } from "../../../../types/flow";
import { updateIds } from "../../../../utils/reactflowUtils";

View File

@ -18,7 +18,6 @@ import {
} from "../../../../components/ui/card";
import { useFolderStore } from "../../../../stores/foldersStore";
import { UndrawCardComponentProps } from "../../../../types/components";
import { updateIds } from "../../../../utils/reactflowUtils";
import { useFlowCardClick } from "../hooks/use-redirect-flow-card-click";
export default function UndrawCardComponent({

View File

@ -15,6 +15,10 @@ interface TableModalProps extends TableComponentProps {
tableOptions?: TableOptionsTypeAPI;
hideColumns?: boolean | string[];
tableIcon?: string;
open?: boolean;
setOpen?: (open: boolean) => void;
onSave?: () => void;
onCancel?: () => void;
}
const TableModal = forwardRef<AgGridReact, TableModalProps>(
@ -25,22 +29,43 @@ const TableModal = forwardRef<AgGridReact, TableModalProps>(
children,
disabled,
tableIcon,
open,
setOpen,
onSave,
onCancel,
...props
}: TableModalProps,
ref: ForwardedRef<AgGridReact>,
) => {
const handleSetOpen = (newOpen: boolean) => {
if (!newOpen && onCancel) {
onCancel();
}
if (setOpen) {
setOpen(newOpen);
}
};
const handleOnEscapeKeyDown = (e: KeyboardEvent) => {
const editingCells = (
ref as React.RefObject<AgGridReact>
)?.current?.api?.getEditingCells();
if (editingCells && editingCells.length > 0) {
e.preventDefault();
}
};
return (
<BaseModal
onEscapeKeyDown={(e) => {
if (
(
ref as React.RefObject<AgGridReact>
)?.current?.api.getEditingCells().length
) {
e.preventDefault();
}
handleOnEscapeKeyDown(e);
}}
disable={disabled}
open={open}
setOpen={(newOpen) => {
handleSetOpen(newOpen);
}}
>
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
<BaseModal.Header
@ -59,6 +84,9 @@ const TableModal = forwardRef<AgGridReact, TableModalProps>(
{...props}
></TableComponent>
</BaseModal.Content>
<BaseModal.Footer
submit={onSave ? { label: "Save", onClick: onSave } : undefined}
></BaseModal.Footer>
</BaseModal>
);
},

View File

@ -1,7 +1,4 @@
import {
ENABLE_INTEGRATIONS,
ENABLE_MVPS,
} from "@/customization/feature-flags";
import { ENABLE_INTEGRATIONS } from "@/customization/feature-flags";
import { useStoreStore } from "@/stores/storeStore";
import { cloneDeep } from "lodash";
import { useEffect, useState } from "react";

View File

@ -1,4 +1,3 @@
import React from "react";
const NoResultsMessage = ({
onClearSearch,
message = "No components found.",

View File

@ -1,6 +1,5 @@
import { Badge } from "@/components/ui/badge";
import { Switch } from "@/components/ui/switch";
import React from "react";
const FeatureToggles = ({
showBeta,

View File

@ -2,7 +2,6 @@ import ForwardedIconComponent from "@/components/common/genericIconComponent";
import { Button } from "@/components/ui/button";
import { SidebarMenuButton } from "@/components/ui/sidebar";
import { CustomLink } from "@/customization/components/custom-link";
import React from "react";
const SidebarMenuButtons = ({
hasStore = false,

View File

@ -1,5 +1,4 @@
import { APIDataType } from "@/types/api";
import { FuseResult } from "fuse.js";
export const filteredDataFn = (
data: APIDataType,

View File

@ -1,5 +1,4 @@
import { APIDataType } from "@/types/api";
import { normalizeString } from "./normalize-string";
import { searchInMetadata } from "./search-on-metadata";
export const traditionalSearchMetadata = (

View File

@ -4,7 +4,7 @@ import EditNodeModal from "@/modals/editNodeModal";
import ShareModal from "@/modals/shareModal";
import { APIClassType } from "@/types/api";
import { FlowType } from "@/types/flow";
import React, { memo } from "react";
import { memo } from "react";
interface ToolbarModalsProps {
// Modal visibility states

View File

@ -1,6 +1,5 @@
import ForwardedIconComponent from "../../../../../components/common/genericIconComponent";
import RenderIcons from "../../../../../components/common/renderIconComponent";
import { IS_MAC } from "../../../../../constants/constants";
import { toolbarSelectItemProps } from "../../../../../types/components";
export default function ToolbarSelectItem({

View File

@ -2,7 +2,7 @@ import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-fol
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import { useFolderStore } from "@/stores/foldersStore";
import { useIsFetching, useIsMutating } from "@tanstack/react-query";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useState } from "react";
import { useLocation, useParams } from "react-router-dom";
import ComponentsComponent from "../componentsComponent";
import HeaderTabsSearchComponent from "./components/headerTabsSearchComponent";

View File

@ -10,7 +10,7 @@ import {
useGetApiKeysQuery,
} from "@/controllers/API/queries/api-keys";
import { SelectionChangedEvent } from "ag-grid-community";
import { useContext, useEffect, useRef, useState } from "react";
import { useContext, useEffect, useState } from "react";
import TableComponent from "../../../../components/core/parameterRenderComponent/components/tableComponent";
import { AuthContext } from "../../../../contexts/authContext";
import useAlertStore from "../../../../stores/alertStore";

View File

@ -1,6 +1,4 @@
import { useGetRefreshFlows } from "@/controllers/API/queries/flows/use-get-refresh-flows";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { useTypesStore } from "@/stores/typesStore";
import { useEffect } from "react";
import { useParams } from "react-router-dom";
import useFlowsManagerStore from "../../stores/flowsManagerStore";

View File

@ -1,5 +1,3 @@
import { FieldParserType, FieldValidatorType } from "../api";
export type getCodesObjProps = {
runCurlCode: string;
webhookCurlCode: string;

View File

@ -1,4 +1,3 @@
import { FlowType } from "@/types/flow";
import { FolderType } from "../../../pages/MainPage/entities";
export type FoldersStoreType = {

View File

@ -1,4 +1,3 @@
import { ColDef, ColGroupDef } from "ag-grid-community";
import { Message } from "../../messages";
export type MessagesStoreType = {

View File

@ -316,6 +316,7 @@ import SvgWolfram from "../icons/Wolfram/Wolfram";
import { HackerNewsIcon } from "../icons/hackerNews";
import { MistralIcon } from "../icons/mistral";
import { SupabaseIcon } from "../icons/supabase";
import { XAIIcon } from "../icons/xAI";
import { iconsType } from "../types/components";
export const BG_NOISE =
"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAUVBMVEWFhYWDg4N3d3dtbW17e3t1dXWBgYGHh4d5eXlzc3OLi4ubm5uVlZWPj4+NjY19fX2JiYl/f39ra2uRkZGZmZlpaWmXl5dvb29xcXGTk5NnZ2c8TV1mAAAAG3RSTlNAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAvEOwtAAAFVklEQVR4XpWWB67c2BUFb3g557T/hRo9/WUMZHlgr4Bg8Z4qQgQJlHI4A8SzFVrapvmTF9O7dmYRFZ60YiBhJRCgh1FYhiLAmdvX0CzTOpNE77ME0Zty/nWWzchDtiqrmQDeuv3powQ5ta2eN0FY0InkqDD73lT9c9lEzwUNqgFHs9VQce3TVClFCQrSTfOiYkVJQBmpbq2L6iZavPnAPcoU0dSw0SUTqz/GtrGuXfbyyBniKykOWQWGqwwMA7QiYAxi+IlPdqo+hYHnUt5ZPfnsHJyNiDtnpJyayNBkF6cWoYGAMY92U2hXHF/C1M8uP/ZtYdiuj26UdAdQQSXQErwSOMzt/XWRWAz5GuSBIkwG1H3FabJ2OsUOUhGC6tK4EMtJO0ttC6IBD3kM0ve0tJwMdSfjZo+EEISaeTr9P3wYrGjXqyC1krcKdhMpxEnt5JetoulscpyzhXN5FRpuPHvbeQaKxFAEB6EN+cYN6xD7RYGpXpNndMmZgM5Dcs3YSNFDHUo2LGfZuukSWyUYirJAdYbF3MfqEKmjM+I2EfhA94iG3L7uKrR+GdWD73ydlIB+6hgref1QTlmgmbM3/LeX5GI1Ux1RWpgxpLuZ2+I+IjzZ8wqE4nilvQdkUdfhzI5QDWy+kw5Wgg2pGpeEVeCCA7b85BO3F9DzxB3cdqvBzWcmzbyMiqhzuYqtHRVG2y4x+KOlnyqla8AoWWpuBoYRxzXrfKuILl6SfiWCbjxoZJUaCBj1CjH7GIaDbc9kqBY3W/Rgjda1iqQcOJu2WW+76pZC9QG7M00dffe9hNnseupFL53r8F7YHSwJWUKP2q+k7RdsxyOB11n0xtOvnW4irMMFNV4H0uqwS5ExsmP9AxbDTc9JwgneAT5vTiUSm1E7BSflSt3bfa1tv8Di3R8n3Af7MNWzs49hmauE2wP+ttrq+AsWpFG2awvsuOqbipWHgtuvuaAE+A1Z/7gC9hesnr+7wqCwG8c5yAg3AL1fm8T9AZtp/bbJGwl1pNrE7RuOX7PeMRUERVaPpEs+yqeoSmuOlokqw49pgomjLeh7icHNlG19yjs6XXOMedYm5xH2YxpV2tc0Ro2jJfxC50ApuxGob7lMsxfTbeUv07TyYxpeLucEH1gNd4IKH2LAg5TdVhlCafZvpskfncCfx8pOhJzd76bJWeYFnFciwcYfubRc12Ip/ppIhA1/mSZ/RxjFDrJC5xifFjJpY2Xl5zXdguFqYyTR1zSp1Y9p+tktDYYSNflcxI0iyO4TPBdlRcpeqjK/piF5bklq77VSEaA+z8qmJTFzIWiitbnzR794USKBUaT0NTEsVjZqLaFVqJoPN9ODG70IPbfBHKK+/q/AWR0tJzYHRULOa4MP+W/HfGadZUbfw177G7j/OGbIs8TahLyynl4X4RinF793Oz+BU0saXtUHrVBFT/DnA3ctNPoGbs4hRIjTok8i+algT1lTHi4SxFvONKNrgQFAq2/gFnWMXgwffgYMJpiKYkmW3tTg3ZQ9Jq+f8XN+A5eeUKHWvJWJ2sgJ1Sop+wwhqFVijqWaJhwtD8MNlSBeWNNWTa5Z5kPZw5+LbVT99wqTdx29lMUH4OIG/D86ruKEauBjvH5xy6um/Sfj7ei6UUVk4AIl3MyD4MSSTOFgSwsH/QJWaQ5as7ZcmgBZkzjjU1UrQ74ci1gWBCSGHtuV1H2mhSnO3Wp/3fEV5a+4wz//6qy8JxjZsmxxy5+4w9CDNJY09T072iKG0EnOS0arEYgXqYnXcYHwjTtUNAcMelOd4xpkoqiTYICWFq0JSiPfPDQdnt+4/wuqcXY47QILbgAAAABJRU5ErkJggg==)";
@ -689,6 +690,7 @@ export const nodeIconsLucide: iconsType = {
OpenAI: OpenAiIcon,
OpenRouter: OpenRouterIcon,
DeepSeek: DeepSeekIcon,
xAI: XAIIcon,
OpenAIEmbeddings: OpenAiIcon,
Pinecone: PineconeIcon,
Qdrant: QDrantIcon,

View File

@ -2,7 +2,6 @@ import { expect, Page, test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
import { evaluateReactStateChanges } from "../../utils/evaluate-input-react-state-changes";
import { initialGPTsetup } from "../../utils/initialGPTsetup";
test(

View File

@ -1,5 +1,4 @@
import { expect, test } from "@playwright/test";
import { adjustScreenView } from "../../utils/adjust-screen-view";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
test(

View File

@ -25,6 +25,10 @@ test(
},
);
await page.waitForSelector('[data-testid="zoom_out"]', {
timeout: 3000,
});
await page.getByTestId("sidebar-custom-component-button").click();
await page.getByTestId("zoom_out").click();
@ -164,7 +168,7 @@ class CustomComponent(Component):
numberOfCopiedRows = await page.getByText(thirdRandomText).count();
expect(numberOfCopiedRows).toBe(0);
await page.getByText("Close").last().click();
await page.getByText("Save").last().click();
await page.waitForSelector("text=Open table", {
timeout: 3000,

View File

@ -1,7 +1,5 @@
import { expect, test } from "@playwright/test";
import { readFileSync } from "fs";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
import { simulateDragAndDrop } from "../../utils/simulate-drag-and-drop";
test(
"user should be able to edit flow name by clicking on the header or on the main page",
{ tag: ["@release"] },

View File

@ -1,4 +1,4 @@
import { expect, Page, test } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { adjustScreenView } from "../../utils/adjust-screen-view";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";

View File

@ -1,4 +1,4 @@
import { expect, test } from "@playwright/test";
import { test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";

View File

@ -1,4 +1,4 @@
import { expect, Page, test } from "@playwright/test";
import { expect, test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";