merge dev
This commit is contained in:
commit
0838baa50a
76 changed files with 2486 additions and 1555 deletions
1
.github/workflows/pre-release.yml
vendored
1
.github/workflows/pre-release.yml
vendored
|
|
@ -38,6 +38,7 @@ jobs:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
draft: false
|
draft: false
|
||||||
generateReleaseNotes: true
|
generateReleaseNotes: true
|
||||||
|
prerelease: true
|
||||||
tag: v${{ steps.check-version.outputs.version }}
|
tag: v${{ steps.check-version.outputs.version }}
|
||||||
commit: main
|
commit: main
|
||||||
- name: Publish to PyPI
|
- name: Publish to PyPI
|
||||||
|
|
|
||||||
6
Makefile
6
Makefile
|
|
@ -41,10 +41,10 @@ run_frontend:
|
||||||
cd src/frontend && npm start
|
cd src/frontend && npm start
|
||||||
|
|
||||||
run_cli:
|
run_cli:
|
||||||
poetry run langflow --path src/frontend/build
|
poetry run langflow run --path src/frontend/build
|
||||||
|
|
||||||
run_cli_debug:
|
run_cli_debug:
|
||||||
poetry run langflow --path src/frontend/build --log-level debug
|
poetry run langflow run --path src/frontend/build --log-level debug
|
||||||
|
|
||||||
setup_devcontainer:
|
setup_devcontainer:
|
||||||
make init
|
make init
|
||||||
|
|
@ -69,7 +69,7 @@ backend:
|
||||||
build_and_run:
|
build_and_run:
|
||||||
echo 'Removing dist folder'
|
echo 'Removing dist folder'
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
make build && poetry run pip install dist/*.tar.gz && poetry run langflow
|
make build && poetry run pip install dist/*.tar.gz && poetry run langflow run
|
||||||
|
|
||||||
build_and_install:
|
build_and_install:
|
||||||
echo 'Removing dist folder'
|
echo 'Removing dist folder'
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ print(run_flow("Your message", flow_id=FLOW_ID, tweaks=TWEAKS))
|
||||||
|
|
||||||
## Deploy on Railway
|
## Deploy on Railway
|
||||||
|
|
||||||
[](https://railway.app/template/Emy2sU?referralCode=MnPSdg)
|
[](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
|
||||||
|
|
||||||
## Deploy on Render
|
## Deploy on Render
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import Admonition from '@theme/Admonition';
|
import Admonition from "@theme/Admonition";
|
||||||
|
|
||||||
# Text Splitters
|
# Text Splitters
|
||||||
|
|
||||||
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
|
||||||
<p>
|
<p>
|
||||||
We appreciate your understanding as we polish our documentation – it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝
|
We appreciate your understanding as we polish our documentation – it may
|
||||||
|
contain some rough edges. Share your feedback or report issues to help us
|
||||||
|
improve! 🛠️📝
|
||||||
</p>
|
</p>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
|
|
@ -44,6 +46,18 @@ The `RecursiveCharacterTextSplitter` splits the text by trying to keep paragra
|
||||||
|
|
||||||
- **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk.
|
- **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk.
|
||||||
|
|
||||||
- **separator_type:** The parameter allows the user to split the code with multiple language support. It supports various languages such as Text, Ruby, Python, Solidity, Java, and more. Defaults to `Text`.
|
- **separators:** The `separators` in RecursiveCharacterTextSplitter are the characters used to split the text into chunks. The text splitter tries to create chunks based on splitting on the first character in the list of `separators`. If any chunks are too large, it moves on to the next character in the list and continues splitting. Defaults to ["\n\n", "\n", " ", ""].
|
||||||
|
|
||||||
- **separators:** The `separators` in RecursiveCharacterTextSplitter are the characters used to split the text into chunks. The text splitter tries to create chunks based on splitting on the first character in the list of `separators`. If any chunks are too large, it moves on to the next character in the list and continues splitting. Defaults to `.`
|
### LanguageRecursiveTextSplitter
|
||||||
|
|
||||||
|
The `LanguageRecursiveTextSplitter` is a text splitter that splits the text into smaller chunks based on the (programming) language of the text.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
- **Documents:** Input documents to split.
|
||||||
|
|
||||||
|
- **chunk_overlap:** Determines the number of characters that overlap between consecutive chunks when splitting text. It specifies how much of the previous chunk should be included in the next chunk.
|
||||||
|
|
||||||
|
- **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk.
|
||||||
|
|
||||||
|
- **separator_type:** The parameter allows the user to split the code with multiple language support. It supports various languages such as Ruby, Python, Solidity, Java, and more. Defaults to `Python`.
|
||||||
|
|
|
||||||
49
docs/docs/guides/langfuse_integration.mdx
Normal file
49
docs/docs/guides/langfuse_integration.mdx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Integrating Langfuse with Langflow
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Langfuse is an open-source tracing and analytics tool designed for LLM applications. Integrating Langfuse with Langflow provides detailed production traces and granular insights into quality, cost, and latency. This integration allows you to monitor and debug your Langflow's chat or APIs easily.
|
||||||
|
|
||||||
|
## Step-by-Step Instructions
|
||||||
|
|
||||||
|
### Step 1: Create a Langfuse account
|
||||||
|
|
||||||
|
1. Go to [Langfuse](https://langfuse.com) and click on the "Sign In" button in the top right corner.
|
||||||
|
2. Click on the "Sign Up" button and create an account.
|
||||||
|
3. Once logged in, click on "Settings" and then on "Create new API keys."
|
||||||
|
4. Copy the Public key and the Secret Key and save them somewhere safe.
|
||||||
|
{/* Add these keys to your environment variables in the following step. */}
|
||||||
|
|
||||||
|
### Step 2: Set up Langfuse in Langflow
|
||||||
|
|
||||||
|
1. **Export the Environment Variables**: You'll need to export the environment variables `LANGFLOW_LANGFUSE_SECRET_KEY` and `LANGFLOW_LANGFUSE_PUBLIC_KEY` with the values obtained in Step 1.
|
||||||
|
|
||||||
|
You can do this by executing the following commands in your terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LANGFLOW_LANGFUSE_SECRET_KEY=<your secret key>
|
||||||
|
export LANGFLOW_LANGFUSE_PUBLIC_KEY=<your public key>
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can run the Langflow CLI command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LANGFLOW_LANGFUSE_SECRET_KEY=<your secret key> LANGFLOW_LANGFUSE_PUBLIC_KEY=<your public key> langflow
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are self-hosting Langfuse, you can also set the environment variable `LANGFLOW_LANGFUSE_HOST` to point to your Langfuse instance. By default, Langfuse points to the cloud instance at `https://cloud.langfuse.com`.
|
||||||
|
|
||||||
|
2. **Verify Integration**: Ensure that the environment variables are set correctly by checking their existence in your environment, for example by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo $LANGFLOW_LANGFUSE_SECRET_KEY
|
||||||
|
echo $LANGFLOW_LANGFUSE_PUBLIC_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Monitor Langflow**: Now, whenever you use Langflow's chat or API, you will be able to see the tracing of your conversations in Langfuse.
|
||||||
|
|
||||||
|
That's it! You have successfully integrated Langfuse with Langflow, enhancing observability and debugging capabilities for your LLM application.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Note: For more details or customized configurations, please refer to the official [Langfuse documentation](https://langfuse.com/docs/integrations/langchain).
|
||||||
|
|
@ -51,7 +51,11 @@ module.exports = {
|
||||||
type: "category",
|
type: "category",
|
||||||
label: "Step-by-Step Guides",
|
label: "Step-by-Step Guides",
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
items: ["guides/loading_document", "guides/chatprompttemplate_guide"],
|
items: [
|
||||||
|
"guides/loading_document",
|
||||||
|
"guides/chatprompttemplate_guide",
|
||||||
|
"guides/langfuse_integration",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// type: 'category',
|
// type: 'category',
|
||||||
|
|
|
||||||
1444
poetry.lock
generated
1444
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -32,17 +32,17 @@ beautifulsoup4 = "^4.12.2"
|
||||||
google-search-results = "^2.4.1"
|
google-search-results = "^2.4.1"
|
||||||
google-api-python-client = "^2.79.0"
|
google-api-python-client = "^2.79.0"
|
||||||
typer = "^0.9.0"
|
typer = "^0.9.0"
|
||||||
gunicorn = "^21.1.0"
|
gunicorn = "^21.2.0"
|
||||||
langchain = "^0.0.274"
|
langchain = "^0.0.274"
|
||||||
openai = "^0.27.8"
|
openai = "^0.27.8"
|
||||||
pandas = "^2.0.0"
|
pandas = "^2.0.0"
|
||||||
chromadb = "^0.3.21"
|
chromadb = "^0.3.0"
|
||||||
huggingface-hub = { version = "^0.16.0", extras = ["inference"] }
|
huggingface-hub = { version = "^0.16.0", extras = ["inference"] }
|
||||||
rich = "^13.4.2"
|
rich = "^13.5.0"
|
||||||
llama-cpp-python = { version = "~0.1.0", optional = true }
|
llama-cpp-python = { version = "~0.1.0", optional = true }
|
||||||
networkx = "^3.1"
|
networkx = "^3.1"
|
||||||
unstructured = "^0.7.0"
|
unstructured = "^0.10.0"
|
||||||
pypdf = "^3.11.0"
|
pypdf = "^3.15.0"
|
||||||
lxml = "^4.9.2"
|
lxml = "^4.9.2"
|
||||||
pysrt = "^1.1.2"
|
pysrt = "^1.1.2"
|
||||||
fake-useragent = "^1.2.1"
|
fake-useragent = "^1.2.1"
|
||||||
|
|
@ -52,13 +52,13 @@ pyarrow = "^12.0.0"
|
||||||
tiktoken = "~0.4.0"
|
tiktoken = "~0.4.0"
|
||||||
wikipedia = "^1.4.0"
|
wikipedia = "^1.4.0"
|
||||||
langchain-serve = { version = ">0.0.51", optional = true }
|
langchain-serve = { version = ">0.0.51", optional = true }
|
||||||
qdrant-client = "^1.3.0"
|
qdrant-client = "^1.4.0"
|
||||||
websockets = "^10.3"
|
websockets = "^10.3"
|
||||||
weaviate-client = "^3.21.0"
|
weaviate-client = "^3.23.0"
|
||||||
jina = "3.15.2"
|
jina = "3.15.2"
|
||||||
sentence-transformers = { version = "^2.2.2", optional = true }
|
sentence-transformers = { version = "^2.2.2", optional = true }
|
||||||
ctransformers = { version = "^0.2.10", optional = true }
|
ctransformers = { version = "^0.2.10", optional = true }
|
||||||
cohere = "^4.11.0"
|
cohere = "^4.21.0"
|
||||||
python-multipart = "^0.0.6"
|
python-multipart = "^0.0.6"
|
||||||
sqlmodel = "^0.0.8"
|
sqlmodel = "^0.0.8"
|
||||||
faiss-cpu = "^1.7.4"
|
faiss-cpu = "^1.7.4"
|
||||||
|
|
@ -82,9 +82,12 @@ passlib = "^1.7.4"
|
||||||
bcrypt = "^4.0.1"
|
bcrypt = "^4.0.1"
|
||||||
python-jose = "^3.3.0"
|
python-jose = "^3.3.0"
|
||||||
metaphor-python = "^0.1.11"
|
metaphor-python = "^0.1.11"
|
||||||
markupsafe = "^2.1.3"
|
|
||||||
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
|
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
|
||||||
loguru = "^0.7.1"
|
loguru = "^0.7.1"
|
||||||
|
langfuse = "^1.0.13"
|
||||||
|
pillow = "^10.0.0"
|
||||||
|
metal-sdk = "^2.0.2"
|
||||||
|
markupsafe = "^2.1.3"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^23.1.0"
|
black = "^23.1.0"
|
||||||
|
|
@ -105,6 +108,7 @@ types-passlib = "^1.7.7.13"
|
||||||
pytest-mock = "^3.11.1"
|
pytest-mock = "^3.11.1"
|
||||||
pytest-xdist = "^3.3.1"
|
pytest-xdist = "^3.3.1"
|
||||||
types-pywin32 = "^306.0.0.4"
|
types-pywin32 = "^306.0.0.4"
|
||||||
|
types-google-cloud-ndb = "^2.2.0.0"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,14 @@ services:
|
||||||
- type: web
|
- type: web
|
||||||
name: langflow
|
name: langflow
|
||||||
runtime: docker
|
runtime: docker
|
||||||
plan: free
|
|
||||||
dockerfilePath: ./Dockerfile
|
dockerfilePath: ./Dockerfile
|
||||||
repo: https://github.com/logspace-ai/langflow
|
repo: https://github.com/logspace-ai/langflow
|
||||||
branch: main
|
branch: main
|
||||||
healthCheckPath: /health
|
healthCheckPath: /health
|
||||||
autoDeploy: false
|
autoDeploy: false
|
||||||
|
envVars:
|
||||||
|
- key: LANGFLOW_DATABASE_URL
|
||||||
|
value: sqlite:////home/user/.cache/langflow/langflow.db
|
||||||
|
disk:
|
||||||
|
name: langflow-data
|
||||||
|
mountPath: /home/user/.cache/langflow
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
"""Add profile-image column
|
||||||
|
|
||||||
|
Revision ID: 67cc006d50bf
|
||||||
|
Revises: 260dbcc8b680
|
||||||
|
Create Date: 2023-09-08 07:36:13.387318
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
from sqlalchemy.engine.reflection import Inspector
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "67cc006d50bf"
|
||||||
|
down_revision: Union[str, None] = "260dbcc8b680"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = Inspector.from_engine(conn)
|
||||||
|
if "user" in inspector.get_table_names() and "profile_image" not in [
|
||||||
|
column["name"] for column in inspector.get_columns("user")
|
||||||
|
]:
|
||||||
|
with op.batch_alter_table("user", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column(
|
||||||
|
"profile_image", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = Inspector.from_engine(conn)
|
||||||
|
if "user" in inspector.get_table_names() and "profile_image" in [
|
||||||
|
column["name"] for column in inspector.get_columns("user")
|
||||||
|
]:
|
||||||
|
with op.batch_alter_table("user", schema=None) as batch_op:
|
||||||
|
batch_op.drop_column("profile_image")
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
@ -13,7 +13,6 @@ from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, St
|
||||||
|
|
||||||
from langflow.graph.graph.base import Graph
|
from langflow.graph.graph.base import Graph
|
||||||
from langflow.services.auth.utils import get_current_active_user, get_current_user
|
from langflow.services.auth.utils import get_current_active_user, get_current_user
|
||||||
from langflow.services.utils import get_session
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from langflow.services.utils import get_chat_manager, get_session
|
from langflow.services.utils import get_chat_manager, get_session
|
||||||
from cachetools import LRUCache
|
from cachetools import LRUCache
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,17 @@ from langflow.services.auth.utils import (
|
||||||
get_current_active_superuser,
|
get_current_active_superuser,
|
||||||
get_current_active_user,
|
get_current_active_user,
|
||||||
get_password_hash,
|
get_password_hash,
|
||||||
|
verify_password,
|
||||||
)
|
)
|
||||||
from langflow.services.database.models.user.crud import (
|
from langflow.services.database.models.user.crud import (
|
||||||
|
get_user_by_id,
|
||||||
update_user,
|
update_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter(tags=["Users"])
|
router = APIRouter(tags=["Users"], prefix="/users")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/user", response_model=UserRead, status_code=201)
|
@router.post("/", response_model=UserRead, status_code=201)
|
||||||
def add_user(
|
def add_user(
|
||||||
user: UserCreate,
|
user: UserCreate,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
|
|
@ -50,7 +52,7 @@ def add_user(
|
||||||
return new_user
|
return new_user
|
||||||
|
|
||||||
|
|
||||||
@router.get("/user", response_model=UserRead)
|
@router.get("/whoami", response_model=UserRead)
|
||||||
def read_current_user(
|
def read_current_user(
|
||||||
current_user: User = Depends(get_current_active_user),
|
current_user: User = Depends(get_current_active_user),
|
||||||
) -> User:
|
) -> User:
|
||||||
|
|
@ -60,7 +62,7 @@ def read_current_user(
|
||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
@router.get("/users", response_model=UsersResponse)
|
@router.get("/", response_model=UsersResponse)
|
||||||
def read_all_users(
|
def read_all_users(
|
||||||
skip: int = 0,
|
skip: int = 0,
|
||||||
limit: int = 10,
|
limit: int = 10,
|
||||||
|
|
@ -82,20 +84,61 @@ def read_all_users(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/user/{user_id}", response_model=UserRead)
|
@router.patch("/{user_id}", response_model=UserRead)
|
||||||
def patch_user(
|
def patch_user(
|
||||||
user_id: UUID,
|
user_id: UUID,
|
||||||
user: UserUpdate,
|
user_update: UserUpdate,
|
||||||
_: Session = Depends(get_current_active_user),
|
user: User = Depends(get_current_active_user),
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
) -> User:
|
) -> User:
|
||||||
"""
|
"""
|
||||||
Update an existing user's data.
|
Update an existing user's data.
|
||||||
"""
|
"""
|
||||||
return update_user(user_id, user, session)
|
if not user.is_superuser and user.id != user_id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403, detail="You don't have the permission to update this user"
|
||||||
|
)
|
||||||
|
if user_update.password:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="You can't change your password here"
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_db := get_user_by_id(session, user_id):
|
||||||
|
return update_user(user_db, user_update, session)
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/user/{user_id}")
|
@router.patch("/{user_id}/reset-password", response_model=UserRead)
|
||||||
|
def reset_password(
|
||||||
|
user_id: UUID,
|
||||||
|
user_update: UserUpdate,
|
||||||
|
user: User = Depends(get_current_active_user),
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
) -> User:
|
||||||
|
"""
|
||||||
|
Reset a user's password.
|
||||||
|
"""
|
||||||
|
if user_id != user.id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="You can't change another user's password"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
if verify_password(user_update.password, user.password):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="You can't use your current password"
|
||||||
|
)
|
||||||
|
new_password = get_password_hash(user_update.password)
|
||||||
|
user.password = new_password
|
||||||
|
session.commit()
|
||||||
|
session.refresh(user)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{user_id}", response_model=dict)
|
||||||
def delete_user(
|
def delete_user(
|
||||||
user_id: UUID,
|
user_id: UUID,
|
||||||
current_user: User = Depends(get_current_active_superuser),
|
current_user: User = Depends(get_current_active_superuser),
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ class ConversationalAgent(CustomComponent):
|
||||||
self,
|
self,
|
||||||
model_name: str,
|
model_name: str,
|
||||||
openai_api_key: str,
|
openai_api_key: str,
|
||||||
openai_api_base: str,
|
|
||||||
tools: Tool,
|
tools: Tool,
|
||||||
|
openai_api_base: Optional[str] = None,
|
||||||
memory: Optional[BaseMemory] = None,
|
memory: Optional[BaseMemory] = None,
|
||||||
system_message: Optional[SystemMessagePromptTemplate] = None,
|
system_message: Optional[SystemMessagePromptTemplate] = None,
|
||||||
max_token_limit: int = 2000,
|
max_token_limit: int = 2000,
|
||||||
|
|
|
||||||
42
src/backend/langflow/components/llms/HuggingFaceEndpoints.py
Normal file
42
src/backend/langflow/components/llms/HuggingFaceEndpoints.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.llms import HuggingFaceEndpoint
|
||||||
|
from langchain.llms.base import BaseLLM
|
||||||
|
|
||||||
|
|
||||||
|
class HuggingFaceEndpointsComponent(CustomComponent):
|
||||||
|
display_name: str = "Hugging Face Inference API"
|
||||||
|
description: str = "LLM model from Hugging Face Inference API."
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"endpoint_url": {"display_name": "Endpoint URL", "password": True},
|
||||||
|
"task": {
|
||||||
|
"display_name": "Task",
|
||||||
|
"type": "select",
|
||||||
|
"options": ["text2text-generation", "text-generation", "summarization"],
|
||||||
|
},
|
||||||
|
"huggingfacehub_api_token": {"display_name": "API token", "password": True},
|
||||||
|
"model_kwargs": {
|
||||||
|
"display_name": "Model Keyword Arguments",
|
||||||
|
"field_type": "code",
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
endpoint_url: str,
|
||||||
|
task="text2text-generation",
|
||||||
|
huggingfacehub_api_token: Optional[str] = None,
|
||||||
|
model_kwargs: Optional[dict] = None,
|
||||||
|
) -> BaseLLM:
|
||||||
|
try:
|
||||||
|
output = HuggingFaceEndpoint(
|
||||||
|
endpoint_url=endpoint_url,
|
||||||
|
task=task,
|
||||||
|
huggingfacehub_api_token=huggingfacehub_api_token,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Could not connect to HuggingFace Endpoints API.") from e
|
||||||
|
return output
|
||||||
0
src/backend/langflow/components/llms/__init__.py
Normal file
0
src/backend/langflow/components/llms/__init__.py
Normal file
28
src/backend/langflow/components/retrievers/MetalRetriever.py
Normal file
28
src/backend/langflow/components/retrievers/MetalRetriever.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.retrievers import MetalRetriever
|
||||||
|
from langchain.schema import BaseRetriever
|
||||||
|
from metal_sdk.metal import Metal # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class MetalRetrieverComponent(CustomComponent):
|
||||||
|
display_name: str = "Metal Retriever"
|
||||||
|
description: str = "Retriever that uses the Metal API."
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"api_key": {"display_name": "API Key", "password": True},
|
||||||
|
"client_id": {"display_name": "Client ID", "password": True},
|
||||||
|
"index_id": {"display_name": "Index ID"},
|
||||||
|
"params": {"display_name": "Parameters", "field_type": "code"},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None
|
||||||
|
) -> BaseRetriever:
|
||||||
|
try:
|
||||||
|
metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Could not connect to Metal API.") from e
|
||||||
|
return MetalRetriever(client=metal, params=params or {})
|
||||||
0
src/backend/langflow/components/retrievers/__init__.py
Normal file
0
src/backend/langflow/components/retrievers/__init__.py
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.text_splitter import Language
|
||||||
|
from langchain.schema import Document
|
||||||
|
|
||||||
|
|
||||||
|
class LanguageRecursiveTextSplitterComponent(CustomComponent):
|
||||||
|
display_name: str = "Language Recursive Text Splitter"
|
||||||
|
description: str = "Split text into chunks of a specified length based on language."
|
||||||
|
documentation: str = "https://docs.langflow.org/components/text-splitters#languagerecursivetextsplitter"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
options = [x.value for x in Language]
|
||||||
|
return {
|
||||||
|
"documents": {
|
||||||
|
"display_name": "Documents",
|
||||||
|
"info": "The documents to split.",
|
||||||
|
},
|
||||||
|
"separator_type": {
|
||||||
|
"display_name": "Separator Type",
|
||||||
|
"info": "The type of separator to use.",
|
||||||
|
"field_type": "str",
|
||||||
|
"options": options,
|
||||||
|
"value": "Python",
|
||||||
|
},
|
||||||
|
"separators": {
|
||||||
|
"display_name": "Separators",
|
||||||
|
"info": "The characters to split on.",
|
||||||
|
"is_list": True,
|
||||||
|
},
|
||||||
|
"chunk_size": {
|
||||||
|
"display_name": "Chunk Size",
|
||||||
|
"info": "The maximum length of each chunk.",
|
||||||
|
"field_type": "int",
|
||||||
|
"value": 1000,
|
||||||
|
},
|
||||||
|
"chunk_overlap": {
|
||||||
|
"display_name": "Chunk Overlap",
|
||||||
|
"info": "The amount of overlap between chunks.",
|
||||||
|
"field_type": "int",
|
||||||
|
"value": 200,
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
documents: list[Document],
|
||||||
|
chunk_size: Optional[int] = 1000,
|
||||||
|
chunk_overlap: Optional[int] = 200,
|
||||||
|
separator_type: Optional[str] = "Python",
|
||||||
|
) -> list[Document]:
|
||||||
|
"""
|
||||||
|
Split text into chunks of a specified length.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
separators (list[str]): The characters to split on.
|
||||||
|
chunk_size (int): The maximum length of each chunk.
|
||||||
|
chunk_overlap (int): The amount of overlap between chunks.
|
||||||
|
length_function (function): The function to use to calculate the length of the text.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[str]: The chunks of text.
|
||||||
|
"""
|
||||||
|
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||||
|
|
||||||
|
# Make sure chunk_size and chunk_overlap are ints
|
||||||
|
if isinstance(chunk_size, str):
|
||||||
|
chunk_size = int(chunk_size)
|
||||||
|
if isinstance(chunk_overlap, str):
|
||||||
|
chunk_overlap = int(chunk_overlap)
|
||||||
|
|
||||||
|
splitter = RecursiveCharacterTextSplitter.from_language(
|
||||||
|
language=Language(separator_type),
|
||||||
|
chunk_size=chunk_size,
|
||||||
|
chunk_overlap=chunk_overlap,
|
||||||
|
)
|
||||||
|
|
||||||
|
docs = splitter.split_documents(documents)
|
||||||
|
return docs
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.schema import Document
|
||||||
|
from langflow.utils.util import build_loader_repr_from_documents
|
||||||
|
|
||||||
|
|
||||||
|
class RecursiveCharacterTextSplitterComponent(CustomComponent):
|
||||||
|
display_name: str = "Recursive Character Text Splitter"
|
||||||
|
description: str = "Split text into chunks of a specified length."
|
||||||
|
documentation: str = "https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"documents": {
|
||||||
|
"display_name": "Documents",
|
||||||
|
"info": "The documents to split.",
|
||||||
|
},
|
||||||
|
"separators": {
|
||||||
|
"display_name": "Separators",
|
||||||
|
"info": 'The characters to split on.\nIf left empty defaults to ["\\n\\n", "\\n", " ", ""].',
|
||||||
|
"is_list": True,
|
||||||
|
},
|
||||||
|
"chunk_size": {
|
||||||
|
"display_name": "Chunk Size",
|
||||||
|
"info": "The maximum length of each chunk.",
|
||||||
|
"field_type": "int",
|
||||||
|
"value": 1000,
|
||||||
|
},
|
||||||
|
"chunk_overlap": {
|
||||||
|
"display_name": "Chunk Overlap",
|
||||||
|
"info": "The amount of overlap between chunks.",
|
||||||
|
"field_type": "int",
|
||||||
|
"value": 200,
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
documents: list[Document],
|
||||||
|
separators: Optional[list[str]] = None,
|
||||||
|
chunk_size: Optional[int] = 1000,
|
||||||
|
chunk_overlap: Optional[int] = 200,
|
||||||
|
) -> list[Document]:
|
||||||
|
"""
|
||||||
|
Split text into chunks of a specified length.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
separators (list[str]): The characters to split on.
|
||||||
|
chunk_size (int): The maximum length of each chunk.
|
||||||
|
chunk_overlap (int): The amount of overlap between chunks.
|
||||||
|
length_function (function): The function to use to calculate the length of the text.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[str]: The chunks of text.
|
||||||
|
"""
|
||||||
|
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||||
|
|
||||||
|
if separators == "":
|
||||||
|
separators = None
|
||||||
|
elif separators:
|
||||||
|
# check if the separators list has escaped characters
|
||||||
|
# if there are escaped characters, unescape them
|
||||||
|
separators = [x.encode().decode("unicode-escape") for x in separators]
|
||||||
|
|
||||||
|
# Make sure chunk_size and chunk_overlap are ints
|
||||||
|
if isinstance(chunk_size, str):
|
||||||
|
chunk_size = int(chunk_size)
|
||||||
|
if isinstance(chunk_overlap, str):
|
||||||
|
chunk_overlap = int(chunk_overlap)
|
||||||
|
splitter = RecursiveCharacterTextSplitter(
|
||||||
|
separators=separators,
|
||||||
|
chunk_size=chunk_size,
|
||||||
|
chunk_overlap=chunk_overlap,
|
||||||
|
)
|
||||||
|
|
||||||
|
docs = splitter.split_documents(documents)
|
||||||
|
self.repr_value = build_loader_repr_from_documents(docs)
|
||||||
|
return docs
|
||||||
|
|
@ -171,8 +171,6 @@ prompts:
|
||||||
textsplitters:
|
textsplitters:
|
||||||
CharacterTextSplitter:
|
CharacterTextSplitter:
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter"
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter"
|
||||||
RecursiveCharacterTextSplitter:
|
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/recursive_text_splitter"
|
|
||||||
toolkits:
|
toolkits:
|
||||||
OpenAPIToolkit:
|
OpenAPIToolkit:
|
||||||
documentation: ""
|
documentation: ""
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from langflow.api.utils import merge_nested_dicts_with_renaming
|
||||||
from langflow.interface.agents.base import agent_creator
|
from langflow.interface.agents.base import agent_creator
|
||||||
from langflow.interface.chains.base import chain_creator
|
from langflow.interface.chains.base import chain_creator
|
||||||
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
||||||
|
from langflow.interface.custom.utils import extract_inner_type
|
||||||
from langflow.interface.document_loaders.base import documentloader_creator
|
from langflow.interface.document_loaders.base import documentloader_creator
|
||||||
from langflow.interface.embeddings.base import embedding_creator
|
from langflow.interface.embeddings.base import embedding_creator
|
||||||
from langflow.interface.importing.utils import get_function_custom
|
from langflow.interface.importing.utils import get_function_custom
|
||||||
|
|
@ -84,6 +85,8 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
|
||||||
|
|
||||||
|
|
||||||
def process_type(field_type: str):
|
def process_type(field_type: str):
|
||||||
|
if field_type.startswith("list") or field_type.startswith("List"):
|
||||||
|
return extract_inner_type(field_type)
|
||||||
return "prompt" if field_type == "Prompt" else field_type
|
return "prompt" if field_type == "Prompt" else field_type
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -100,6 +103,7 @@ def add_new_custom_field(
|
||||||
# if it is, update the value
|
# if it is, update the value
|
||||||
display_name = field_config.pop("display_name", field_name)
|
display_name = field_config.pop("display_name", field_name)
|
||||||
field_type = field_config.pop("field_type", field_type)
|
field_type = field_config.pop("field_type", field_type)
|
||||||
|
field_contains_list = "list" in field_type.lower()
|
||||||
field_type = process_type(field_type)
|
field_type = process_type(field_type)
|
||||||
field_value = field_config.pop("value", field_value)
|
field_value = field_config.pop("value", field_value)
|
||||||
field_advanced = field_config.pop("advanced", False)
|
field_advanced = field_config.pop("advanced", False)
|
||||||
|
|
@ -110,7 +114,9 @@ def add_new_custom_field(
|
||||||
# If options is a list, then it's a dropdown
|
# If options is a list, then it's a dropdown
|
||||||
# If options is None, then it's a list of strings
|
# If options is None, then it's a list of strings
|
||||||
is_list = isinstance(field_config.get("options"), list)
|
is_list = isinstance(field_config.get("options"), list)
|
||||||
field_config["is_list"] = is_list or field_config.get("is_list", False)
|
field_config["is_list"] = (
|
||||||
|
is_list or field_config.get("is_list", False) or field_contains_list
|
||||||
|
)
|
||||||
|
|
||||||
if "name" in field_config:
|
if "name" in field_config:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
|
|
@ -172,7 +178,7 @@ def extract_type_from_optional(field_type):
|
||||||
Returns:
|
Returns:
|
||||||
str: The extracted type, or an empty string if no type was found.
|
str: The extracted type, or an empty string if no type was found.
|
||||||
"""
|
"""
|
||||||
match = re.search(r"\[(.*?)\]", field_type)
|
match = re.search(r"\[(.*?)\]$", field_type)
|
||||||
return match[1] if match else None
|
return match[1] if match else None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from langflow.api import router
|
||||||
from langflow.interface.utils import setup_llm_caching
|
from langflow.interface.utils import setup_llm_caching
|
||||||
from langflow.services.database.utils import initialize_database
|
from langflow.services.database.utils import initialize_database
|
||||||
from langflow.services.manager import initialize_services, teardown_services
|
from langflow.services.manager import initialize_services, teardown_services
|
||||||
|
from langflow.services.plugins.langfuse import LangfuseInstance
|
||||||
from langflow.utils.logger import configure
|
from langflow.utils.logger import configure
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -41,6 +42,8 @@ def create_app():
|
||||||
app.on_event("startup")(initialize_database)
|
app.on_event("startup")(initialize_database)
|
||||||
app.on_event("startup")(setup_llm_caching)
|
app.on_event("startup")(setup_llm_caching)
|
||||||
app.on_event("shutdown")(teardown_services)
|
app.on_event("shutdown")(teardown_services)
|
||||||
|
app.on_event("startup")(LangfuseInstance.update)
|
||||||
|
app.on_event("shutdown")(LangfuseInstance.teardown)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Union
|
from typing import List, Union, TYPE_CHECKING
|
||||||
from langflow.api.v1.callback import (
|
from langflow.api.v1.callback import (
|
||||||
AsyncStreamingLLMCallbackHandler,
|
AsyncStreamingLLMCallbackHandler,
|
||||||
StreamingLLMCallbackHandler,
|
StreamingLLMCallbackHandler,
|
||||||
|
|
@ -6,6 +6,52 @@ from langflow.api.v1.callback import (
|
||||||
from langflow.processing.process import fix_memory_inputs, format_actions
|
from langflow.processing.process import fix_memory_inputs, format_actions
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from langchain.agents.agent import AgentExecutor
|
from langchain.agents.agent import AgentExecutor
|
||||||
|
from langchain.callbacks.base import BaseCallbackHandler
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from langfuse.callback import CallbackHandler # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def setup_callbacks(sync, trace_id, **kwargs):
|
||||||
|
"""Setup callbacks for langchain object"""
|
||||||
|
callbacks = []
|
||||||
|
if sync:
|
||||||
|
callbacks.append(StreamingLLMCallbackHandler(**kwargs))
|
||||||
|
else:
|
||||||
|
callbacks.append(AsyncStreamingLLMCallbackHandler(**kwargs))
|
||||||
|
|
||||||
|
if langfuse_callback := get_langfuse_callback(trace_id=trace_id):
|
||||||
|
logger.debug("Langfuse callback loaded")
|
||||||
|
callbacks.append(langfuse_callback)
|
||||||
|
return callbacks
|
||||||
|
|
||||||
|
|
||||||
|
def get_langfuse_callback(trace_id):
|
||||||
|
from langflow.services.plugins.langfuse import LangfuseInstance
|
||||||
|
from langfuse.callback import CreateTrace
|
||||||
|
|
||||||
|
logger.debug("Initializing langfuse callback")
|
||||||
|
if langfuse := LangfuseInstance.get():
|
||||||
|
logger.debug("Langfuse credentials found")
|
||||||
|
try:
|
||||||
|
trace = langfuse.trace(CreateTrace(id=trace_id))
|
||||||
|
return trace.getNewHandler()
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error initializing langfuse callback: {exc}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def flush_langfuse_callback_if_present(
|
||||||
|
callbacks: List[Union[BaseCallbackHandler, "CallbackHandler"]]
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
If langfuse callback is present, run callback.langfuse.flush()
|
||||||
|
"""
|
||||||
|
for callback in callbacks:
|
||||||
|
if hasattr(callback, "langfuse"):
|
||||||
|
callback.langfuse.flush()
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwargs):
|
async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwargs):
|
||||||
|
|
@ -27,13 +73,18 @@ async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwa
|
||||||
logger.error(f"Error fixing memory inputs: {exc}")
|
logger.error(f"Error fixing memory inputs: {exc}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)]
|
trace_id = kwargs.pop("session_id", None)
|
||||||
output = await langchain_object.acall(inputs, callbacks=async_callbacks)
|
callbacks = setup_callbacks(sync=False, trace_id=trace_id, **kwargs)
|
||||||
|
output = await langchain_object.acall(inputs, callbacks=callbacks)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# make the error message more informative
|
# make the error message more informative
|
||||||
logger.debug(f"Error: {str(exc)}")
|
logger.debug(f"Error: {str(exc)}")
|
||||||
sync_callbacks = [StreamingLLMCallbackHandler(**kwargs)]
|
trace_id = kwargs.pop("session_id", None)
|
||||||
output = langchain_object(inputs, callbacks=sync_callbacks)
|
callbacks = setup_callbacks(sync=True, trace_id=trace_id, **kwargs)
|
||||||
|
output = langchain_object(inputs, callbacks=callbacks)
|
||||||
|
|
||||||
|
# if langfuse callback is present, run callback.langfuse.flush()
|
||||||
|
flush_langfuse_callback_if_present(callbacks)
|
||||||
|
|
||||||
intermediate_steps = (
|
intermediate_steps = (
|
||||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from langflow.graph import Graph
|
||||||
from langchain.chains.base import Chain
|
from langchain.chains.base import Chain
|
||||||
from langchain.vectorstores.base import VectorStore
|
from langchain.vectorstores.base import VectorStore
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
|
from langchain.schema import Document
|
||||||
|
|
||||||
|
|
||||||
def fix_memory_inputs(langchain_object):
|
def fix_memory_inputs(langchain_object):
|
||||||
|
|
@ -142,6 +143,8 @@ def generate_result(langchain_object: Union[Chain, VectorStore], inputs: dict):
|
||||||
logger.debug("Generated result and thought")
|
logger.debug("Generated result and thought")
|
||||||
elif isinstance(langchain_object, VectorStore):
|
elif isinstance(langchain_object, VectorStore):
|
||||||
result = langchain_object.search(**inputs)
|
result = langchain_object.search(**inputs)
|
||||||
|
elif isinstance(langchain_object, Document):
|
||||||
|
result = langchain_object.dict()
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Unknown langchain_object type: {type(langchain_object).__name__}"
|
f"Unknown langchain_object type: {type(langchain_object).__name__}"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
import uuid
|
||||||
from fastapi import WebSocket, status
|
from fastapi import WebSocket, status
|
||||||
from langflow.api.v1.schemas import ChatMessage, ChatResponse, FileResponse
|
from langflow.api.v1.schemas import ChatMessage, ChatResponse, FileResponse
|
||||||
from langflow.services.base import Service
|
from langflow.services.base import Service
|
||||||
|
|
@ -49,6 +50,7 @@ class ChatManager(Service):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.active_connections: Dict[str, WebSocket] = {}
|
self.active_connections: Dict[str, WebSocket] = {}
|
||||||
|
self.connection_ids: Dict[str, str] = {}
|
||||||
self.chat_history = ChatHistory()
|
self.chat_history = ChatHistory()
|
||||||
self.cache_manager = service_manager.get(ServiceType.CACHE_MANAGER)
|
self.cache_manager = service_manager.get(ServiceType.CACHE_MANAGER)
|
||||||
self.cache_manager.attach(self.update)
|
self.cache_manager.attach(self.update)
|
||||||
|
|
@ -93,9 +95,13 @@ class ChatManager(Service):
|
||||||
|
|
||||||
async def connect(self, client_id: str, websocket: WebSocket):
|
async def connect(self, client_id: str, websocket: WebSocket):
|
||||||
self.active_connections[client_id] = websocket
|
self.active_connections[client_id] = websocket
|
||||||
|
# This is to avoid having multiple clients with the same id
|
||||||
|
#! Temporary solution
|
||||||
|
self.connection_ids[client_id] = f"{client_id}-{uuid.uuid4()}"
|
||||||
|
|
||||||
def disconnect(self, client_id: str):
|
def disconnect(self, client_id: str):
|
||||||
self.active_connections.pop(client_id, None)
|
self.active_connections.pop(client_id, None)
|
||||||
|
self.connection_ids.pop(client_id, None)
|
||||||
|
|
||||||
async def send_message(self, client_id: str, message: str):
|
async def send_message(self, client_id: str, message: str):
|
||||||
websocket = self.active_connections[client_id]
|
websocket = self.active_connections[client_id]
|
||||||
|
|
@ -137,6 +143,7 @@ class ChatManager(Service):
|
||||||
langchain_object=langchain_object,
|
langchain_object=langchain_object,
|
||||||
chat_inputs=chat_inputs,
|
chat_inputs=chat_inputs,
|
||||||
websocket=self.active_connections[client_id],
|
websocket=self.active_connections[client_id],
|
||||||
|
session_id=self.connection_ids[client_id],
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log stack trace
|
# Log stack trace
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ async def process_graph(
|
||||||
langchain_object,
|
langchain_object,
|
||||||
chat_inputs: ChatMessage,
|
chat_inputs: ChatMessage,
|
||||||
websocket: WebSocket,
|
websocket: WebSocket,
|
||||||
|
session_id: str,
|
||||||
):
|
):
|
||||||
langchain_object = try_setting_streaming_options(langchain_object, websocket)
|
langchain_object = try_setting_streaming_options(langchain_object, websocket)
|
||||||
logger.debug("Loaded langchain object")
|
logger.debug("Loaded langchain object")
|
||||||
|
|
@ -27,7 +28,10 @@ async def process_graph(
|
||||||
|
|
||||||
logger.debug("Generating result and thought")
|
logger.debug("Generating result and thought")
|
||||||
result, intermediate_steps = await get_result_and_steps(
|
result, intermediate_steps = await get_result_and_steps(
|
||||||
langchain_object, chat_inputs.message, websocket=websocket
|
langchain_object,
|
||||||
|
chat_inputs.message,
|
||||||
|
websocket=websocket,
|
||||||
|
session_id=session_id,
|
||||||
)
|
)
|
||||||
logger.debug("Generated result and intermediate_steps")
|
logger.debug("Generated result and intermediate_steps")
|
||||||
return result, intermediate_steps
|
return result, intermediate_steps
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from fastapi import Depends, HTTPException
|
from fastapi import Depends, HTTPException, status
|
||||||
from langflow.services.database.models.user.user import User, UserUpdate
|
from langflow.services.database.models.user.user import User, UserUpdate
|
||||||
from langflow.services.utils import get_session
|
from langflow.services.utils import get_session
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy.orm.attributes import flag_modified
|
from sqlalchemy.orm.attributes import flag_modified
|
||||||
|
|
||||||
|
|
@ -20,20 +20,26 @@ def get_user_by_id(db: Session, id: UUID) -> Union[User, None]:
|
||||||
|
|
||||||
|
|
||||||
def update_user(
|
def update_user(
|
||||||
user_id: UUID, user: UserUpdate, db: Session = Depends(get_session)
|
user_db: Optional[User], user: UserUpdate, db: Session = Depends(get_session)
|
||||||
) -> User:
|
) -> User:
|
||||||
user_db = get_user_by_id(db, user_id)
|
|
||||||
if not user_db:
|
if not user_db:
|
||||||
raise HTTPException(status_code=404, detail="User not found")
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
|
||||||
user_db_by_username = get_user_by_username(db, user.username) # type: ignore
|
# user_db_by_username = get_user_by_username(db, user.username) # type: ignore
|
||||||
if user_db_by_username and user_db_by_username.id != user_id:
|
# if user_db_by_username and user_db_by_username.id != user_id:
|
||||||
raise HTTPException(status_code=409, detail="Username already exists")
|
# raise HTTPException(status_code=409, detail="Username already exists")
|
||||||
|
|
||||||
user_data = user.dict(exclude_unset=True)
|
user_data = user.dict(exclude_unset=True)
|
||||||
|
changed = False
|
||||||
for attr, value in user_data.items():
|
for attr, value in user_data.items():
|
||||||
if hasattr(user_db, attr) and value is not None:
|
if hasattr(user_db, attr) and value is not None:
|
||||||
setattr(user_db, attr, value)
|
setattr(user_db, attr, value)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if not changed:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_304_NOT_MODIFIED, detail="Nothing to update"
|
||||||
|
)
|
||||||
|
|
||||||
user_db.updated_at = datetime.now(timezone.utc)
|
user_db.updated_at = datetime.now(timezone.utc)
|
||||||
flag_modified(user_db, "updated_at")
|
flag_modified(user_db, "updated_at")
|
||||||
|
|
@ -49,5 +55,5 @@ def update_user(
|
||||||
|
|
||||||
def update_user_last_login_at(user_id: UUID, db: Session = Depends(get_session)):
|
def update_user_last_login_at(user_id: UUID, db: Session = Depends(get_session)):
|
||||||
user_data = UserUpdate(last_login_at=datetime.now(timezone.utc)) # type: ignore
|
user_data = UserUpdate(last_login_at=datetime.now(timezone.utc)) # type: ignore
|
||||||
|
user = get_user_by_id(db, user_id)
|
||||||
return update_user(user_id, user_data, db)
|
return update_user(user, user_data, db)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class User(SQLModelSerializable, table=True):
|
||||||
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
||||||
username: str = Field(index=True, unique=True)
|
username: str = Field(index=True, unique=True)
|
||||||
password: str = Field()
|
password: str = Field()
|
||||||
|
profile_image: Optional[str] = Field(default=None)
|
||||||
is_active: bool = Field(default=False)
|
is_active: bool = Field(default=False)
|
||||||
is_superuser: bool = Field(default=False)
|
is_superuser: bool = Field(default=False)
|
||||||
create_at: datetime = Field(default_factory=datetime.utcnow)
|
create_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|
@ -32,6 +33,7 @@ class UserCreate(SQLModel):
|
||||||
class UserRead(SQLModel):
|
class UserRead(SQLModel):
|
||||||
id: UUID = Field(default_factory=uuid4)
|
id: UUID = Field(default_factory=uuid4)
|
||||||
username: str = Field()
|
username: str = Field()
|
||||||
|
profile_image: Optional[str] = Field()
|
||||||
is_active: bool = Field()
|
is_active: bool = Field()
|
||||||
is_superuser: bool = Field()
|
is_superuser: bool = Field()
|
||||||
create_at: datetime = Field()
|
create_at: datetime = Field()
|
||||||
|
|
@ -40,7 +42,8 @@ class UserRead(SQLModel):
|
||||||
|
|
||||||
|
|
||||||
class UserUpdate(SQLModel):
|
class UserUpdate(SQLModel):
|
||||||
username: Optional[str] = Field()
|
profile_image: Optional[str] = Field()
|
||||||
|
password: Optional[str] = Field()
|
||||||
is_active: Optional[bool] = Field()
|
is_active: Optional[bool] = Field()
|
||||||
is_superuser: Optional[bool] = Field()
|
is_superuser: Optional[bool] = Field()
|
||||||
last_login_at: Optional[datetime] = Field()
|
last_login_at: Optional[datetime] = Field()
|
||||||
|
|
|
||||||
0
src/backend/langflow/services/plugins/__init__.py
Normal file
0
src/backend/langflow/services/plugins/__init__.py
Normal file
49
src/backend/langflow/services/plugins/langfuse.py
Normal file
49
src/backend/langflow/services/plugins/langfuse.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
### Temporary implementation
|
||||||
|
# This will be replaced by a plugin system once merged into 0.5.0
|
||||||
|
|
||||||
|
|
||||||
|
class LangfuseInstance:
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls):
|
||||||
|
logger.debug("Getting Langfuse instance")
|
||||||
|
if cls._instance is None:
|
||||||
|
cls.create()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls):
|
||||||
|
logger.debug("Creating Langfuse instance")
|
||||||
|
from langfuse import Langfuse # type: ignore
|
||||||
|
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
|
|
||||||
|
if (
|
||||||
|
settings_manager.settings.LANGFUSE_PUBLIC_KEY
|
||||||
|
and settings_manager.settings.LANGFUSE_SECRET_KEY
|
||||||
|
):
|
||||||
|
logger.debug("Langfuse credentials found")
|
||||||
|
cls._instance = Langfuse(
|
||||||
|
public_key=settings_manager.settings.LANGFUSE_PUBLIC_KEY,
|
||||||
|
secret_key=settings_manager.settings.LANGFUSE_SECRET_KEY,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug("No Langfuse credentials found")
|
||||||
|
cls._instance = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls):
|
||||||
|
logger.debug("Updating Langfuse instance")
|
||||||
|
cls._instance = None
|
||||||
|
cls.create()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def teardown(cls):
|
||||||
|
logger.debug("Tearing down Langfuse instance")
|
||||||
|
if cls._instance is not None:
|
||||||
|
cls._instance.flush()
|
||||||
|
cls._instance = None
|
||||||
|
|
@ -41,6 +41,10 @@ class Settings(BaseSettings):
|
||||||
REMOVE_API_KEYS: bool = False
|
REMOVE_API_KEYS: bool = False
|
||||||
COMPONENTS_PATH: List[str] = []
|
COMPONENTS_PATH: List[str] = []
|
||||||
|
|
||||||
|
LANGFUSE_SECRET_KEY: Optional[str] = None
|
||||||
|
LANGFUSE_PUBLIC_KEY: Optional[str] = None
|
||||||
|
LANGFUSE_HOST: Optional[str] = None
|
||||||
|
|
||||||
@validator("CONFIG_DIR", pre=True, allow_reuse=True)
|
@validator("CONFIG_DIR", pre=True, allow_reuse=True)
|
||||||
def set_langflow_dir(cls, value):
|
def set_langflow_dir(cls, value):
|
||||||
if not value:
|
if not value:
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ import re
|
||||||
import inspect
|
import inspect
|
||||||
import importlib
|
import importlib
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Optional, Dict, Any, Union
|
from typing import List, Optional, Dict, Any, Union
|
||||||
|
|
||||||
from docstring_parser import parse
|
from docstring_parser import parse
|
||||||
|
|
||||||
from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
|
from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
|
||||||
from langflow.utils import constants
|
from langflow.utils import constants
|
||||||
|
from langchain.schema import Document
|
||||||
|
|
||||||
|
|
||||||
def build_template_from_function(
|
def build_template_from_function(
|
||||||
|
|
@ -456,3 +457,12 @@ def add_options_to_field(
|
||||||
value["options"] = options_map[class_name]
|
value["options"] = options_map[class_name]
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
value["value"] = options_map[class_name][0]
|
value["value"] = options_map[class_name][0]
|
||||||
|
|
||||||
|
|
||||||
|
def build_loader_repr_from_documents(documents: List[Document]) -> str:
|
||||||
|
if documents:
|
||||||
|
avg_length = sum(len(doc.page_content) for doc in documents) / len(documents)
|
||||||
|
return f"""{len(documents)} documents
|
||||||
|
\nAvg. Document Length (characters): {int(avg_length)}
|
||||||
|
Documents: {documents[:3]}..."""
|
||||||
|
return "0 documents"
|
||||||
|
|
|
||||||
567
src/frontend/package-lock.json
generated
567
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -141,10 +141,18 @@ export default function ParameterComponent({
|
||||||
nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
|
nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{index === 0 && (
|
||||||
|
<span>
|
||||||
|
{left
|
||||||
|
? "Avaliable input components:"
|
||||||
|
: "Avaliable output components:"}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
index > 0 ? "mt-2 flex items-center" : "flex items-center"
|
index > 0 ? "mt-2 flex items-center" : "mt-3 flex items-center"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
@ -162,7 +170,7 @@ export default function ParameterComponent({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="ps-2 text-xs text-foreground">
|
<span className="ps-2 text-xs text-foreground">
|
||||||
{nodeNames[item.family] ?? "Other"}
|
{nodeNames[item.family] ?? "Other"}{" "}
|
||||||
<span className="text-xs">
|
<span className="text-xs">
|
||||||
{" "}
|
{" "}
|
||||||
{item.type === "" ? "" : " - "}
|
{item.type === "" ? "" : " - "}
|
||||||
|
|
@ -180,6 +188,7 @@ export default function ParameterComponent({
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -158,8 +158,17 @@ export default function GenericNode({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="generic-node-desc">
|
<div
|
||||||
<div className="generic-node-desc-text">{data.node?.description}</div>
|
className={
|
||||||
|
"generic-node-desc " +
|
||||||
|
(data.node?.description !== "" ? "py-5" : "pb-5")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{data.node?.description !== "" && (
|
||||||
|
<div className="generic-node-desc-text">
|
||||||
|
{data.node?.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<>
|
<>
|
||||||
{Object.keys(data.node!.template)
|
{Object.keys(data.node!.template)
|
||||||
|
|
|
||||||
|
|
@ -47,21 +47,9 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
setInvalidName!(true);
|
setInvalidName!(true);
|
||||||
}
|
}
|
||||||
setName(value);
|
setName(value);
|
||||||
setCurrentName(value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [currentName, setCurrentName] = useState(name);
|
|
||||||
|
|
||||||
const [currentDescription, setCurrentDescription] = useState(description);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentName(name);
|
|
||||||
setCurrentDescription(description);
|
|
||||||
}, [name, description]);
|
|
||||||
|
|
||||||
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
flows.find((f) => f.id === tabId).description = event.target.value;
|
|
||||||
setCurrentDescription(flows.find((f) => f.id === tabId).description);
|
|
||||||
setDescription(event.target.value);
|
setDescription(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -82,7 +70,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
value={currentName ?? ""}
|
value={name ?? ""}
|
||||||
placeholder="File name"
|
placeholder="File name"
|
||||||
id="name"
|
id="name"
|
||||||
maxLength={maxLength}
|
maxLength={maxLength}
|
||||||
|
|
@ -97,7 +85,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
name="description"
|
name="description"
|
||||||
id="description"
|
id="description"
|
||||||
onChange={handleDescriptionChange}
|
onChange={handleDescriptionChange}
|
||||||
value={currentDescription}
|
value={description}
|
||||||
placeholder="Flow description"
|
placeholder="Flow description"
|
||||||
className="mt-2 max-h-[100px] font-normal"
|
className="mt-2 max-h-[100px] font-normal"
|
||||||
rows={3}
|
rows={3}
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,15 @@ import { Button } from "../ui/button";
|
||||||
|
|
||||||
export default function PaginatorComponent({
|
export default function PaginatorComponent({
|
||||||
pageSize = 10,
|
pageSize = 10,
|
||||||
pageIndex = 0,
|
pageIndex = 1,
|
||||||
rowsCount = [10, 20, 50, 100],
|
rowsCount = [10, 20, 50, 100],
|
||||||
totalRowsCount = 0,
|
totalRowsCount = 0,
|
||||||
paginate,
|
paginate,
|
||||||
}: PaginatorComponentType) {
|
}: PaginatorComponentType) {
|
||||||
const [size, setPageSize] = useState(pageSize);
|
const [size, setPageSize] = useState(pageSize);
|
||||||
const [index, setPageIndex] = useState(pageIndex);
|
|
||||||
const [maxIndex, setMaxPageIndex] = useState(
|
const [maxIndex, setMaxPageIndex] = useState(
|
||||||
Math.ceil(totalRowsCount / pageSize)
|
Math.ceil(totalRowsCount / pageSize)
|
||||||
);
|
);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMaxPageIndex(Math.ceil(totalRowsCount / size));
|
setMaxPageIndex(Math.ceil(totalRowsCount / size));
|
||||||
|
|
@ -39,8 +37,9 @@ export default function PaginatorComponent({
|
||||||
onValueChange={(pageSize: string) => {
|
onValueChange={(pageSize: string) => {
|
||||||
setPageSize(Number(pageSize));
|
setPageSize(Number(pageSize));
|
||||||
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
|
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
|
||||||
paginate(Number(pageSize), 0);
|
paginate(Number(pageSize), 1);
|
||||||
}}
|
}}
|
||||||
|
value={pageSize.toString()}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[100px]">
|
<SelectTrigger className="w-[100px]">
|
||||||
<SelectValue placeholder="10" />
|
<SelectValue placeholder="10" />
|
||||||
|
|
@ -55,30 +54,25 @@ export default function PaginatorComponent({
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||||
Page {currentPage} of {maxIndex}
|
Page {pageIndex} of {maxIndex}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Button
|
<Button
|
||||||
disabled={index <= 0}
|
disabled={pageIndex <= 1}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="hidden h-8 w-8 p-0 lg:flex"
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPageIndex(0);
|
paginate(size, 1);
|
||||||
setCurrentPage(1);
|
|
||||||
paginate(size, 0);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Go to first page</span>
|
<span className="sr-only">Go to first page</span>
|
||||||
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
|
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={index <= 0}
|
disabled={pageIndex <= 1}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (index > 0) {
|
if (pageIndex > 0) {
|
||||||
const pgIndex = size - index;
|
paginate(size, pageIndex - 1);
|
||||||
setCurrentPage(currentPage - 1);
|
|
||||||
setPageIndex(pgIndex);
|
|
||||||
paginate(size, pgIndex);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
@ -88,12 +82,9 @@ export default function PaginatorComponent({
|
||||||
<IconComponent name="ChevronLeft" className="h-4 w-4" />
|
<IconComponent name="ChevronLeft" className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={currentPage === maxIndex}
|
disabled={pageIndex === maxIndex}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const pgIndex = size + index;
|
paginate(size, pageIndex + 1);
|
||||||
setPageIndex(pgIndex);
|
|
||||||
setCurrentPage(currentPage + 1);
|
|
||||||
paginate(size, pgIndex);
|
|
||||||
}}
|
}}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-8 w-8 p-0"
|
className="h-8 w-8 p-0"
|
||||||
|
|
@ -102,13 +93,11 @@ export default function PaginatorComponent({
|
||||||
<IconComponent name="ChevronRight" className="h-4 w-4" />
|
<IconComponent name="ChevronRight" className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={currentPage === maxIndex}
|
disabled={pageIndex === maxIndex}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="hidden h-8 w-8 p-0 lg:flex"
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPageIndex(maxIndex - 1);
|
paginate(size, maxIndex);
|
||||||
setCurrentPage(maxIndex);
|
|
||||||
paginate(size, size);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Go to last page</span>
|
<span className="sr-only">Go to last page</span>
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ export default function CodeTabsComponent({
|
||||||
key={idx} // Remember to add a unique key prop
|
key={idx} // Remember to add a unique key prop
|
||||||
>
|
>
|
||||||
{idx < 4 ? (
|
{idx < 4 ? (
|
||||||
<div className="w-full h-full flex flex-col">
|
<div className="flex h-full w-full flex-col">
|
||||||
{tab.description && (
|
{tab.description && (
|
||||||
<div
|
<div
|
||||||
className="mb-2 w-full text-left text-sm"
|
className="mb-2 w-full text-left text-sm"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { gradients } from "../../utils/styleUtils";
|
||||||
|
|
||||||
|
export default function GradientChooserComponent({ value, onChange }) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-4">
|
||||||
|
{gradients.map((gradient, idx) => (
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
onChange(gradient);
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
"duration-400 h-12 w-12 cursor-pointer rounded-full transition-all " +
|
||||||
|
gradient +
|
||||||
|
(value === gradient ? " shadow-lg ring-2 ring-primary" : "")
|
||||||
|
}
|
||||||
|
key={idx}
|
||||||
|
></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -27,7 +27,7 @@ export default function Header(): JSX.Element {
|
||||||
const { notificationCenter } = useContext(alertContext);
|
const { notificationCenter } = useContext(alertContext);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { logout, autoLogin, isAdmin, userData } = useContext(AuthContext);
|
const { logout, autoLogin, isAdmin, userData } = useContext(AuthContext);
|
||||||
const { stars } = useContext(darkContext);
|
const { stars, gradientIndex } = useContext(darkContext);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -139,9 +139,7 @@ export default function Header(): JSX.Element {
|
||||||
<button
|
<button
|
||||||
className={
|
className={
|
||||||
"h-7 w-7 rounded-full focus-visible:outline-0 " +
|
"h-7 w-7 rounded-full focus-visible:outline-0 " +
|
||||||
gradients[
|
(userData?.profile_image ?? gradients[gradientIndex])
|
||||||
parseInt(userData?.id ?? "", 10) % gradients.length
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|
@ -156,6 +154,12 @@ export default function Header(): JSX.Element {
|
||||||
Admin Page
|
Admin Page
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => navigate("/account/settings")}
|
||||||
|
>
|
||||||
|
Profile Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ export default function InputListComponent({
|
||||||
}
|
}
|
||||||
}, [disabled]);
|
}, [disabled]);
|
||||||
|
|
||||||
|
// @TODO Recursive Character Text Splitter - the value might be in string format, whereas the InputListComponent specifically requires an array format. To ensure smooth operation and prevent potential errors, it's crucial that we handle the conversion from a string to an array with the string as its element.
|
||||||
|
typeof value === "string" ? (value = [value]) : (value = value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ export default function LoadingComponent({
|
||||||
remSize,
|
remSize,
|
||||||
}: LoadingComponentProps): JSX.Element {
|
}: LoadingComponentProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div role="status" className="m-auto w-min">
|
<div role="status" className="flex flex-col items-center justify-center">
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={`w-${remSize} h-${remSize} mr-2 animate-spin fill-almost-medium-blue text-muted`}
|
className={`w-${remSize} h-${remSize} animate-spin fill-almost-medium-blue text-muted`}
|
||||||
viewBox="0 0 100 101"
|
viewBox="0 0 100 101"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
|
import { Cross2Icon } from "@radix-ui/react-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
import IconComponent from "../genericIconComponent";
|
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root;
|
const Dialog = DialogPrimitive.Root;
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ const DialogOverlay = React.forwardRef<
|
||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed top-0 right-0 left-0 bottom-0 overflow-auto inset-0 z-50 bg-blur-shared backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
"fixed inset-0 bottom-0 left-0 right-0 top-0 z-50 overflow-auto bg-blur-shared backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -44,14 +44,14 @@ const DialogContent = React.forwardRef<
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"noundo nocopy flex text-start overflow-auto z-50 justify-center items-left flex-col w-full max-w-lg gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-bottom-[48%] data-[state=open]:slide-in-from-bottom-[48%] sm:rounded-lg md:w-full",
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
<IconComponent name="X" className="h-4 w-4" />
|
<Cross2Icon className="h-4 w-4" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
|
|
|
||||||
|
|
@ -503,6 +503,21 @@ export const NOUNS: string[] = [
|
||||||
*/
|
*/
|
||||||
export const USER_PROJECTS_HEADER = "My Collection";
|
export const USER_PROJECTS_HEADER = "My Collection";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header text for admin page
|
||||||
|
* @constant
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const ADMIN_HEADER_TITLE = "Admin Page";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header description for admin page
|
||||||
|
* @constant
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const ADMIN_HEADER_DESCRIPTION =
|
||||||
|
"Navigate through this section to efficiently oversee all application users. From here, you can seamlessly manage user accounts.";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URLs excluded from error retries.
|
* URLs excluded from error retries.
|
||||||
* @constant
|
* @constant
|
||||||
|
|
@ -523,6 +538,12 @@ export const CONTROL_INPUT_STATE = {
|
||||||
username: "",
|
username: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CONTROL_PATCH_USER_STATE = {
|
||||||
|
password: "",
|
||||||
|
cnfPassword: "",
|
||||||
|
gradient: "",
|
||||||
|
};
|
||||||
|
|
||||||
export const CONTROL_LOGIN_STATE = {
|
export const CONTROL_LOGIN_STATE = {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ export function AuthProvider({ children }): React.ReactElement {
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
setUserData(user);
|
setUserData(user);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
const isSuperUser = user.is_superuser;
|
const isSuperUser = user!.is_superuser;
|
||||||
setIsAdmin(isSuperUser);
|
setIsAdmin(isSuperUser);
|
||||||
})
|
})
|
||||||
.catch((error) => {});
|
.catch((error) => {});
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ const initialValue = {
|
||||||
setDark: () => {},
|
setDark: () => {},
|
||||||
stars: 0,
|
stars: 0,
|
||||||
setStars: (stars) => 0,
|
setStars: (stars) => 0,
|
||||||
|
gradientIndex: 0,
|
||||||
|
setGradientIndex: () => 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const darkContext = createContext<darkContextType>(initialValue);
|
export const darkContext = createContext<darkContextType>(initialValue);
|
||||||
|
|
@ -16,6 +18,7 @@ export function DarkProvider({ children }) {
|
||||||
JSON.parse(window.localStorage.getItem("isDark")!) ?? false
|
JSON.parse(window.localStorage.getItem("isDark")!) ?? false
|
||||||
);
|
);
|
||||||
const [stars, setStars] = useState<number>(0);
|
const [stars, setStars] = useState<number>(0);
|
||||||
|
const [gradientIndex, setGradientIndex] = useState<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchStars() {
|
async function fetchStars() {
|
||||||
|
|
@ -23,6 +26,9 @@ export function DarkProvider({ children }) {
|
||||||
setStars(starsCount);
|
setStars(starsCount);
|
||||||
}
|
}
|
||||||
fetchStars();
|
fetchStars();
|
||||||
|
const min = 0;
|
||||||
|
const max = 30;
|
||||||
|
setGradientIndex(Math.floor(Math.random() * (max - min + 1)) + min);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -41,6 +47,8 @@ export function DarkProvider({ children }) {
|
||||||
stars,
|
stars,
|
||||||
dark,
|
dark,
|
||||||
setDark,
|
setDark,
|
||||||
|
setGradientIndex,
|
||||||
|
gradientIndex,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
function hardReset() {
|
function hardReset() {
|
||||||
newNodeId.current = uid();
|
newNodeId.current = uid();
|
||||||
setTabId("");
|
setTabId("");
|
||||||
|
|
||||||
setFlows([]);
|
setFlows([]);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setId(uid());
|
setId(uid());
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,10 @@ function ApiInterceptor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await renewAccessToken(refreshToken);
|
const res = await renewAccessToken(refreshToken);
|
||||||
|
if (res?.data?.access_token && res?.data?.refresh_token) {
|
||||||
login(res?.data?.access_token, res?.data?.refresh_token);
|
login(res?.data?.access_token, res?.data?.refresh_token);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (error?.config?.headers) {
|
if (error?.config?.headers) {
|
||||||
delete error.config.headers["Authorization"];
|
delete error.config.headers["Authorization"];
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import {
|
||||||
APIObjectType,
|
APIObjectType,
|
||||||
LoginType,
|
LoginType,
|
||||||
Users,
|
Users,
|
||||||
|
changeUser,
|
||||||
|
resetPasswordType,
|
||||||
sendAllProps,
|
sendAllProps,
|
||||||
} from "../../types/api/index";
|
} from "../../types/api/index";
|
||||||
import { UserInputType } from "../../types/components";
|
import { UserInputType } from "../../types/components";
|
||||||
|
|
@ -393,16 +395,18 @@ export async function autoLogin() {
|
||||||
|
|
||||||
export async function renewAccessToken(token: string) {
|
export async function renewAccessToken(token: string) {
|
||||||
try {
|
try {
|
||||||
|
if (token) {
|
||||||
return await api.post(`${BASE_URL_API}refresh?token=${token}`);
|
return await api.post(`${BASE_URL_API}refresh?token=${token}`);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
console.log("Error:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLoggedUser(): Promise<Users> {
|
export async function getLoggedUser(): Promise<Users | null> {
|
||||||
try {
|
try {
|
||||||
const res = await api.get(`${BASE_URL_API}user`);
|
const res = await api.get(`${BASE_URL_API}users/whoami`);
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|
@ -411,14 +415,16 @@ export async function getLoggedUser(): Promise<Users> {
|
||||||
console.log("Error:", error);
|
console.log("Error:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addUser(user: UserInputType): Promise<Users> {
|
export async function addUser(user: UserInputType): Promise<Array<Users>> {
|
||||||
try {
|
try {
|
||||||
const res = await api.post(`${BASE_URL_API}user`, user);
|
const res = await api.post(`${BASE_URL_API}users/`, user);
|
||||||
if (res.status === 200) {
|
if (res.status !== 201) {
|
||||||
return res.data;
|
throw new Error(res.data.detail);
|
||||||
}
|
}
|
||||||
|
return res.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error:", error);
|
console.log("Error:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -428,10 +434,10 @@ export async function addUser(user: UserInputType): Promise<Users> {
|
||||||
export async function getUsersPage(
|
export async function getUsersPage(
|
||||||
skip: number,
|
skip: number,
|
||||||
limit: number
|
limit: number
|
||||||
): Promise<[Users]> {
|
): Promise<Array<Users>> {
|
||||||
try {
|
try {
|
||||||
const res = await api.get(
|
const res = await api.get(
|
||||||
`${BASE_URL_API}users?skip=${skip}&limit=${limit}`
|
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`
|
||||||
);
|
);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|
@ -440,11 +446,12 @@ export async function getUsersPage(
|
||||||
console.log("Error:", error);
|
console.log("Error:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteUser(user_id: string) {
|
export async function deleteUser(user_id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await api.delete(`${BASE_URL_API}user/${user_id}`);
|
const res = await api.delete(`${BASE_URL_API}users/${user_id}`);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
@ -454,9 +461,24 @@ export async function deleteUser(user_id: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateUser(user_id: string, user: Users) {
|
export async function updateUser(user_id: string, user: changeUser) {
|
||||||
try {
|
try {
|
||||||
const res = await api.patch(`${BASE_URL_API}user/${user_id}`, user);
|
const res = await api.patch(`${BASE_URL_API}users/${user_id}`, user);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resetPassword(user_id: string, user: resetPasswordType) {
|
||||||
|
try {
|
||||||
|
const res = await api.patch(
|
||||||
|
`${BASE_URL_API}users/${user_id}/reset-password`,
|
||||||
|
user
|
||||||
|
);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { Infinity } from "lucide-react";
|
|
||||||
import { forwardRef } from "react";
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
const GradientSparkles = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
|
export const GradientSparkles = forwardRef<
|
||||||
(props, ref) => {
|
SVGSVGElement,
|
||||||
|
React.PropsWithChildren<{}>
|
||||||
|
>((props, ref) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<svg width="0" height="0" style={{ position: "absolute" }}>
|
<svg width="0" height="0" style={{ position: "absolute" }}>
|
||||||
|
|
@ -13,10 +14,26 @@ const GradientSparkles = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
<Infinity stroke="url(#grad1)" ref={ref} {...props} />
|
{/* this svg comes from the source code of lucide,
|
||||||
|
we do not use the import because it crashes the ui (why? no one knows...).
|
||||||
|
source code from the used svg:
|
||||||
|
https://github.com/lucide-icons/lucide/blob/a4076db69b52ff0debc383f76d4d671c3bad5345/icons/infinity.svg?short_path=f79de91
|
||||||
|
*/}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke="url(#grad1)"
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M12 12c-2-2.67-4-4-6-4a4 4 0 1 0 0 8c2 0 4-1.33 6-4Zm0 0c2 2.67 4 4 6 4a4 4 0 0 0 0-8c-2 0-4 1.33-6 4Z" />
|
||||||
|
</svg>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export default GradientSparkles;
|
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,9 @@ const ApiModal = forwardRef(
|
||||||
{
|
{
|
||||||
flow,
|
flow,
|
||||||
children,
|
children,
|
||||||
disable,
|
|
||||||
}: {
|
}: {
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
disable: boolean;
|
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -201,8 +199,8 @@ const ApiModal = forwardRef(
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal open={open} setOpen={setOpen} disable={disable}>
|
<BaseModal open={open} setOpen={setOpen}>
|
||||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
|
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
|
||||||
<span className="pr-2">Code</span>
|
<span className="pr-2">Code</span>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import BaseModal from "../baseModal";
|
||||||
|
|
||||||
export default function ConfirmationModal({
|
export default function ConfirmationModal({
|
||||||
title,
|
title,
|
||||||
|
asChild,
|
||||||
titleHeader,
|
titleHeader,
|
||||||
modalContent,
|
modalContent,
|
||||||
modalContentTitle,
|
modalContentTitle,
|
||||||
|
|
@ -22,7 +23,7 @@ export default function ConfirmationModal({
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
return (
|
return (
|
||||||
<BaseModal size="x-small" open={open} setOpen={setOpen}>
|
<BaseModal size="x-small" open={open} setOpen={setOpen}>
|
||||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
<BaseModal.Trigger asChild={asChild}>{children}</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={titleHeader}>
|
<BaseModal.Header description={titleHeader}>
|
||||||
<span className="pr-2">{title}</span>
|
<span className="pr-2">{title}</span>
|
||||||
<Icon
|
<Icon
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export default function UserManagementModal({
|
||||||
data,
|
data,
|
||||||
index,
|
index,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
|
asChild,
|
||||||
}: UserManagementType) {
|
}: UserManagementType) {
|
||||||
const Icon: any = nodeIconsLucide[icon];
|
const Icon: any = nodeIconsLucide[icon];
|
||||||
const [pwdVisible, setPwdVisible] = useState(false);
|
const [pwdVisible, setPwdVisible] = useState(false);
|
||||||
|
|
@ -60,7 +61,7 @@ export default function UserManagementModal({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
|
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
|
||||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
<BaseModal.Trigger asChild={asChild}>{children}</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={titleHeader}>
|
<BaseModal.Header description={titleHeader}>
|
||||||
<span className="pr-2">{title}</span>
|
<span className="pr-2">{title}</span>
|
||||||
<Icon
|
<Icon
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,25 @@ import { modalHeaderType } from "../../types/components";
|
||||||
type ContentProps = { children: ReactNode };
|
type ContentProps = { children: ReactNode };
|
||||||
type HeaderProps = { children: ReactNode; description: string };
|
type HeaderProps = { children: ReactNode; description: string };
|
||||||
type FooterProps = { children: ReactNode };
|
type FooterProps = { children: ReactNode };
|
||||||
type TriggerProps = { children: ReactNode };
|
type TriggerProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
asChild?: boolean;
|
||||||
|
disable?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const Content: React.FC<ContentProps> = ({ children }) => {
|
const Content: React.FC<ContentProps> = ({ children }) => {
|
||||||
return <div className="h-full w-full">{children}</div>;
|
return <div className="h-full w-full">{children}</div>;
|
||||||
};
|
};
|
||||||
const Trigger: React.FC<ContentProps> = ({ children }) => {
|
const Trigger: React.FC<TriggerProps> = ({ children, asChild, disable }) => {
|
||||||
return <>{children}</>;
|
return (
|
||||||
|
<DialogTrigger
|
||||||
|
className={asChild ? "" : "w-full"}
|
||||||
|
hidden={children ? false : true}
|
||||||
|
asChild={asChild}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DialogTrigger>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
|
const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
|
||||||
|
|
@ -47,7 +59,6 @@ interface BaseModalProps {
|
||||||
];
|
];
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
setOpen?: (open: boolean) => void;
|
setOpen?: (open: boolean) => void;
|
||||||
disable?: boolean;
|
|
||||||
size?:
|
size?:
|
||||||
| "x-small"
|
| "x-small"
|
||||||
| "smaller"
|
| "smaller"
|
||||||
|
|
@ -61,7 +72,6 @@ interface BaseModalProps {
|
||||||
function BaseModal({
|
function BaseModal({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
setOpen,
|
||||||
disable = false,
|
|
||||||
children,
|
children,
|
||||||
size = "large",
|
size = "large",
|
||||||
}: BaseModalProps) {
|
}: BaseModalProps) {
|
||||||
|
|
@ -120,17 +130,12 @@ function BaseModal({
|
||||||
//UPDATE COLORS AND STYLE CLASSSES
|
//UPDATE COLORS AND STYLE CLASSSES
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger
|
|
||||||
className={"w-full " + (disable ? "button-disable" : "")}
|
|
||||||
hidden={triggerChild ? false : true}
|
|
||||||
>
|
|
||||||
{triggerChild}
|
{triggerChild}
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className={minWidth}>
|
<DialogContent className={minWidth}>
|
||||||
<div className="truncate-doubleline word-break-break-word">
|
<div className="truncate-doubleline word-break-break-word">
|
||||||
{headerChild}
|
{headerChild}
|
||||||
</div>
|
</div>
|
||||||
<div className={`mt-2 flex flex-col ${height} w-full `}>
|
<div className={`mt-2 flex flex-col ${height!} w-full `}>
|
||||||
{ContentChild}
|
{ContentChild}
|
||||||
</div>
|
</div>
|
||||||
{ContentFooter && (
|
{ContentFooter && (
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,7 @@ export default function CodeAreaModal({
|
||||||
const { dark } = useContext(darkContext);
|
const { dark } = useContext(darkContext);
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const { reactFlowInstance } = useContext(typesContext);
|
||||||
const [height, setHeight] = useState<string | null>(null);
|
const [height, setHeight] = useState<string | null>(null);
|
||||||
const { setErrorData, setSuccessData } =
|
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||||
useContext(alertContext);
|
|
||||||
const [error, setError] = useState<{
|
const [error, setError] = useState<{
|
||||||
detail: { error: string | undefined; traceback: string | undefined };
|
detail: { error: string | undefined; traceback: string | undefined };
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,15 @@ import BaseModal from "../baseModal";
|
||||||
|
|
||||||
const ExportModal = forwardRef(
|
const ExportModal = forwardRef(
|
||||||
(props: { children: ReactNode }, ref): JSX.Element => {
|
(props: { children: ReactNode }, ref): JSX.Element => {
|
||||||
const { flows, tabId, updateFlow, downloadFlow } = useContext(TabsContext);
|
const { flows, tabId, downloadFlow } = useContext(TabsContext);
|
||||||
const [checked, setChecked] = useState(false);
|
const [checked, setChecked] = useState(false);
|
||||||
const flow = flows.find((f) => f.id === tabId);
|
const flow = flows.find((f) => f.id === tabId);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setName(flow.name);
|
setName(flow!.name);
|
||||||
setDescription(flow.description);
|
setDescription(flow!.description);
|
||||||
}, [flow.name, flow.description]);
|
}, [flow!.name, flow!.description]);
|
||||||
const [name, setName] = useState(flow.name);
|
const [name, setName] = useState(flow!.name);
|
||||||
const [description, setDescription] = useState(flow.description);
|
const [description, setDescription] = useState(flow!.description);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -40,7 +40,6 @@ const ExportModal = forwardRef(
|
||||||
tabId={tabId}
|
tabId={tabId}
|
||||||
setName={setName}
|
setName={setName}
|
||||||
setDescription={setDescription}
|
setDescription={setDescription}
|
||||||
updateFlow={updateFlow}
|
|
||||||
/>
|
/>
|
||||||
<div className="mt-3 flex items-center space-x-2">
|
<div className="mt-3 flex items-center space-x-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,8 @@ export default function FormModal({
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleWsMessage(data: any) {
|
function handleWsMessage(data: any) {
|
||||||
if (Array.isArray(data)) {
|
console.log(data);
|
||||||
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
//set chat history
|
//set chat history
|
||||||
setChatHistory((_) => {
|
setChatHistory((_) => {
|
||||||
let newChatHistory: ChatMessageType[] = [];
|
let newChatHistory: ChatMessageType[] = [];
|
||||||
|
|
@ -313,7 +314,7 @@ export default function FormModal({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// do not add connectWS on dependencies array
|
// do not add connectWS on dependencies array
|
||||||
}, []);
|
}, [open]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ export default function GenericModal({
|
||||||
setNoticeData({
|
setNoticeData({
|
||||||
title: "Your template does not have any variables.",
|
title: "Your template does not have any variables.",
|
||||||
});
|
});
|
||||||
|
setModalOpen(false);
|
||||||
} else {
|
} else {
|
||||||
setIsEdit(false);
|
setIsEdit(false);
|
||||||
setSuccessData({
|
setSuccessData({
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ export default function LoginAdminPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUser() {
|
function getUser() {
|
||||||
if (getAuthentication) {
|
if (getAuthentication()) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getLoggedUser()
|
getLoggedUser()
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { X } from "lucide-react";
|
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import PaginatorComponent from "../../components/PaginatorComponent";
|
import PaginatorComponent from "../../components/PaginatorComponent";
|
||||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import Header from "../../components/headerComponent";
|
import Header from "../../components/headerComponent";
|
||||||
|
import LoadingComponent from "../../components/loadingComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Checkbox } from "../../components/ui/checkbox";
|
import { Checkbox } from "../../components/ui/checkbox";
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
|
|
@ -16,8 +16,13 @@ import {
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "../../components/ui/table";
|
} from "../../components/ui/table";
|
||||||
|
import {
|
||||||
|
ADMIN_HEADER_DESCRIPTION,
|
||||||
|
ADMIN_HEADER_TITLE,
|
||||||
|
} from "../../constants/constants";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import {
|
import {
|
||||||
addUser,
|
addUser,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
|
|
@ -33,12 +38,19 @@ export default function AdminPage() {
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
|
|
||||||
const [size, setPageSize] = useState(10);
|
const [size, setPageSize] = useState(10);
|
||||||
const [index, setPageIndex] = useState(0);
|
const [index, setPageIndex] = useState(1);
|
||||||
const [loadingUsers, setLoadingUsers] = useState(true);
|
const [loadingUsers, setLoadingUsers] = useState(true);
|
||||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||||
const { userData } = useContext(AuthContext);
|
const { userData } = useContext(AuthContext);
|
||||||
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
||||||
|
|
||||||
|
const { setTabId } = useContext(TabsContext);
|
||||||
|
|
||||||
|
// set null id
|
||||||
|
useEffect(() => {
|
||||||
|
setTabId("");
|
||||||
|
}, []);
|
||||||
|
|
||||||
const userList = useRef([]);
|
const userList = useRef([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -65,7 +77,9 @@ export default function AdminPage() {
|
||||||
|
|
||||||
function handleChangePagination(pageIndex: number, pageSize: number) {
|
function handleChangePagination(pageIndex: number, pageSize: number) {
|
||||||
setLoadingUsers(true);
|
setLoadingUsers(true);
|
||||||
getUsersPage(pageIndex, pageSize)
|
setPageSize(pageSize);
|
||||||
|
setPageIndex(pageIndex);
|
||||||
|
getUsersPage(pageSize * (pageIndex - 1), pageSize)
|
||||||
.then((users) => {
|
.then((users) => {
|
||||||
setTotalRowsCount(users["total_count"]);
|
setTotalRowsCount(users["total_count"]);
|
||||||
userList.current = users["users"];
|
userList.current = users["users"];
|
||||||
|
|
@ -78,7 +92,7 @@ export default function AdminPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetFilter() {
|
function resetFilter() {
|
||||||
setPageIndex(0);
|
setPageIndex(1);
|
||||||
setPageSize(10);
|
setPageSize(10);
|
||||||
getUsers();
|
getUsers();
|
||||||
}
|
}
|
||||||
|
|
@ -168,70 +182,62 @@ export default function AdminPage() {
|
||||||
function handleNewUser(user: UserInputType) {
|
function handleNewUser(user: UserInputType) {
|
||||||
addUser(user)
|
addUser(user)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
updateUser(res["id"], {
|
||||||
|
is_active: user.is_active,
|
||||||
|
is_superuser: user.is_superuser,
|
||||||
|
}).then((res) => {
|
||||||
resetFilter();
|
resetFilter();
|
||||||
setSuccessData({
|
setSuccessData({
|
||||||
title: "Success! New user added!",
|
title: "Success! New user added!",
|
||||||
});
|
});
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "Error on add new user",
|
title: "Error when adding new user",
|
||||||
list: [error["response"]["data"]["detail"]],
|
list: [error.response.data.detail],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col">
|
|
||||||
<Header />
|
<Header />
|
||||||
{userData && (
|
{userData && (
|
||||||
<div className="main-page-panel">
|
<div className="admin-page-panel flex h-full flex-col pb-8">
|
||||||
<div className="m-auto flex h-full flex-row justify-center">
|
<div className="main-page-nav-arrangement">
|
||||||
<div className="basis-5/6">
|
<span className="main-page-nav-title">
|
||||||
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
<IconComponent name="Shield" className="w-6" />
|
||||||
<div className="flex items-center justify-between space-y-2">
|
{ADMIN_HEADER_TITLE}
|
||||||
<div>
|
</span>
|
||||||
<h2 className="text-2xl font-bold tracking-tight">
|
|
||||||
Welcome back!
|
|
||||||
</h2>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
Navigate through this section to efficiently oversee all
|
|
||||||
application users. From here, you can seamlessly manage
|
|
||||||
user accounts.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2"></div>
|
<span className="admin-page-description-text">
|
||||||
</div>
|
{ADMIN_HEADER_DESCRIPTION}
|
||||||
|
</span>
|
||||||
{userList.current.length === 0 && !loadingUsers && (
|
<div className="flex w-full justify-between px-4">
|
||||||
<>
|
<div className="flex w-96 items-center gap-4">
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h2>There's no users registered :)</h2>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex flex-1 items-center space-x-2">
|
|
||||||
<Input
|
<Input
|
||||||
|
placeholder="Search Username"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
placeholder="Filter users..."
|
|
||||||
className="h-8 w-[150px] lg:w-[250px]"
|
|
||||||
onChange={(e) => handleFilterUsers(e.target.value)}
|
onChange={(e) => handleFilterUsers(e.target.value)}
|
||||||
/>
|
/>
|
||||||
{inputValue.length > 0 && (
|
{inputValue.length > 0 ? (
|
||||||
<Button
|
<div
|
||||||
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setInputValue("");
|
setInputValue("");
|
||||||
setFilterUserList(userList.current);
|
setFilterUserList(userList.current);
|
||||||
}}
|
}}
|
||||||
variant="ghost"
|
|
||||||
className="h-8 px-2 lg:px-3"
|
|
||||||
>
|
>
|
||||||
Reset
|
<IconComponent name="X" className="w-6 text-foreground" />
|
||||||
<X className="ml-2 h-4 w-4" />
|
</div>
|
||||||
</Button>
|
) : (
|
||||||
|
<div>
|
||||||
|
<IconComponent
|
||||||
|
name="Search"
|
||||||
|
className="w-6 text-foreground"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -244,28 +250,34 @@ export default function AdminPage() {
|
||||||
onConfirm={(index, user) => {
|
onConfirm={(index, user) => {
|
||||||
handleNewUser(user);
|
handleNewUser(user);
|
||||||
}}
|
}}
|
||||||
|
asChild
|
||||||
>
|
>
|
||||||
<Button>New User</Button>
|
<Button variant="primary">New User</Button>
|
||||||
</UserManagementModal>
|
</UserManagementModal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{loadingUsers && (
|
{loadingUsers ? (
|
||||||
<div>
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<strong>Loading...</strong>
|
<LoadingComponent remSize={12} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : userList.current.length === 0 ? (
|
||||||
|
<>
|
||||||
|
<div className="m-4 flex items-center justify-between text-sm">
|
||||||
|
No users registered.
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
"max-h-[26rem] min-h-[26rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
|
"m-4 h-full overflow-x-hidden overflow-y-scroll rounded-md border-2 bg-background custom-scroll" +
|
||||||
(loadingUsers ? " border-0" : "")
|
(loadingUsers ? " border-0" : "")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table className={"table-fixed bg-muted outline-1"}>
|
<Table className={"table-fixed outline-1 "}>
|
||||||
<TableHeader
|
<TableHeader
|
||||||
className={
|
className={
|
||||||
loadingUsers
|
loadingUsers ? "hidden" : "table-fixed bg-muted outline-1"
|
||||||
? "hidden"
|
|
||||||
: "table-fixed bg-muted outline-1"
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
|
@ -280,14 +292,11 @@ export default function AdminPage() {
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
{!loadingUsers && (
|
{!loadingUsers && (
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{filterUserList.map(
|
{filterUserList.map((user: UserInputType, index) => (
|
||||||
(user: UserInputType, index) => (
|
|
||||||
<TableRow key={index}>
|
<TableRow key={index}>
|
||||||
<TableCell className="truncate py-2 font-medium">
|
<TableCell className="truncate py-2 font-medium">
|
||||||
<ShadTooltip content={user.id}>
|
<ShadTooltip content={user.id}>
|
||||||
<span className="cursor-default">
|
<span className="cursor-default">{user.id}</span>
|
||||||
{user.id}
|
|
||||||
</span>
|
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="truncate py-2">
|
<TableCell className="truncate py-2">
|
||||||
|
|
@ -297,8 +306,9 @@ export default function AdminPage() {
|
||||||
</span>
|
</span>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
|
<TableCell className="relative left-1 truncate py-2 text-align-last-left">
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
|
asChild
|
||||||
title="Edit"
|
title="Edit"
|
||||||
titleHeader={`${user.username}`}
|
titleHeader={`${user.username}`}
|
||||||
modalContentTitle="Attention!"
|
modalContentTitle="Attention!"
|
||||||
|
|
@ -316,14 +326,17 @@ export default function AdminPage() {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div className="flex w-fit">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="is_active"
|
id="is_active"
|
||||||
checked={user.is_active}
|
checked={user.is_active}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="relative left-5 truncate py-2 text-align-last-left">
|
<TableCell className="relative left-1 truncate py-2 text-align-last-left">
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
|
asChild
|
||||||
title="Edit"
|
title="Edit"
|
||||||
titleHeader={`${user.username}`}
|
titleHeader={`${user.username}`}
|
||||||
modalContentTitle="Attention!"
|
modalContentTitle="Attention!"
|
||||||
|
|
@ -341,10 +354,12 @@ export default function AdminPage() {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div className="flex w-fit">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="is_superuser"
|
id="is_superuser"
|
||||||
checked={user.is_superuser}
|
checked={user.is_superuser}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="truncate py-2 ">
|
<TableCell className="truncate py-2 ">
|
||||||
|
|
@ -397,10 +412,7 @@ export default function AdminPage() {
|
||||||
handleDeleteUser(user);
|
handleDeleteUser(user);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ShadTooltip
|
<ShadTooltip content="Delete" side="top">
|
||||||
content="Delete"
|
|
||||||
side="top"
|
|
||||||
>
|
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Trash2"
|
name="Trash2"
|
||||||
className="ml-2 h-4 w-4 cursor-pointer"
|
className="ml-2 h-4 w-4 cursor-pointer"
|
||||||
|
|
@ -410,8 +422,7 @@ export default function AdminPage() {
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)
|
))}
|
||||||
)}
|
|
||||||
</TableBody>
|
</TableBody>
|
||||||
)}
|
)}
|
||||||
</Table>
|
</Table>
|
||||||
|
|
@ -421,17 +432,14 @@ export default function AdminPage() {
|
||||||
pageIndex={index}
|
pageIndex={index}
|
||||||
pageSize={size}
|
pageSize={size}
|
||||||
totalRowsCount={totalRowsCount}
|
totalRowsCount={totalRowsCount}
|
||||||
paginate={(pageIndex, pageSize) => {
|
paginate={(pageSize, pageIndex) => {
|
||||||
handleChangePagination(pageSize, pageIndex);
|
handleChangePagination(pageIndex, pageSize);
|
||||||
}}
|
}}
|
||||||
></PaginatorComponent>
|
></PaginatorComponent>
|
||||||
</>
|
</>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { useNavigate } from "react-router-dom";
|
||||||
import { CardComponent } from "../../components/cardComponent";
|
import { CardComponent } from "../../components/cardComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import Header from "../../components/headerComponent";
|
import Header from "../../components/headerComponent";
|
||||||
|
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
|
||||||
import { getExamples } from "../../controllers/API";
|
import { getExamples } from "../../controllers/API";
|
||||||
import { FlowType } from "../../types/flow";
|
import { FlowType } from "../../types/flow";
|
||||||
export default function CommunityPage(): JSX.Element {
|
export default function CommunityPage(): JSX.Element {
|
||||||
|
|
@ -74,7 +75,14 @@ export default function CommunityPage(): JSX.Element {
|
||||||
new and powerful features.
|
new and powerful features.
|
||||||
</span>
|
</span>
|
||||||
<div className="community-pages-flows-panel">
|
<div className="community-pages-flows-panel">
|
||||||
{!loadingExamples &&
|
{loadingExamples ? (
|
||||||
|
<>
|
||||||
|
<SkeletonCardComponent />
|
||||||
|
<SkeletonCardComponent />
|
||||||
|
<SkeletonCardComponent />
|
||||||
|
<SkeletonCardComponent />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
examples.map((flow, idx) => (
|
examples.map((flow, idx) => (
|
||||||
<CardComponent
|
<CardComponent
|
||||||
key={idx}
|
key={idx}
|
||||||
|
|
@ -99,7 +107,8 @@ export default function CommunityPage(): JSX.Element {
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -298,7 +298,14 @@ export default function Page({
|
||||||
setNodes((nds) => nds.concat(newNode));
|
setNodes((nds) => nds.concat(newNode));
|
||||||
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
|
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
uploadFlow(false, event.dataTransfer.files.item(0)!);
|
if (event.dataTransfer.files.item(0)!.type === "application/json") {
|
||||||
|
uploadFlow(true, event.dataTransfer.files.item(0)!);
|
||||||
|
} else {
|
||||||
|
setErrorData({
|
||||||
|
title: "Invalid file type",
|
||||||
|
list: ["Please upload a JSON file"],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Specify dependencies for useCallback
|
// Specify dependencies for useCallback
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,10 @@ export default function ExtraSidebar(): JSX.Element {
|
||||||
<ShadTooltip content={"Code"} side="top">
|
<ShadTooltip content={"Code"} side="top">
|
||||||
<div className="side-bar-button">
|
<div className="side-bar-button">
|
||||||
{flow && flow.data && (
|
{flow && flow.data && (
|
||||||
<ApiModal flow={flow} disable={!isBuilt}>
|
<ApiModal flow={flow}>
|
||||||
|
<button
|
||||||
|
className={"w-full " + (!isBuilt ? "button-disable" : "")}
|
||||||
|
>
|
||||||
<div className={classNames("extra-side-bar-buttons")}>
|
<div className={classNames("extra-side-bar-buttons")}>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Code2"
|
name="Code2"
|
||||||
|
|
@ -109,6 +112,7 @@ export default function ExtraSidebar(): JSX.Element {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</button>
|
||||||
</ApiModal>
|
</ApiModal>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useContext, useEffect } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import DropdownButton from "../../components/DropdownButtonComponent";
|
import DropdownButton from "../../components/DropdownButtonComponent";
|
||||||
import { CardComponent } from "../../components/cardComponent";
|
import { CardComponent } from "../../components/cardComponent";
|
||||||
|
|
@ -7,6 +7,7 @@ import Header from "../../components/headerComponent";
|
||||||
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
|
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
||||||
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
export default function HomePage(): JSX.Element {
|
export default function HomePage(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
|
|
@ -19,6 +20,7 @@ export default function HomePage(): JSX.Element {
|
||||||
uploadFlow,
|
uploadFlow,
|
||||||
isLoading,
|
isLoading,
|
||||||
} = useContext(TabsContext);
|
} = useContext(TabsContext);
|
||||||
|
const { setErrorData } = useContext(alertContext);
|
||||||
const dropdownOptions = [
|
const dropdownOptions = [
|
||||||
{
|
{
|
||||||
name: "Import from JSON",
|
name: "Import from JSON",
|
||||||
|
|
@ -35,9 +37,36 @@ export default function HomePage(): JSX.Element {
|
||||||
}, []);
|
}, []);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
console.log(isLoading);
|
|
||||||
}, [isLoading]);
|
const dragOver = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsDragging(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dragEnter = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsDragging(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dragLeave = () => {
|
||||||
|
setIsDragging(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileDrop = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsDragging(false);
|
||||||
|
if (e.dataTransfer.types.some((types) => types === "Files")) {
|
||||||
|
if (e.dataTransfer.files.item(0).type === "application/json") {
|
||||||
|
uploadFlow(true, e.dataTransfer.files.item(0)!);
|
||||||
|
} else {
|
||||||
|
setErrorData({
|
||||||
|
title: "Invalid file type",
|
||||||
|
list: ["Please upload a JSON file"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Personal flows display
|
// Personal flows display
|
||||||
return (
|
return (
|
||||||
|
|
@ -82,6 +111,27 @@ export default function HomePage(): JSX.Element {
|
||||||
<span className="main-page-description-text">
|
<span className="main-page-description-text">
|
||||||
Manage your personal projects. Download or upload your collection.
|
Manage your personal projects. Download or upload your collection.
|
||||||
</span>
|
</span>
|
||||||
|
<div
|
||||||
|
onDragOver={dragOver}
|
||||||
|
onDragEnter={dragEnter}
|
||||||
|
onDragLeave={dragLeave}
|
||||||
|
onDrop={fileDrop}
|
||||||
|
className={
|
||||||
|
"h-full w-full " +
|
||||||
|
(isDragging
|
||||||
|
? "mb-24 flex flex-col items-center justify-center gap-4 text-2xl font-light"
|
||||||
|
: "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isDragging ? (
|
||||||
|
<>
|
||||||
|
<IconComponent
|
||||||
|
name="ArrowUpToLine"
|
||||||
|
className="h-12 w-12 stroke-1"
|
||||||
|
/>
|
||||||
|
Drop your flow here
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<div className="main-page-flows-display">
|
<div className="main-page-flows-display">
|
||||||
{isLoading && flows.length == 0 ? (
|
{isLoading && flows.length == 0 ? (
|
||||||
<>
|
<>
|
||||||
|
|
@ -118,6 +168,8 @@ export default function HomePage(): JSX.Element {
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
173
src/frontend/src/pages/ProfileSettingsPage/index.tsx
Normal file
173
src/frontend/src/pages/ProfileSettingsPage/index.tsx
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
import * as Form from "@radix-ui/react-form";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
|
import GradientChooserComponent from "../../components/gradientChooserComponent";
|
||||||
|
import Header from "../../components/headerComponent";
|
||||||
|
import InputComponent from "../../components/inputComponent";
|
||||||
|
import { Button } from "../../components/ui/button";
|
||||||
|
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
|
||||||
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
|
import { resetPassword, updateUser } from "../../controllers/API";
|
||||||
|
import {
|
||||||
|
inputHandlerEventType,
|
||||||
|
patchUserInputStateType,
|
||||||
|
} from "../../types/components";
|
||||||
|
import { gradients } from "../../utils/styleUtils";
|
||||||
|
export default function ProfileSettingsPage(): JSX.Element {
|
||||||
|
const { setTabId } = useContext(TabsContext);
|
||||||
|
|
||||||
|
const [inputState, setInputState] = useState<patchUserInputStateType>(
|
||||||
|
CONTROL_PATCH_USER_STATE
|
||||||
|
);
|
||||||
|
|
||||||
|
// set null id
|
||||||
|
useEffect(() => {
|
||||||
|
setTabId("");
|
||||||
|
}, []);
|
||||||
|
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||||
|
const { userData, setUserData } = useContext(AuthContext);
|
||||||
|
const { password, cnfPassword, gradient } = inputState;
|
||||||
|
|
||||||
|
async function handlePatchUser() {
|
||||||
|
if (password !== cnfPassword) {
|
||||||
|
setErrorData({
|
||||||
|
title: "Error changing password",
|
||||||
|
list: ["Passwords do not match"],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (password !== "") await resetPassword(userData!.id, { password });
|
||||||
|
if (gradient !== "")
|
||||||
|
await updateUser(userData!.id, { profile_image: gradient });
|
||||||
|
if (gradient !== "") {
|
||||||
|
let newUserData = cloneDeep(userData);
|
||||||
|
newUserData!.profile_image = gradient;
|
||||||
|
|
||||||
|
setUserData(newUserData);
|
||||||
|
}
|
||||||
|
handleInput({ target: { name: "password", value: "" } });
|
||||||
|
handleInput({ target: { name: "cnfPassword", value: "" } });
|
||||||
|
setSuccessData({ title: "Changes saved successfully!" });
|
||||||
|
} catch (error) {
|
||||||
|
setErrorData({
|
||||||
|
title: "Error saving changes",
|
||||||
|
list: [(error as any).response.data.detail],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInput({
|
||||||
|
target: { name, value },
|
||||||
|
}: inputHandlerEventType): void {
|
||||||
|
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<div className="community-page-arrangement">
|
||||||
|
<div className="community-page-nav-arrangement">
|
||||||
|
<span className="community-page-nav-title">
|
||||||
|
<IconComponent name="User" className="w-6" />
|
||||||
|
Profile Settings
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="community-page-description-text">
|
||||||
|
Change your profile settings like your password and your profile
|
||||||
|
picture.
|
||||||
|
</span>
|
||||||
|
<Form.Root
|
||||||
|
onSubmit={(event) => {
|
||||||
|
handlePatchUser();
|
||||||
|
const data = Object.fromEntries(new FormData(event.currentTarget));
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
|
className="flex h-full flex-col px-6 pb-16"
|
||||||
|
>
|
||||||
|
<div className="flex h-full flex-col gap-4">
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="mb-3 w-96">
|
||||||
|
<Form.Field name="password">
|
||||||
|
<Form.Label className="data-[invalid]:label-invalid">
|
||||||
|
Password{" "}
|
||||||
|
</Form.Label>
|
||||||
|
<InputComponent
|
||||||
|
onChange={(value) => {
|
||||||
|
handleInput({ target: { name: "password", value } });
|
||||||
|
}}
|
||||||
|
value={password}
|
||||||
|
isForm
|
||||||
|
password={true}
|
||||||
|
placeholder="Password"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<Form.Message match="valueMissing" className="field-invalid">
|
||||||
|
Please enter your password
|
||||||
|
</Form.Message>
|
||||||
|
</Form.Field>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3 w-96">
|
||||||
|
<Form.Field name="cnfPassword">
|
||||||
|
<Form.Label className="data-[invalid]:label-invalid">
|
||||||
|
Confirm Password{" "}
|
||||||
|
</Form.Label>
|
||||||
|
|
||||||
|
<InputComponent
|
||||||
|
onChange={(value) => {
|
||||||
|
handleInput({ target: { name: "cnfPassword", value } });
|
||||||
|
}}
|
||||||
|
value={cnfPassword}
|
||||||
|
isForm
|
||||||
|
password={true}
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Message className="field-invalid" match="valueMissing">
|
||||||
|
Please confirm your password
|
||||||
|
</Form.Message>
|
||||||
|
</Form.Field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Form.Field name="gradient">
|
||||||
|
<Form.Label className="data-[invalid]:label-invalid">
|
||||||
|
Profile Gradient{" "}
|
||||||
|
</Form.Label>
|
||||||
|
|
||||||
|
<div className="mt-4 w-[1010px]">
|
||||||
|
<GradientChooserComponent
|
||||||
|
value={
|
||||||
|
gradient == ""
|
||||||
|
? userData!.profile_image ??
|
||||||
|
gradients[
|
||||||
|
parseInt(userData!.id ?? "", 30) % gradients.length
|
||||||
|
]
|
||||||
|
: gradient
|
||||||
|
}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleInput({ target: { name: "gradient", value } });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Form.Field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex w-full justify-end">
|
||||||
|
<div className="w-32">
|
||||||
|
<Form.Submit asChild>
|
||||||
|
<Button className="mr-3 mt-6 w-full" type="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Form.Submit>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form.Root>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -54,7 +54,7 @@ export default function LoginPage(): JSX.Element {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
getLoggedUser()
|
getLoggedUser()
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
const isSuperUser = user.is_superuser;
|
const isSuperUser = user!.is_superuser;
|
||||||
setIsAdmin(isSuperUser);
|
setIsAdmin(isSuperUser);
|
||||||
setUserData(user);
|
setUserData(user);
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import ApiKeysPage from "./pages/ApiKeysPage";
|
||||||
import CommunityPage from "./pages/CommunityPage";
|
import CommunityPage from "./pages/CommunityPage";
|
||||||
import FlowPage from "./pages/FlowPage";
|
import FlowPage from "./pages/FlowPage";
|
||||||
import HomePage from "./pages/MainPage";
|
import HomePage from "./pages/MainPage";
|
||||||
|
import ProfileSettingsPage from "./pages/ProfileSettingsPage";
|
||||||
import ViewPage from "./pages/ViewPage";
|
import ViewPage from "./pages/ViewPage";
|
||||||
import DeleteAccountPage from "./pages/deleteAccountPage";
|
import DeleteAccountPage from "./pages/deleteAccountPage";
|
||||||
import LoginPage from "./pages/loginPage";
|
import LoginPage from "./pages/loginPage";
|
||||||
|
|
@ -95,6 +96,14 @@ const Router = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path="/account">
|
<Route path="/account">
|
||||||
|
<Route
|
||||||
|
path="settings"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<ProfileSettingsPage />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="delete"
|
path="delete"
|
||||||
element={
|
element={
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,10 @@
|
||||||
@apply flex-max-width h-full flex-col overflow-auto bg-muted px-16;
|
@apply flex-max-width h-full flex-col overflow-auto bg-muted px-16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-page-panel {
|
||||||
|
@apply flex-max-width h-full flex-col overflow-auto bg-muted px-16;
|
||||||
|
}
|
||||||
|
|
||||||
.main-page-nav-arrangement {
|
.main-page-nav-arrangement {
|
||||||
@apply flex-max-width justify-between px-6 py-12 pb-2;
|
@apply flex-max-width justify-between px-6 py-12 pb-2;
|
||||||
}
|
}
|
||||||
|
|
@ -228,6 +232,10 @@
|
||||||
@apply flex w-[60%] px-6 pb-14 text-muted-foreground;
|
@apply flex w-[60%] px-6 pb-14 text-muted-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-page-description-text {
|
||||||
|
@apply flex w-[80%] px-6 pb-8 text-muted-foreground;
|
||||||
|
}
|
||||||
|
|
||||||
.main-page-flows-display {
|
.main-page-flows-display {
|
||||||
@apply grid w-full gap-4 p-4 md:grid-cols-2 lg:grid-cols-4;
|
@apply grid w-full gap-4 p-4 md:grid-cols-2 lg:grid-cols-4;
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +311,7 @@
|
||||||
@apply hover:text-accent-foreground hover:transition-all;
|
@apply hover:text-accent-foreground hover:transition-all;
|
||||||
}
|
}
|
||||||
.generic-node-desc {
|
.generic-node-desc {
|
||||||
@apply h-full w-full py-5 text-foreground;
|
@apply h-full w-full text-foreground;
|
||||||
}
|
}
|
||||||
.generic-node-desc-text {
|
.generic-node-desc-text {
|
||||||
@apply w-full px-5 pb-3 text-sm text-muted-foreground;
|
@apply w-full px-5 pb-3 text-sm text-muted-foreground;
|
||||||
|
|
|
||||||
|
|
@ -78,11 +78,25 @@ export type LoginAuthType = {
|
||||||
token_type?: string;
|
token_type?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type changeUser = {
|
||||||
|
username?: string;
|
||||||
|
is_active?: boolean;
|
||||||
|
is_superuser?: boolean;
|
||||||
|
password?: string;
|
||||||
|
profile_image?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type resetPasswordType = {
|
||||||
|
password?: string;
|
||||||
|
profile_image?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type Users = {
|
export type Users = {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
is_superuser: boolean;
|
is_superuser: boolean;
|
||||||
|
profile_image: string;
|
||||||
create_at: Date;
|
create_at: Date;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,7 @@ export type PaginatorComponentType = {
|
||||||
export type ConfirmationModalType = {
|
export type ConfirmationModalType = {
|
||||||
title: string;
|
title: string;
|
||||||
titleHeader: string;
|
titleHeader: string;
|
||||||
|
asChild?: boolean;
|
||||||
modalContent: string;
|
modalContent: string;
|
||||||
modalContentTitle: string;
|
modalContentTitle: string;
|
||||||
cancelText: string;
|
cancelText: string;
|
||||||
|
|
@ -268,6 +269,7 @@ export type UserManagementType = {
|
||||||
icon: string;
|
icon: string;
|
||||||
data?: any;
|
data?: any;
|
||||||
index?: number;
|
index?: number;
|
||||||
|
asChild?: boolean;
|
||||||
onConfirm: (index, data) => void;
|
onConfirm: (index, data) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -276,6 +278,12 @@ export type loginInputStateType = {
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type patchUserInputStateType = {
|
||||||
|
password: string;
|
||||||
|
cnfPassword: string;
|
||||||
|
gradient: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type UserInputType = {
|
export type UserInputType = {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ export type darkContextType = {
|
||||||
setDark: (newState: {}) => void;
|
setDark: (newState: {}) => void;
|
||||||
stars: number;
|
stars: number;
|
||||||
setStars: (stars: number) => void;
|
setStars: (stars: number) => void;
|
||||||
|
gradientIndex: number;
|
||||||
|
setGradientIndex: (index: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type locationContextType = {
|
export type locationContextType = {
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,9 @@ export function addVersionToDuplicates(flow: FlowType, flows: FlowType[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleKeyDown(
|
export function handleKeyDown(
|
||||||
e: React.KeyboardEvent<HTMLInputElement>,
|
e:
|
||||||
|
| React.KeyboardEvent<HTMLInputElement>
|
||||||
|
| React.KeyboardEvent<HTMLTextAreaElement>,
|
||||||
inputValue: string | string[] | null,
|
inputValue: string | string[] | null,
|
||||||
block: string
|
block: string
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
ArrowUpToLine,
|
||||||
Bell,
|
Bell,
|
||||||
Check,
|
Check,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
|
|
@ -58,6 +59,7 @@ import {
|
||||||
Scissors,
|
Scissors,
|
||||||
Search,
|
Search,
|
||||||
Settings2,
|
Settings2,
|
||||||
|
Shield,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
SunIcon,
|
SunIcon,
|
||||||
TerminalSquare,
|
TerminalSquare,
|
||||||
|
|
@ -65,6 +67,7 @@ import {
|
||||||
Undo,
|
Undo,
|
||||||
Unplug,
|
Unplug,
|
||||||
Upload,
|
Upload,
|
||||||
|
User,
|
||||||
UserCog2,
|
UserCog2,
|
||||||
UserMinus2,
|
UserMinus2,
|
||||||
UserPlus2,
|
UserPlus2,
|
||||||
|
|
@ -87,7 +90,7 @@ import { EvernoteIcon } from "../icons/Evernote";
|
||||||
import { FBIcon } from "../icons/FacebookMessenger";
|
import { FBIcon } from "../icons/FacebookMessenger";
|
||||||
import { GitBookIcon } from "../icons/GitBook";
|
import { GitBookIcon } from "../icons/GitBook";
|
||||||
import { GoogleIcon } from "../icons/Google";
|
import { GoogleIcon } from "../icons/Google";
|
||||||
import GradientSparkles from "../icons/GradientSparkles";
|
import { GradientSparkles } from "../icons/GradientSparkles";
|
||||||
import { HuggingFaceIcon } from "../icons/HuggingFace";
|
import { HuggingFaceIcon } from "../icons/HuggingFace";
|
||||||
import { IFixIcon } from "../icons/IFixIt";
|
import { IFixIcon } from "../icons/IFixIt";
|
||||||
import { MetaIcon } from "../icons/Meta";
|
import { MetaIcon } from "../icons/Meta";
|
||||||
|
|
@ -187,6 +190,7 @@ export const nodeNames: { [char: string]: string } = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const nodeIconsLucide: iconsType = {
|
export const nodeIconsLucide: iconsType = {
|
||||||
|
ArrowUpToLine: ArrowUpToLine,
|
||||||
Chroma: ChromaIcon,
|
Chroma: ChromaIcon,
|
||||||
AirbyteJSONLoader: AirbyteIcon,
|
AirbyteJSONLoader: AirbyteIcon,
|
||||||
Anthropic: AnthropicIcon,
|
Anthropic: AnthropicIcon,
|
||||||
|
|
@ -221,6 +225,7 @@ export const nodeIconsLucide: iconsType = {
|
||||||
ChatVertexAI: VertexAIIcon,
|
ChatVertexAI: VertexAIIcon,
|
||||||
VertexAIEmbeddings: VertexAIIcon,
|
VertexAIEmbeddings: VertexAIIcon,
|
||||||
agents: Rocket,
|
agents: Rocket,
|
||||||
|
User,
|
||||||
WikipediaAPIWrapper: SvgWikipedia,
|
WikipediaAPIWrapper: SvgWikipedia,
|
||||||
chains: Link,
|
chains: Link,
|
||||||
memories: Cpu,
|
memories: Cpu,
|
||||||
|
|
@ -261,6 +266,7 @@ export const nodeIconsLucide: iconsType = {
|
||||||
Bell,
|
Bell,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
|
Shield,
|
||||||
Plus,
|
Plus,
|
||||||
Redo,
|
Redo,
|
||||||
Settings2,
|
Settings2,
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,8 @@ def test_user(client):
|
||||||
username="testuser",
|
username="testuser",
|
||||||
password="testpassword",
|
password="testpassword",
|
||||||
)
|
)
|
||||||
response = client.post("/api/v1/user", json=user_data.dict())
|
response = client.post("/api/v1/users", json=user_data.dict())
|
||||||
|
assert response.status_code == 201
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,24 +78,27 @@ def test_deactivated_user_cannot_access(client, deactivated_user, logged_in_head
|
||||||
assert response.json()["detail"] == "The user doesn't have enough privileges"
|
assert response.json()["detail"] == "The user doesn't have enough privileges"
|
||||||
|
|
||||||
|
|
||||||
def test_data_consistency_after_update(client, active_user, logged_in_headers):
|
def test_data_consistency_after_update(
|
||||||
|
client, active_user, logged_in_headers, super_user_headers
|
||||||
|
):
|
||||||
user_id = active_user.id
|
user_id = active_user.id
|
||||||
update_data = UserUpdate(username="newname")
|
update_data = UserUpdate(is_active=False)
|
||||||
|
|
||||||
response = client.patch(
|
response = client.patch(
|
||||||
f"/api/v1/user/{user_id}", json=update_data.dict(), headers=logged_in_headers
|
f"/api/v1/users/{user_id}", json=update_data.dict(), headers=super_user_headers
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200, response.json()
|
||||||
|
|
||||||
# Fetch the updated user from the database
|
# Fetch the updated user from the database
|
||||||
response = client.get("/api/v1/user", headers=logged_in_headers)
|
response = client.get("/api/v1/users/whoami", headers=logged_in_headers)
|
||||||
assert response.json()["username"] == "newname", response.json()
|
assert response.status_code == 401, response.json()
|
||||||
|
assert response.json()["detail"] == "Could not validate credentials"
|
||||||
|
|
||||||
|
|
||||||
def test_data_consistency_after_delete(client, test_user, super_user_headers):
|
def test_data_consistency_after_delete(client, test_user, super_user_headers):
|
||||||
user_id = test_user["id"]
|
user_id = test_user.get("id")
|
||||||
response = client.delete(f"/api/v1/user/{user_id}", headers=super_user_headers)
|
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200, response.json()
|
||||||
|
|
||||||
# Attempt to fetch the deleted user from the database
|
# Attempt to fetch the deleted user from the database
|
||||||
response = client.get("/api/v1/users", headers=super_user_headers)
|
response = client.get("/api/v1/users", headers=super_user_headers)
|
||||||
|
|
@ -157,11 +160,37 @@ def test_patch_user(client, active_user, logged_in_headers):
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.patch(
|
response = client.patch(
|
||||||
f"/api/v1/user/{user_id}", json=update_data.dict(), headers=logged_in_headers
|
f"/api/v1/users/{user_id}", json=update_data.dict(), headers=logged_in_headers
|
||||||
|
)
|
||||||
|
assert response.status_code == 304, response.json()
|
||||||
|
update_data = UserUpdate(
|
||||||
|
profile_image="new_image",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.patch(
|
||||||
|
f"/api/v1/users/{user_id}", json=update_data.dict(), headers=logged_in_headers
|
||||||
)
|
)
|
||||||
assert response.status_code == 200, response.json()
|
assert response.status_code == 200, response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def test_patch_reset_password(client, active_user, logged_in_headers):
|
||||||
|
user_id = active_user.id
|
||||||
|
update_data = UserUpdate(
|
||||||
|
password="newpassword",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.patch(
|
||||||
|
f"/api/v1/users/{user_id}/reset-password",
|
||||||
|
json=update_data.dict(),
|
||||||
|
headers=logged_in_headers,
|
||||||
|
)
|
||||||
|
assert response.status_code == 200, response.json()
|
||||||
|
# Now we need to test if the new password works
|
||||||
|
login_data = {"username": active_user.username, "password": "newpassword"}
|
||||||
|
response = client.post("/api/v1/login", data=login_data)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_patch_user_wrong_id(client, active_user, logged_in_headers):
|
def test_patch_user_wrong_id(client, active_user, logged_in_headers):
|
||||||
user_id = "wrong_id"
|
user_id = "wrong_id"
|
||||||
update_data = UserUpdate(
|
update_data = UserUpdate(
|
||||||
|
|
@ -169,7 +198,7 @@ def test_patch_user_wrong_id(client, active_user, logged_in_headers):
|
||||||
)
|
)
|
||||||
|
|
||||||
response = client.patch(
|
response = client.patch(
|
||||||
f"/api/v1/user/{user_id}", json=update_data.dict(), headers=logged_in_headers
|
f"/api/v1/users/{user_id}", json=update_data.dict(), headers=logged_in_headers
|
||||||
)
|
)
|
||||||
assert response.status_code == 422, response.json()
|
assert response.status_code == 422, response.json()
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
|
|
@ -185,14 +214,14 @@ def test_patch_user_wrong_id(client, active_user, logged_in_headers):
|
||||||
|
|
||||||
def test_delete_user(client, test_user, super_user_headers):
|
def test_delete_user(client, test_user, super_user_headers):
|
||||||
user_id = test_user["id"]
|
user_id = test_user["id"]
|
||||||
response = client.delete(f"/api/v1/user/{user_id}", headers=super_user_headers)
|
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"detail": "User deleted"}
|
assert response.json() == {"detail": "User deleted"}
|
||||||
|
|
||||||
|
|
||||||
def test_delete_user_wrong_id(client, test_user, super_user_headers):
|
def test_delete_user_wrong_id(client, test_user, super_user_headers):
|
||||||
user_id = "wrong_id"
|
user_id = "wrong_id"
|
||||||
response = client.delete(f"/api/v1/user/{user_id}", headers=super_user_headers)
|
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
|
||||||
assert response.status_code == 422
|
assert response.status_code == 422
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"detail": [
|
"detail": [
|
||||||
|
|
@ -207,13 +236,6 @@ def test_delete_user_wrong_id(client, test_user, super_user_headers):
|
||||||
|
|
||||||
def test_normal_user_cant_delete_user(client, test_user, logged_in_headers):
|
def test_normal_user_cant_delete_user(client, test_user, logged_in_headers):
|
||||||
user_id = test_user["id"]
|
user_id = test_user["id"]
|
||||||
response = client.delete(f"/api/v1/user/{user_id}", headers=logged_in_headers)
|
response = client.delete(f"/api/v1/users/{user_id}", headers=logged_in_headers)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert response.json() == {"detail": "The user doesn't have enough privileges"}
|
assert response.json() == {"detail": "The user doesn't have enough privileges"}
|
||||||
|
|
||||||
|
|
||||||
# If you still want to test the superuser endpoint
|
|
||||||
def test_add_super_user_for_testing_purposes_delete_me_before_merge_into_dev(client):
|
|
||||||
response = client.post("/api/v1/super_user")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.json()["username"] == "superuser"
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue