Merge branch 'main' into lf-ffs
This commit is contained in:
commit
8fef1c7976
35 changed files with 6942 additions and 5187 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
44
.env.example
44
.env.example
|
|
@ -1,42 +1,60 @@
|
||||||
# Ingestion Configuration
|
# Ingestion Configuration
|
||||||
# Set to true to disable Langflow ingestion and use traditional OpenRAG processor
|
# Set to true to disable Langflow ingestion and use the traditional OpenRAG processor.
|
||||||
# If unset or false, Langflow pipeline will be used (default: upload -> ingest -> delete)
|
# If unset or false, the Langflow pipeline is used (default: upload -> ingest -> delete).
|
||||||
DISABLE_INGEST_WITH_LANGFLOW=false
|
DISABLE_INGEST_WITH_LANGFLOW=false
|
||||||
# make one like so https://docs.langflow.org/api-keys-and-authentication#langflow-secret-key
|
|
||||||
|
# Create a Langflow secret key:
|
||||||
|
# https://docs.langflow.org/api-keys-and-authentication#langflow-secret-key
|
||||||
LANGFLOW_SECRET_KEY=
|
LANGFLOW_SECRET_KEY=
|
||||||
|
|
||||||
# flow ids for chat and ingestion flows
|
|
||||||
|
# Flow IDs for chat and ingestion
|
||||||
LANGFLOW_CHAT_FLOW_ID=1098eea1-6649-4e1d-aed1-b77249fb8dd0
|
LANGFLOW_CHAT_FLOW_ID=1098eea1-6649-4e1d-aed1-b77249fb8dd0
|
||||||
LANGFLOW_INGEST_FLOW_ID=5488df7c-b93f-4f87-a446-b67028bc0813
|
LANGFLOW_INGEST_FLOW_ID=5488df7c-b93f-4f87-a446-b67028bc0813
|
||||||
# Ingest flow using docling
|
# Ingest flow using Docling
|
||||||
# LANGFLOW_INGEST_FLOW_ID=1402618b-e6d1-4ff2-9a11-d6ce71186915
|
# LANGFLOW_INGEST_FLOW_ID=1402618b-e6d1-4ff2-9a11-d6ce71186915
|
||||||
NUDGES_FLOW_ID=ebc01d31-1976-46ce-a385-b0240327226c
|
NUDGES_FLOW_ID=ebc01d31-1976-46ce-a385-b0240327226c
|
||||||
|
|
||||||
# Set a strong admin password for OpenSearch; a bcrypt hash is generated at
|
|
||||||
# container startup from this value. Do not commit real secrets.
|
# OpenSearch Auth
|
||||||
# must match the hashed password in secureconfig, must change for secure deployment!!!
|
# Set a strong admin password for OpenSearch.
|
||||||
|
# A bcrypt hash is generated at container startup from this value.
|
||||||
|
# Do not commit real secrets.
|
||||||
|
# Must be changed for secure deployments.
|
||||||
OPENSEARCH_PASSWORD=
|
OPENSEARCH_PASSWORD=
|
||||||
|
|
||||||
# make here https://console.cloud.google.com/apis/credentials
|
|
||||||
|
# Google OAuth
|
||||||
|
# Create credentials here:
|
||||||
|
# https://console.cloud.google.com/apis/credentials
|
||||||
GOOGLE_OAUTH_CLIENT_ID=
|
GOOGLE_OAUTH_CLIENT_ID=
|
||||||
GOOGLE_OAUTH_CLIENT_SECRET=
|
GOOGLE_OAUTH_CLIENT_SECRET=
|
||||||
|
|
||||||
# Azure app registration credentials for SharePoint/OneDrive
|
|
||||||
|
# Microsoft (SharePoint/OneDrive) OAuth
|
||||||
|
# Azure app registration credentials.
|
||||||
MICROSOFT_GRAPH_OAUTH_CLIENT_ID=
|
MICROSOFT_GRAPH_OAUTH_CLIENT_ID=
|
||||||
MICROSOFT_GRAPH_OAUTH_CLIENT_SECRET=
|
MICROSOFT_GRAPH_OAUTH_CLIENT_SECRET=
|
||||||
|
|
||||||
# OPTIONAL: dns routable from google (etc.) to handle continous ingest (something like ngrok works). This enables continous ingestion
|
|
||||||
|
# Webhooks (optional)
|
||||||
|
# Public, DNS-resolvable base URL (e.g., via ngrok) for continuous ingestion.
|
||||||
WEBHOOK_BASE_URL=
|
WEBHOOK_BASE_URL=
|
||||||
|
|
||||||
|
|
||||||
|
# API Keys
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
|
|
||||||
AWS_ACCESS_KEY_ID=
|
AWS_ACCESS_KEY_ID=
|
||||||
AWS_SECRET_ACCESS_KEY=
|
AWS_SECRET_ACCESS_KEY=
|
||||||
|
|
||||||
# OPTIONAL url for openrag link to langflow in the UI
|
|
||||||
|
# Langflow UI URL (optional)
|
||||||
|
# Public URL to link OpenRAG to Langflow in the UI.
|
||||||
LANGFLOW_PUBLIC_URL=
|
LANGFLOW_PUBLIC_URL=
|
||||||
|
|
||||||
# Langflow auth
|
|
||||||
|
# Langflow Auth
|
||||||
LANGFLOW_AUTO_LOGIN=False
|
LANGFLOW_AUTO_LOGIN=False
|
||||||
LANGFLOW_SUPERUSER=
|
LANGFLOW_SUPERUSER=
|
||||||
LANGFLOW_SUPERUSER_PASSWORD=
|
LANGFLOW_SUPERUSER_PASSWORD=
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ ENV RUSTFLAGS="--cfg reqwest_unstable"
|
||||||
|
|
||||||
# Accept build arguments for git repository and branch
|
# Accept build arguments for git repository and branch
|
||||||
ARG GIT_REPO=https://github.com/langflow-ai/langflow.git
|
ARG GIT_REPO=https://github.com/langflow-ai/langflow.git
|
||||||
ARG GIT_BRANCH=load_flows_autologin_false
|
ARG GIT_BRANCH=test-openai-responses
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@ services:
|
||||||
|
|
||||||
openrag-backend:
|
openrag-backend:
|
||||||
image: phact/openrag-backend:${OPENRAG_VERSION:-latest}
|
image: phact/openrag-backend:${OPENRAG_VERSION:-latest}
|
||||||
#build:
|
# build:
|
||||||
#context: .
|
# context: .
|
||||||
#dockerfile: Dockerfile.backend
|
# dockerfile: Dockerfile.backend
|
||||||
container_name: openrag-backend
|
container_name: openrag-backend
|
||||||
depends_on:
|
depends_on:
|
||||||
- langflow
|
- langflow
|
||||||
|
|
@ -77,9 +77,10 @@ services:
|
||||||
|
|
||||||
openrag-frontend:
|
openrag-frontend:
|
||||||
image: phact/openrag-frontend:${OPENRAG_VERSION:-latest}
|
image: phact/openrag-frontend:${OPENRAG_VERSION:-latest}
|
||||||
#build:
|
# build:
|
||||||
#context: .
|
# context: .
|
||||||
#dockerfile: Dockerfile.frontend
|
# dockerfile: Dockerfile.frontend
|
||||||
|
# #dockerfile: Dockerfile.frontend
|
||||||
container_name: openrag-frontend
|
container_name: openrag-frontend
|
||||||
depends_on:
|
depends_on:
|
||||||
- openrag-backend
|
- openrag-backend
|
||||||
|
|
@ -92,6 +93,9 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./flows:/app/flows:z
|
- ./flows:/app/flows:z
|
||||||
image: phact/openrag-langflow:${LANGFLOW_VERSION:-latest}
|
image: phact/openrag-langflow:${LANGFLOW_VERSION:-latest}
|
||||||
|
# build:
|
||||||
|
# context: .
|
||||||
|
# dockerfile: Dockerfile.langflow
|
||||||
container_name: langflow
|
container_name: langflow
|
||||||
ports:
|
ports:
|
||||||
- "7860:7860"
|
- "7860:7860"
|
||||||
|
|
@ -99,7 +103,7 @@ services:
|
||||||
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||||
- LANGFLOW_LOAD_FLOWS_PATH=/app/flows
|
- LANGFLOW_LOAD_FLOWS_PATH=/app/flows
|
||||||
- LANGFLOW_SECRET_KEY=${LANGFLOW_SECRET_KEY}
|
- LANGFLOW_SECRET_KEY=${LANGFLOW_SECRET_KEY}
|
||||||
- JWT="dummy"
|
- JWT=None
|
||||||
- OWNER=None
|
- OWNER=None
|
||||||
- OWNER_NAME=None
|
- OWNER_NAME=None
|
||||||
- OWNER_EMAIL=None
|
- OWNER_EMAIL=None
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,114 @@
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"id": "OllamaEmbeddings-4ah5Q",
|
|
||||||
"node": {
|
"node": {
|
||||||
"base_classes": [
|
"template": {
|
||||||
"Embeddings"
|
"_type": "Component",
|
||||||
],
|
"base_url": {
|
||||||
"beta": false,
|
"tool_mode": false,
|
||||||
"conditional_paths": [],
|
"trace_as_input": true,
|
||||||
"custom_fields": {},
|
"trace_as_metadata": true,
|
||||||
|
"load_from_db": true,
|
||||||
|
"list": false,
|
||||||
|
"list_add_label": "Add More",
|
||||||
|
"required": true,
|
||||||
|
"placeholder": "",
|
||||||
|
"show": true,
|
||||||
|
"name": "base_url",
|
||||||
|
"value": "OLLAMA_BASE_URL",
|
||||||
|
"display_name": "Ollama Base URL",
|
||||||
|
"advanced": false,
|
||||||
|
"input_types": ["Message"],
|
||||||
|
"dynamic": false,
|
||||||
|
"info": "",
|
||||||
|
"title_case": false,
|
||||||
|
"type": "str",
|
||||||
|
"_input_type": "MessageTextInput"
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "code",
|
||||||
|
"required": true,
|
||||||
|
"placeholder": "",
|
||||||
|
"list": false,
|
||||||
|
"show": true,
|
||||||
|
"multiline": true,
|
||||||
|
"value": "from typing import Any\nfrom urllib.parse import urljoin\n\nimport httpx\nfrom langchain_ollama import OllamaEmbeddings\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.ollama_constants import OLLAMA_EMBEDDING_MODELS, URL_LIST\nfrom lfx.field_typing import Embeddings\nfrom lfx.io import DropdownInput, MessageTextInput, Output\n\nHTTP_STATUS_OK = 200\n\n\nclass OllamaEmbeddingsComponent(LCModelComponent):\n display_name: str = \"Ollama Embeddings\"\n description: str = \"Generate embeddings using Ollama models.\"\n documentation = \"https://python.langchain.com/docs/integrations/text_embedding/ollama\"\n icon = \"Ollama\"\n name = \"OllamaEmbeddings\"\n\n inputs = [\n DropdownInput(\n name=\"model_name\",\n display_name=\"Ollama Model\",\n value=\"\",\n options=[],\n real_time_refresh=True,\n refresh_button=True,\n combobox=True,\n required=True,\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Ollama Base URL\",\n value=\"\",\n required=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Embeddings\", name=\"embeddings\", method=\"build_embeddings\"),\n ]\n\n def build_embeddings(self) -> Embeddings:\n try:\n output = OllamaEmbeddings(model=self.model_name, base_url=self.base_url)\n except Exception as e:\n msg = (\n \"Unable to connect to the Ollama API. \",\n \"Please verify the base URL, ensure the relevant Ollama model is pulled, and try again.\",\n )\n raise ValueError(msg) from e\n return output\n\n async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):\n if field_name in {\"base_url\", \"model_name\"} and not await self.is_valid_ollama_url(field_value):\n # Check if any URL in the list is valid\n valid_url = \"\"\n for url in URL_LIST:\n if await self.is_valid_ollama_url(url):\n valid_url = url\n break\n build_config[\"base_url\"][\"value\"] = valid_url\n if field_name in {\"model_name\", \"base_url\", \"tool_model_enabled\"}:\n if await self.is_valid_ollama_url(self.base_url):\n build_config[\"model_name\"][\"options\"] = await self.get_model(self.base_url)\n elif await self.is_valid_ollama_url(build_config[\"base_url\"].get(\"value\", \"\")):\n build_config[\"model_name\"][\"options\"] = await self.get_model(build_config[\"base_url\"].get(\"value\", \"\"))\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n return build_config\n\n async def get_model(self, base_url_value: str) -> list[str]:\n \"\"\"Get the model names from Ollama.\"\"\"\n model_ids = []\n try:\n url = urljoin(base_url_value, \"/api/tags\")\n async with httpx.AsyncClient() as client:\n response = await client.get(url)\n response.raise_for_status()\n data = response.json()\n\n model_ids = [model[\"name\"] for model in data.get(\"models\", [])]\n # this to ensure that not embedding models are included.\n # not even the base models since models can have 1b 2b etc\n # handles cases when embeddings models have tags like :latest - etc.\n model_ids = [\n model\n for model in model_ids\n if any(model.startswith(f\"{embedding_model}\") for embedding_model in OLLAMA_EMBEDDING_MODELS)\n ]\n\n except (ImportError, ValueError, httpx.RequestError) as e:\n msg = \"Could not get model names from Ollama.\"\n raise ValueError(msg) from e\n\n return model_ids\n\n async def is_valid_ollama_url(self, url: str) -> bool:\n try:\n async with httpx.AsyncClient() as client:\n return (await client.get(f\"{url}/api/tags\")).status_code == HTTP_STATUS_OK\n except httpx.RequestError:\n return False\n",
|
||||||
|
"fileTypes": [],
|
||||||
|
"file_path": "",
|
||||||
|
"password": false,
|
||||||
|
"name": "code",
|
||||||
|
"advanced": true,
|
||||||
|
"dynamic": true,
|
||||||
|
"info": "",
|
||||||
|
"load_from_db": false,
|
||||||
|
"title_case": false
|
||||||
|
},
|
||||||
|
"model_name": {
|
||||||
|
"tool_mode": false,
|
||||||
|
"trace_as_metadata": true,
|
||||||
|
"options": ["nomic-embed-text:latest", "all-minilm:latest"],
|
||||||
|
"options_metadata": [],
|
||||||
|
"combobox": true,
|
||||||
|
"dialog_inputs": {},
|
||||||
|
"toggle": false,
|
||||||
|
"required": true,
|
||||||
|
"placeholder": "",
|
||||||
|
"show": true,
|
||||||
|
"name": "model_name",
|
||||||
|
"value": "",
|
||||||
|
"display_name": "Ollama Model",
|
||||||
|
"advanced": false,
|
||||||
|
"dynamic": false,
|
||||||
|
"info": "",
|
||||||
|
"real_time_refresh": true,
|
||||||
|
"refresh_button": true,
|
||||||
|
"title_case": false,
|
||||||
|
"external_options": {},
|
||||||
|
"type": "str",
|
||||||
|
"_input_type": "DropdownInput"
|
||||||
|
}
|
||||||
|
},
|
||||||
"description": "Generate embeddings using Ollama models.",
|
"description": "Generate embeddings using Ollama models.",
|
||||||
|
"icon": "Ollama",
|
||||||
|
"base_classes": ["Embeddings"],
|
||||||
"display_name": "Ollama Embeddings",
|
"display_name": "Ollama Embeddings",
|
||||||
"documentation": "https://python.langchain.com/docs/integrations/text_embedding/ollama",
|
"documentation": "https://python.langchain.com/docs/integrations/text_embedding/ollama",
|
||||||
"edited": false,
|
"minimized": false,
|
||||||
"field_order": [
|
"custom_fields": {},
|
||||||
"model_name",
|
"output_types": [],
|
||||||
"base_url"
|
"pinned": false,
|
||||||
],
|
"conditional_paths": [],
|
||||||
"frozen": false,
|
"frozen": false,
|
||||||
"icon": "Ollama",
|
"outputs": [
|
||||||
"last_updated": "2025-09-22T20:18:27.128Z",
|
{
|
||||||
|
"types": ["Embeddings"],
|
||||||
|
"selected": "Embeddings",
|
||||||
|
"name": "embeddings",
|
||||||
|
"display_name": "Embeddings",
|
||||||
|
"method": "build_embeddings",
|
||||||
|
"value": "__UNDEFINED__",
|
||||||
|
"cache": true,
|
||||||
|
"required_inputs": null,
|
||||||
|
"allows_loop": false,
|
||||||
|
"group_outputs": false,
|
||||||
|
"options": null,
|
||||||
|
"tool_mode": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"field_order": ["model_name", "base_url"],
|
||||||
|
"beta": false,
|
||||||
"legacy": false,
|
"legacy": false,
|
||||||
|
"edited": false,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"code_hash": "0db0f99e91e9",
|
"keywords": [
|
||||||
|
"model",
|
||||||
|
"llm",
|
||||||
|
"language model",
|
||||||
|
"large language model"
|
||||||
|
],
|
||||||
|
"module": "lfx.components.ollama.ollama_embeddings.OllamaEmbeddingsComponent",
|
||||||
|
"code_hash": "c41821735548",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"total_dependencies": 3,
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"name": "httpx",
|
"name": "httpx",
|
||||||
|
|
@ -33,125 +119,24 @@
|
||||||
"version": "0.2.1"
|
"version": "0.2.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "langflow",
|
"name": "lfx",
|
||||||
"version": null
|
"version": null
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"total_dependencies": 3
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"model",
|
|
||||||
"llm",
|
|
||||||
"language model",
|
|
||||||
"large language model"
|
|
||||||
],
|
|
||||||
"module": "langflow.components.ollama.ollama_embeddings.OllamaEmbeddingsComponent"
|
|
||||||
},
|
|
||||||
"minimized": false,
|
|
||||||
"output_types": [],
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"allows_loop": false,
|
|
||||||
"cache": true,
|
|
||||||
"display_name": "Embeddings",
|
|
||||||
"group_outputs": false,
|
|
||||||
"method": "build_embeddings",
|
|
||||||
"name": "embeddings",
|
|
||||||
"options": null,
|
|
||||||
"required_inputs": null,
|
|
||||||
"selected": "Embeddings",
|
|
||||||
"tool_mode": true,
|
|
||||||
"types": [
|
|
||||||
"Embeddings"
|
|
||||||
],
|
|
||||||
"value": "__UNDEFINED__"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"pinned": false,
|
|
||||||
"template": {
|
|
||||||
"_type": "Component",
|
|
||||||
"base_url": {
|
|
||||||
"_input_type": "MessageTextInput",
|
|
||||||
"advanced": false,
|
|
||||||
"display_name": "Ollama Base URL",
|
|
||||||
"dynamic": false,
|
|
||||||
"info": "",
|
|
||||||
"input_types": [
|
|
||||||
"Message"
|
|
||||||
],
|
|
||||||
"list": false,
|
|
||||||
"list_add_label": "Add More",
|
|
||||||
"load_from_db": true,
|
|
||||||
"name": "base_url",
|
|
||||||
"placeholder": "",
|
|
||||||
"required": true,
|
|
||||||
"show": true,
|
|
||||||
"title_case": false,
|
|
||||||
"tool_mode": false,
|
|
||||||
"trace_as_input": true,
|
|
||||||
"trace_as_metadata": true,
|
|
||||||
"type": "str",
|
|
||||||
"value": "OLLAMA_BASE_URL"
|
|
||||||
},
|
|
||||||
"code": {
|
|
||||||
"advanced": true,
|
|
||||||
"dynamic": true,
|
|
||||||
"fileTypes": [],
|
|
||||||
"file_path": "",
|
|
||||||
"info": "",
|
|
||||||
"list": false,
|
|
||||||
"load_from_db": false,
|
|
||||||
"multiline": true,
|
|
||||||
"name": "code",
|
|
||||||
"password": false,
|
|
||||||
"placeholder": "",
|
|
||||||
"required": true,
|
|
||||||
"show": true,
|
|
||||||
"title_case": false,
|
|
||||||
"type": "code",
|
|
||||||
"value": "from typing import Any\nfrom urllib.parse import urljoin\n\nimport httpx\nfrom langchain_ollama import OllamaEmbeddings\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.ollama_constants import OLLAMA_EMBEDDING_MODELS, URL_LIST\nfrom langflow.field_typing import Embeddings\nfrom langflow.io import DropdownInput, MessageTextInput, Output\n\nHTTP_STATUS_OK = 200\n\n\nclass OllamaEmbeddingsComponent(LCModelComponent):\n display_name: str = \"Ollama Embeddings\"\n description: str = \"Generate embeddings using Ollama models.\"\n documentation = \"https://python.langchain.com/docs/integrations/text_embedding/ollama\"\n icon = \"Ollama\"\n name = \"OllamaEmbeddings\"\n\n inputs = [\n DropdownInput(\n name=\"model_name\",\n display_name=\"Ollama Model\",\n value=\"\",\n options=[],\n real_time_refresh=True,\n refresh_button=True,\n combobox=True,\n required=True,\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Ollama Base URL\",\n value=\"\",\n required=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Embeddings\", name=\"embeddings\", method=\"build_embeddings\"),\n ]\n\n def build_embeddings(self) -> Embeddings:\n try:\n output = OllamaEmbeddings(model=self.model_name, base_url=self.base_url)\n except Exception as e:\n msg = (\n \"Unable to connect to the Ollama API. \",\n \"Please verify the base URL, ensure the relevant Ollama model is pulled, and try again.\",\n )\n raise ValueError(msg) from e\n return output\n\n async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):\n if field_name in {\"base_url\", \"model_name\"} and not await self.is_valid_ollama_url(field_value):\n # Check if any URL in the list is valid\n valid_url = \"\"\n for url in URL_LIST:\n if await self.is_valid_ollama_url(url):\n valid_url = url\n break\n build_config[\"base_url\"][\"value\"] = valid_url\n if field_name in {\"model_name\", \"base_url\", \"tool_model_enabled\"}:\n if await self.is_valid_ollama_url(self.base_url):\n build_config[\"model_name\"][\"options\"] = await self.get_model(self.base_url)\n elif await self.is_valid_ollama_url(build_config[\"base_url\"].get(\"value\", \"\")):\n build_config[\"model_name\"][\"options\"] = await self.get_model(build_config[\"base_url\"].get(\"value\", \"\"))\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n return build_config\n\n async def get_model(self, base_url_value: str) -> list[str]:\n \"\"\"Get the model names from Ollama.\"\"\"\n model_ids = []\n try:\n url = urljoin(base_url_value, \"/api/tags\")\n async with httpx.AsyncClient() as client:\n response = await client.get(url)\n response.raise_for_status()\n data = response.json()\n\n model_ids = [model[\"name\"] for model in data.get(\"models\", [])]\n # this to ensure that not embedding models are included.\n # not even the base models since models can have 1b 2b etc\n # handles cases when embeddings models have tags like :latest - etc.\n model_ids = [\n model\n for model in model_ids\n if any(model.startswith(f\"{embedding_model}\") for embedding_model in OLLAMA_EMBEDDING_MODELS)\n ]\n\n except (ImportError, ValueError, httpx.RequestError) as e:\n msg = \"Could not get model names from Ollama.\"\n raise ValueError(msg) from e\n\n return model_ids\n\n async def is_valid_ollama_url(self, url: str) -> bool:\n try:\n async with httpx.AsyncClient() as client:\n return (await client.get(f\"{url}/api/tags\")).status_code == HTTP_STATUS_OK\n except httpx.RequestError:\n return False\n"
|
|
||||||
},
|
|
||||||
"model_name": {
|
|
||||||
"_input_type": "DropdownInput",
|
|
||||||
"advanced": false,
|
|
||||||
"combobox": true,
|
|
||||||
"dialog_inputs": {},
|
|
||||||
"display_name": "Ollama Model",
|
|
||||||
"dynamic": false,
|
|
||||||
"info": "",
|
|
||||||
"name": "model_name",
|
|
||||||
"options": [
|
|
||||||
"all-minilm:latest"
|
|
||||||
],
|
|
||||||
"options_metadata": [],
|
|
||||||
"placeholder": "",
|
|
||||||
"real_time_refresh": true,
|
|
||||||
"refresh_button": true,
|
|
||||||
"required": true,
|
|
||||||
"show": true,
|
|
||||||
"title_case": false,
|
|
||||||
"toggle": false,
|
|
||||||
"tool_mode": false,
|
|
||||||
"trace_as_metadata": true,
|
|
||||||
"type": "str",
|
|
||||||
"value": "all-minilm:latest"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tool_mode": false
|
"tool_mode": false,
|
||||||
|
"last_updated": "2025-09-29T18:40:10.242Z",
|
||||||
|
"official": false
|
||||||
},
|
},
|
||||||
"showNode": true,
|
"showNode": true,
|
||||||
"type": "OllamaEmbeddings"
|
"type": "OllamaEmbeddings",
|
||||||
},
|
"id": "OllamaEmbeddings-vnNn8"
|
||||||
"dragging": false,
|
|
||||||
"id": "OllamaEmbeddings-4ah5Q",
|
|
||||||
"measured": {
|
|
||||||
"height": 286,
|
|
||||||
"width": 320
|
|
||||||
},
|
},
|
||||||
|
"id": "OllamaEmbeddings-vnNn8",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 282.29416840859585,
|
"x": 0,
|
||||||
"y": 279.4218065717267
|
"y": 0
|
||||||
},
|
},
|
||||||
"selected": false,
|
|
||||||
"type": "genericNode"
|
"type": "genericNode"
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Plus } from "lucide-react";
|
||||||
import { Loader2, Plus } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
useGetFiltersSearchQuery,
|
useGetFiltersSearchQuery,
|
||||||
|
|
@ -65,97 +64,102 @@ export function KnowledgeFilterList({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex-1 min-h-0 flex flex-col">
|
||||||
<div className="flex flex-col gap-2 px-3 !mb-12 mt-0 h-full overflow-y-auto">
|
<div className="px-3 flex-1 min-h-0 flex flex-col">
|
||||||
<div className="flex items-center w-full justify-between pl-3">
|
<div className="flex-shrink-0">
|
||||||
<div className="text-sm font-medium text-muted-foreground">
|
<div className="flex items-center justify-between mb-3 ml-3 mr-2">
|
||||||
Knowledge Filters
|
<h3 className="text-xs font-medium text-muted-foreground">
|
||||||
</div>
|
Knowledge Filters
|
||||||
<Button
|
</h3>
|
||||||
variant="ghost"
|
<button
|
||||||
size="sm"
|
type="button"
|
||||||
onClick={handleCreateNew}
|
className="p-1 hover:bg-accent rounded"
|
||||||
title="Create New Filter"
|
onClick={handleCreateNew}
|
||||||
className="!h-8 w-8 px-0 text-muted-foreground"
|
title="Create New Filter"
|
||||||
>
|
|
||||||
<Plus className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{loading ? (
|
|
||||||
<div className="flex items-center justify-center p-4">
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
|
||||||
<span className="ml-2 text-sm text-muted-foreground">
|
|
||||||
Loading...
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : filters.length === 0 ? (
|
|
||||||
<div className="py-2 px-4 text-sm text-muted-foreground">
|
|
||||||
{searchQuery ? "No filters found" : "No saved filters"}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
filters.map((filter) => (
|
|
||||||
<div
|
|
||||||
key={filter.id}
|
|
||||||
onClick={() => handleFilterSelect(filter)}
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-3 px-3 py-2 w-full rounded-lg hover:bg-accent hover:text-accent-foreground cursor-pointer group transition-colors",
|
|
||||||
selectedFilter?.id === filter.id &&
|
|
||||||
"active bg-accent text-accent-foreground"
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-1 flex-1 min-w-0">
|
<Plus className="h-4 w-4 text-muted-foreground" />
|
||||||
<div className="flex items-center gap-2">
|
</button>
|
||||||
{(() => {
|
</div>
|
||||||
const parsed = parseQueryData(
|
<div className="overflow-y-auto scrollbar-hide space-y-1">
|
||||||
filter.query_data
|
{loading ? (
|
||||||
) as ParsedQueryData;
|
<div className="text-[13px] text-muted-foreground p-2 ml-1">
|
||||||
const Icon = iconKeyToComponent(parsed.icon);
|
Loading...
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex items-center justify-center w-5 h-5 rounded flex-shrink-0 transition-colors",
|
|
||||||
filterAccentClasses[parsed.color],
|
|
||||||
parsed.color === "zinc" &&
|
|
||||||
"group-hover:bg-background group-[.active]:bg-background"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{Icon && <Icon className="h-3 w-3" />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
<div className="text-sm font-medium truncate group-hover:text-accent-foreground">
|
|
||||||
{filter.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{filter.description && (
|
|
||||||
<div className="text-xs text-muted-foreground line-clamp-2">
|
|
||||||
{filter.description}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="text-xs text-muted-foreground">
|
|
||||||
{new Date(filter.created_at).toLocaleDateString(undefined, {
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
year: "numeric",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<span className="text-xs bg-muted text-muted-foreground px-1 py-0.5 rounded-sm group-hover:bg-background group-[.active]:bg-background transition-colors">
|
|
||||||
{(() => {
|
|
||||||
const dataSources = parseQueryData(filter.query_data)
|
|
||||||
.filters.data_sources;
|
|
||||||
if (dataSources[0] === "*") return "All sources";
|
|
||||||
const count = dataSources.length;
|
|
||||||
return `${count} ${count === 1 ? "source" : "sources"}`;
|
|
||||||
})()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : filters.length === 0 ? (
|
||||||
))
|
<div className="text-[13px] text-muted-foreground p-2 ml-1">
|
||||||
)}
|
{searchQuery ? "No filters found" : "No saved filters"}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filters.map(filter => (
|
||||||
|
<div
|
||||||
|
key={filter.id}
|
||||||
|
onClick={() => handleFilterSelect(filter)}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-3 px-3 py-2 w-full rounded-lg hover:bg-accent hover:text-accent-foreground cursor-pointer group transition-colors",
|
||||||
|
selectedFilter?.id === filter.id &&
|
||||||
|
"active bg-accent text-accent-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-1 flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{(() => {
|
||||||
|
const parsed = parseQueryData(
|
||||||
|
filter.query_data
|
||||||
|
) as ParsedQueryData;
|
||||||
|
const Icon = iconKeyToComponent(parsed.icon);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-center w-5 h-5 rounded transition-colors",
|
||||||
|
filterAccentClasses[parsed.color],
|
||||||
|
parsed.color === "zinc" &&
|
||||||
|
"group-hover:bg-background group-[.active]:bg-background"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{Icon && <Icon className="h-3 w-3" />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
<div className="text-sm font-medium truncate group-hover:text-accent-foreground">
|
||||||
|
{filter.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{filter.description && (
|
||||||
|
<div className="text-xs text-muted-foreground line-clamp-2">
|
||||||
|
{filter.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
{new Date(filter.created_at).toLocaleDateString(
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs bg-muted text-muted-foreground px-1 py-0.5 rounded-sm group-hover:bg-background group-[.active]:bg-background transition-colors">
|
||||||
|
{(() => {
|
||||||
|
const dataSources = parseQueryData(filter.query_data)
|
||||||
|
.filters.data_sources;
|
||||||
|
if (dataSources[0] === "*") return "All sources";
|
||||||
|
const count = dataSources.length;
|
||||||
|
return `${count} ${
|
||||||
|
count === 1 ? "source" : "sources"
|
||||||
|
}`;
|
||||||
|
})()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Create flow moved to panel create mode */}
|
||||||
</div>
|
</div>
|
||||||
{/* Create flow moved to panel create mode */}
|
</div>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useGetConversationsQuery, type ChatConversation } from "@/app/api/queries/useGetConversationsQuery";
|
import {
|
||||||
|
useGetConversationsQuery,
|
||||||
|
type ChatConversation,
|
||||||
|
} from "@/app/api/queries/useGetConversationsQuery";
|
||||||
import { KnowledgeFilterDropdown } from "@/components/knowledge-filter-dropdown";
|
import { KnowledgeFilterDropdown } from "@/components/knowledge-filter-dropdown";
|
||||||
import { ModeToggle } from "@/components/mode-toggle";
|
import { ModeToggle } from "@/components/mode-toggle";
|
||||||
import { Navigation } from "@/components/navigation";
|
import { Navigation } from "@/components/navigation";
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import {
|
||||||
FileText,
|
FileText,
|
||||||
Library,
|
Library,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
MoreHorizontal,
|
|
||||||
Plus,
|
Plus,
|
||||||
Settings2,
|
Settings2,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
|
@ -111,7 +110,7 @@ export function Navigation({
|
||||||
) {
|
) {
|
||||||
// Filter out the deleted conversation and find the next one
|
// Filter out the deleted conversation and find the next one
|
||||||
const remainingConversations = conversations.filter(
|
const remainingConversations = conversations.filter(
|
||||||
(conv) => conv.response_id !== conversationToDelete.response_id,
|
conv => conv.response_id !== conversationToDelete.response_id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (remainingConversations.length > 0) {
|
if (remainingConversations.length > 0) {
|
||||||
|
|
@ -132,7 +131,7 @@ export function Navigation({
|
||||||
setDeleteModalOpen(false);
|
setDeleteModalOpen(false);
|
||||||
setConversationToDelete(null);
|
setConversationToDelete(null);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: error => {
|
||||||
toast.error(`Failed to delete conversation: ${error.message}`);
|
toast.error(`Failed to delete conversation: ${error.message}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -164,7 +163,7 @@ export function Navigation({
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent("fileUploadStart", {
|
new CustomEvent("fileUploadStart", {
|
||||||
detail: { filename: file.name },
|
detail: { filename: file.name },
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -188,7 +187,7 @@ export function Navigation({
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
error: "Failed to process document",
|
error: "Failed to process document",
|
||||||
},
|
},
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Trigger loading end event
|
// Trigger loading end event
|
||||||
|
|
@ -208,7 +207,7 @@ export function Navigation({
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent("fileUploaded", {
|
new CustomEvent("fileUploaded", {
|
||||||
detail: { file, result },
|
detail: { file, result },
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Trigger loading end event
|
// Trigger loading end event
|
||||||
|
|
@ -222,7 +221,7 @@ export function Navigation({
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent("fileUploadError", {
|
new CustomEvent("fileUploadError", {
|
||||||
detail: { filename: file.name, error: "Failed to process document" },
|
detail: { filename: file.name, error: "Failed to process document" },
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -244,7 +243,7 @@ export function Navigation({
|
||||||
|
|
||||||
const handleDeleteConversation = (
|
const handleDeleteConversation = (
|
||||||
conversation: ChatConversation,
|
conversation: ChatConversation,
|
||||||
event?: React.MouseEvent,
|
event?: React.MouseEvent
|
||||||
) => {
|
) => {
|
||||||
if (event) {
|
if (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -256,7 +255,7 @@ export function Navigation({
|
||||||
|
|
||||||
const handleContextMenuAction = (
|
const handleContextMenuAction = (
|
||||||
action: string,
|
action: string,
|
||||||
conversation: ChatConversation,
|
conversation: ChatConversation
|
||||||
) => {
|
) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "delete":
|
case "delete":
|
||||||
|
|
@ -332,33 +331,33 @@ export function Navigation({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full bg-background">
|
<div className="flex flex-col h-full bg-background">
|
||||||
<div className="px-3 py-2 flex-shrink-0">
|
<div className="px-4 py-2 flex-shrink-0">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{routes.map((route) => (
|
{routes.map(route => (
|
||||||
<div key={route.href}>
|
<div key={route.href}>
|
||||||
<Link
|
<Link
|
||||||
href={route.href}
|
href={route.href}
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm group flex p-3 w-full justify-start font-medium cursor-pointer hover:bg-accent hover:text-accent-foreground rounded-lg transition-all",
|
"text-[13px] group flex p-3 w-full justify-start font-medium cursor-pointer hover:bg-accent hover:text-accent-foreground rounded-lg transition-all",
|
||||||
route.active
|
route.active
|
||||||
? "bg-accent text-accent-foreground shadow-sm"
|
? "bg-accent text-accent-foreground shadow-sm"
|
||||||
: "text-foreground hover:text-accent-foreground",
|
: "text-foreground hover:text-accent-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center flex-1">
|
<div className="flex items-center flex-1">
|
||||||
<route.icon
|
<route.icon
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4 mr-3 shrink-0",
|
"h-[18px] w-[18px] mr-2 shrink-0",
|
||||||
route.active
|
route.active
|
||||||
? "text-accent-foreground"
|
? "text-muted-foreground"
|
||||||
: "text-muted-foreground group-hover:text-foreground",
|
: "text-muted-foreground group-hover:text-muted-foreground"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{route.label}
|
{route.label}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{route.label === "Settings" && (
|
{route.label === "Settings" && (
|
||||||
<div className="mx-3 my-2 border-t border-border/40" />
|
<div className="my-2 border-t border-border" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -374,11 +373,11 @@ export function Navigation({
|
||||||
|
|
||||||
{/* Chat Page Specific Sections */}
|
{/* Chat Page Specific Sections */}
|
||||||
{isOnChatPage && (
|
{isOnChatPage && (
|
||||||
<div className="flex-1 min-h-0 flex flex-col">
|
<div className="flex-1 min-h-0 flex flex-col px-4">
|
||||||
{/* Conversations Section */}
|
{/* Conversations Section */}
|
||||||
<div className="px-3 flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3 mx-3">
|
||||||
<h3 className="text-sm font-medium text-muted-foreground">
|
<h3 className="text-xs font-medium text-muted-foreground">
|
||||||
Conversations
|
Conversations
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
|
|
@ -393,11 +392,11 @@ export function Navigation({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-3 flex-1 min-h-0 flex flex-col">
|
<div className="flex-1 min-h-0 flex flex-col">
|
||||||
{/* Conversations List - grows naturally, doesn't fill all space */}
|
{/* Conversations List - grows naturally, doesn't fill all space */}
|
||||||
<div className="flex-shrink-0 overflow-y-auto scrollbar-hide space-y-1 max-h-full">
|
<div className="flex-shrink-0 overflow-y-auto scrollbar-hide space-y-1 max-h-full">
|
||||||
{loadingNewConversation || isConversationsLoading ? (
|
{loadingNewConversation || isConversationsLoading ? (
|
||||||
<div className="text-sm text-muted-foreground p-2">
|
<div className="text-[13px] text-muted-foreground p-2">
|
||||||
Loading...
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -406,7 +405,7 @@ export function Navigation({
|
||||||
{placeholderConversation && (
|
{placeholderConversation && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-full p-2 rounded-lg bg-accent/50 border border-dashed border-accent cursor-pointer group text-left"
|
className="w-full px-3 rounded-lg bg-accent border border-dashed border-accent cursor-pointer group text-left h-[44px]"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// Don't load placeholder as a real conversation, just focus the input
|
// Don't load placeholder as a real conversation, just focus the input
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
|
|
@ -414,7 +413,7 @@ export function Navigation({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-sm font-medium text-foreground mb-1 truncate">
|
<div className="text-[13px] font-medium text-foreground truncate">
|
||||||
{placeholderConversation.title}
|
{placeholderConversation.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
|
|
@ -425,15 +424,15 @@ export function Navigation({
|
||||||
|
|
||||||
{/* Show regular conversations */}
|
{/* Show regular conversations */}
|
||||||
{conversations.length === 0 && !placeholderConversation ? (
|
{conversations.length === 0 && !placeholderConversation ? (
|
||||||
<div className="text-sm text-muted-foreground p-2">
|
<div className="text-[13px] text-muted-foreground py-2 pl-3">
|
||||||
No conversations yet
|
No conversations yet
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
conversations.map((conversation) => (
|
conversations.map(conversation => (
|
||||||
<button
|
<button
|
||||||
key={conversation.response_id}
|
key={conversation.response_id}
|
||||||
type="button"
|
type="button"
|
||||||
className={`w-full px-3 pr-2 h-11 rounded-lg group relative text-left ${
|
className={`w-full px-3 h-11 rounded-lg group relative text-left ${
|
||||||
loading
|
loading
|
||||||
? "opacity-50 cursor-not-allowed"
|
? "opacity-50 cursor-not-allowed"
|
||||||
: "hover:bg-accent cursor-pointer"
|
: "hover:bg-accent cursor-pointer"
|
||||||
|
|
@ -456,17 +455,22 @@ export function Navigation({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger disabled={loading || deleteSessionMutation.isPending} asChild>
|
<DropdownMenuTrigger
|
||||||
|
disabled={
|
||||||
|
loading || deleteSessionMutation.isPending
|
||||||
|
}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="opacity-0 group-hover:opacity-100 data-[state=open]:opacity-100 data-[state=open]:text-foreground transition-opacity p-1 hover:bg-accent rounded text-muted-foreground hover:text-foreground ml-2 flex-shrink-0 cursor-pointer"
|
className="opacity-0 group-hover:opacity-100 data-[state=open]:opacity-100 data-[state=open]:text-foreground transition-opacity p-1 hover:bg-accent rounded text-muted-foreground hover:text-foreground ml-2 flex-shrink-0 cursor-pointer"
|
||||||
title="More options"
|
title="More options"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={(e) => {
|
onClick={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={e => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
@ -479,14 +483,14 @@ export function Navigation({
|
||||||
side="bottom"
|
side="bottom"
|
||||||
align="end"
|
align="end"
|
||||||
className="w-48"
|
className="w-48"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={e => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleContextMenuAction(
|
handleContextMenuAction(
|
||||||
"delete",
|
"delete",
|
||||||
conversation,
|
conversation
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="cursor-pointer text-destructive focus:text-destructive"
|
className="cursor-pointer text-destructive focus:text-destructive"
|
||||||
|
|
@ -506,8 +510,8 @@ export function Navigation({
|
||||||
|
|
||||||
{/* Conversation Knowledge Section - appears right after last conversation */}
|
{/* Conversation Knowledge Section - appears right after last conversation */}
|
||||||
<div className="flex-shrink-0 mt-4">
|
<div className="flex-shrink-0 mt-4">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3 mx-3">
|
||||||
<h3 className="text-sm font-medium text-muted-foreground">
|
<h3 className="text-xs font-medium text-muted-foreground">
|
||||||
Conversation knowledge
|
Conversation knowledge
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
|
|
@ -526,16 +530,16 @@ export function Navigation({
|
||||||
className="hidden"
|
className="hidden"
|
||||||
accept=".pdf,.doc,.docx,.txt,.md,.rtf,.odt"
|
accept=".pdf,.doc,.docx,.txt,.md,.rtf,.odt"
|
||||||
/>
|
/>
|
||||||
<div className="overflow-y-auto scrollbar-hide space-y-1 max-h-40">
|
<div className="overflow-y-auto scrollbar-hide space-y-1">
|
||||||
{conversationDocs.length === 0 ? (
|
{conversationDocs.length === 0 ? (
|
||||||
<div className="text-sm text-muted-foreground p-2">
|
<div className="text-[13px] text-muted-foreground py-2 px-3">
|
||||||
No documents yet
|
No documents yet
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
conversationDocs.map((doc) => (
|
conversationDocs.map(doc => (
|
||||||
<div
|
<div
|
||||||
key={`${doc.filename}-${doc.uploadTime.getTime()}`}
|
key={`${doc.filename}-${doc.uploadTime.getTime()}`}
|
||||||
className="p-2 rounded-lg hover:bg-accent cursor-pointer group flex items-center"
|
className="w-full px-3 h-11 rounded-lg hover:bg-accent cursor-pointer group flex items-center"
|
||||||
>
|
>
|
||||||
<FileText className="h-4 w-4 mr-2 text-muted-foreground flex-shrink-0" />
|
<FileText className="h-4 w-4 mr-2 text-muted-foreground flex-shrink-0" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,29 @@
|
||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
||||||
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const Switch = React.forwardRef<
|
const Switch = React.forwardRef<
|
||||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SwitchPrimitives.Root
|
<SwitchPrimitives.Root
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-muted",
|
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-muted",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<SwitchPrimitives.Thumb
|
<SwitchPrimitives.Thumb
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0 data-[state=unchecked]:bg-primary"
|
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0 data-[state=unchecked]:bg-primary",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</SwitchPrimitives.Root>
|
</SwitchPrimitives.Root>
|
||||||
))
|
));
|
||||||
Switch.displayName = SwitchPrimitives.Root.displayName
|
Switch.displayName = SwitchPrimitives.Root.displayName;
|
||||||
|
|
||||||
export { Switch }
|
export { Switch };
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export function ModelSelector({
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value && (!options.find((option) => option.value === value) && !custom)) {
|
if (value && value !== "" && (!options.find((option) => option.value === value) && !custom)) {
|
||||||
onValueChange("");
|
onValueChange("");
|
||||||
}
|
}
|
||||||
}, [options, value, custom, onValueChange]);
|
}, [options, value, custom, onValueChange]);
|
||||||
|
|
|
||||||
|
|
@ -1,87 +1,97 @@
|
||||||
import OpenAILogo from "@/components/logo/openai-logo";
|
|
||||||
import OllamaLogo from "@/components/logo/ollama-logo";
|
|
||||||
import IBMLogo from "@/components/logo/ibm-logo";
|
import IBMLogo from "@/components/logo/ibm-logo";
|
||||||
|
import OllamaLogo from "@/components/logo/ollama-logo";
|
||||||
|
import OpenAILogo from "@/components/logo/openai-logo";
|
||||||
|
|
||||||
export type ModelProvider = 'openai' | 'ollama' | 'ibm';
|
export type ModelProvider = "openai" | "ollama" | "watsonx";
|
||||||
|
|
||||||
export interface ModelOption {
|
export interface ModelOption {
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get model logo based on provider or model name
|
// Helper function to get model logo based on provider or model name
|
||||||
export function getModelLogo(modelValue: string, provider?: ModelProvider) {
|
export function getModelLogo(modelValue: string, provider?: ModelProvider) {
|
||||||
// First check by provider
|
// First check by provider
|
||||||
if (provider === 'openai') {
|
if (provider === "openai") {
|
||||||
return <OpenAILogo className="w-4 h-4" />;
|
return <OpenAILogo className="w-4 h-4" />;
|
||||||
} else if (provider === 'ollama') {
|
} else if (provider === "ollama") {
|
||||||
return <OllamaLogo className="w-4 h-4" />;
|
return <OllamaLogo className="w-4 h-4" />;
|
||||||
} else if (provider === 'ibm') {
|
} else if (provider === "watsonx") {
|
||||||
return <IBMLogo className="w-4 h-4" />;
|
return <IBMLogo className="w-4 h-4" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to model name analysis
|
// Fallback to model name analysis
|
||||||
if (modelValue.includes('gpt') || modelValue.includes('text-embedding')) {
|
if (modelValue.includes("gpt") || modelValue.includes("text-embedding")) {
|
||||||
return <OpenAILogo className="w-4 h-4" />;
|
return <OpenAILogo className="w-4 h-4" />;
|
||||||
} else if (modelValue.includes('llama') || modelValue.includes('ollama')) {
|
} else if (modelValue.includes("llama") || modelValue.includes("ollama")) {
|
||||||
return <OllamaLogo className="w-4 h-4" />;
|
return <OllamaLogo className="w-4 h-4" />;
|
||||||
} else if (modelValue.includes('granite') || modelValue.includes('slate') || modelValue.includes('ibm')) {
|
} else if (
|
||||||
return <IBMLogo className="w-4 h-4" />;
|
modelValue.includes("granite") ||
|
||||||
}
|
modelValue.includes("slate") ||
|
||||||
|
modelValue.includes("ibm")
|
||||||
|
) {
|
||||||
|
return <IBMLogo className="w-4 h-4" />;
|
||||||
|
}
|
||||||
|
|
||||||
return <OpenAILogo className="w-4 h-4" />; // Default to OpenAI logo
|
return <OpenAILogo className="w-4 h-4" />; // Default to OpenAI logo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get fallback models by provider
|
// Helper function to get fallback models by provider
|
||||||
export function getFallbackModels(provider: ModelProvider) {
|
export function getFallbackModels(provider: ModelProvider) {
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case 'openai':
|
case "openai":
|
||||||
return {
|
return {
|
||||||
language: [
|
language: [
|
||||||
{ value: 'gpt-4', label: 'GPT-4' },
|
{ value: "gpt-4", label: "GPT-4" },
|
||||||
{ value: 'gpt-4-turbo', label: 'GPT-4 Turbo' },
|
{ value: "gpt-4-turbo", label: "GPT-4 Turbo" },
|
||||||
{ value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo' },
|
{ value: "gpt-3.5-turbo", label: "GPT-3.5 Turbo" },
|
||||||
],
|
],
|
||||||
embedding: [
|
embedding: [
|
||||||
{ value: 'text-embedding-ada-002', label: 'text-embedding-ada-002' },
|
{ value: "text-embedding-ada-002", label: "text-embedding-ada-002" },
|
||||||
{ value: 'text-embedding-3-small', label: 'text-embedding-3-small' },
|
{ value: "text-embedding-3-small", label: "text-embedding-3-small" },
|
||||||
{ value: 'text-embedding-3-large', label: 'text-embedding-3-large' },
|
{ value: "text-embedding-3-large", label: "text-embedding-3-large" },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
case 'ollama':
|
case "ollama":
|
||||||
return {
|
return {
|
||||||
language: [
|
language: [
|
||||||
{ value: 'llama2', label: 'Llama 2' },
|
{ value: "llama2", label: "Llama 2" },
|
||||||
{ value: 'llama2:13b', label: 'Llama 2 13B' },
|
{ value: "llama2:13b", label: "Llama 2 13B" },
|
||||||
{ value: 'codellama', label: 'Code Llama' },
|
{ value: "codellama", label: "Code Llama" },
|
||||||
],
|
],
|
||||||
embedding: [
|
embedding: [
|
||||||
{ value: 'mxbai-embed-large', label: 'MxBai Embed Large' },
|
{ value: "mxbai-embed-large", label: "MxBai Embed Large" },
|
||||||
{ value: 'nomic-embed-text', label: 'Nomic Embed Text' },
|
{ value: "nomic-embed-text", label: "Nomic Embed Text" },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
case 'ibm':
|
case "watsonx":
|
||||||
return {
|
return {
|
||||||
language: [
|
language: [
|
||||||
{ value: 'meta-llama/llama-3-1-70b-instruct', label: 'Llama 3.1 70B Instruct' },
|
{
|
||||||
{ value: 'ibm/granite-13b-chat-v2', label: 'Granite 13B Chat v2' },
|
value: "meta-llama/llama-3-1-70b-instruct",
|
||||||
],
|
label: "Llama 3.1 70B Instruct",
|
||||||
embedding: [
|
},
|
||||||
{ value: 'ibm/slate-125m-english-rtrvr', label: 'Slate 125M English Retriever' },
|
{ value: "ibm/granite-13b-chat-v2", label: "Granite 13B Chat v2" },
|
||||||
],
|
],
|
||||||
};
|
embedding: [
|
||||||
default:
|
{
|
||||||
return {
|
value: "ibm/slate-125m-english-rtrvr",
|
||||||
language: [
|
label: "Slate 125M English Retriever",
|
||||||
{ value: 'gpt-4', label: 'GPT-4' },
|
},
|
||||||
{ value: 'gpt-4-turbo', label: 'GPT-4 Turbo' },
|
],
|
||||||
{ value: 'gpt-3.5-turbo', label: 'GPT-3.5 Turbo' },
|
};
|
||||||
],
|
default:
|
||||||
embedding: [
|
return {
|
||||||
{ value: 'text-embedding-ada-002', label: 'text-embedding-ada-002' },
|
language: [
|
||||||
{ value: 'text-embedding-3-small', label: 'text-embedding-3-small' },
|
{ value: "gpt-4", label: "GPT-4" },
|
||||||
{ value: 'text-embedding-3-large', label: 'text-embedding-3-large' },
|
{ value: "gpt-4-turbo", label: "GPT-4 Turbo" },
|
||||||
],
|
{ value: "gpt-3.5-turbo", label: "GPT-3.5 Turbo" },
|
||||||
};
|
],
|
||||||
}
|
embedding: [
|
||||||
}
|
{ value: "text-embedding-ada-002", label: "text-embedding-ada-002" },
|
||||||
|
{ value: "text-embedding-3-small", label: "text-embedding-3-small" },
|
||||||
|
{ value: "text-embedding-3-large", label: "text-embedding-3-large" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -57,7 +57,7 @@ export const PickerHeader = ({
|
||||||
className="bg-foreground text-background hover:bg-foreground/90 font-semibold"
|
className="bg-foreground text-background hover:bg-foreground/90 font-semibold"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
{isPickerOpen ? "Opening Picker..." : "Add Files"}
|
Add Files
|
||||||
</Button>
|
</Button>
|
||||||
<div className="text-xs text-muted-foreground pt-4">
|
<div className="text-xs text-muted-foreground pt-4">
|
||||||
csv, json, pdf,{" "}
|
csv, json, pdf,{" "}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
||||||
|
|
||||||
// Calculate active tasks for the bell icon
|
// Calculate active tasks for the bell icon
|
||||||
const activeTasks = tasks.filter(
|
const activeTasks = tasks.filter(
|
||||||
(task) =>
|
task =>
|
||||||
task.status === "pending" ||
|
task.status === "pending" ||
|
||||||
task.status === "running" ||
|
task.status === "running" ||
|
||||||
task.status === "processing"
|
task.status === "processing"
|
||||||
|
|
@ -84,16 +84,16 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
||||||
// For all other pages, render with Langflow-styled navigation and task menu
|
// For all other pages, render with Langflow-styled navigation and task menu
|
||||||
return (
|
return (
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<header className="header-arrangement bg-background sticky top-0 z-50">
|
<header className="header-arrangement bg-background sticky top-0 z-50 h-10">
|
||||||
<div className="header-start-display px-4">
|
<div className="header-start-display px-[16px]">
|
||||||
{/* Logo/Title */}
|
{/* Logo/Title */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center">
|
||||||
<Logo className="fill-primary" width={24} height={22} />
|
<Logo className="fill-primary" width={24} height={22} />
|
||||||
<span className="text-lg font-semibold">OpenRAG</span>
|
<span className="text-lg font-semibold pl-2.5">OpenRAG</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="header-end-division">
|
<div className="header-end-division">
|
||||||
<div className="header-end-display">
|
<div className="justify-end flex items-center">
|
||||||
{/* Knowledge Filter Dropdown */}
|
{/* Knowledge Filter Dropdown */}
|
||||||
{/* <KnowledgeFilterDropdown
|
{/* <KnowledgeFilterDropdown
|
||||||
selectedFilter={selectedFilter}
|
selectedFilter={selectedFilter}
|
||||||
|
|
@ -107,26 +107,24 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
||||||
{/* <DiscordLink inviteCode="EqksyE2EX9" /> */}
|
{/* <DiscordLink inviteCode="EqksyE2EX9" /> */}
|
||||||
|
|
||||||
{/* Task Notification Bell */}
|
{/* Task Notification Bell */}
|
||||||
<Button
|
<button
|
||||||
variant="ghost"
|
|
||||||
size="iconSm"
|
|
||||||
onClick={toggleMenu}
|
onClick={toggleMenu}
|
||||||
className="relative"
|
className="h-8 w-8 hover:bg-muted rounded-lg flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<Bell className="h-4 w-4 text-muted-foreground" />
|
<Bell size={16} className="text-muted-foreground" />
|
||||||
{activeTasks.length > 0 && (
|
{activeTasks.length > 0 && (
|
||||||
<div className="header-notifications" />
|
<div className="header-notifications" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</button>
|
||||||
|
|
||||||
{/* Separator */}
|
{/* Separator */}
|
||||||
<div className="w-px h-6 bg-border" />
|
<div className="w-px h-6 bg-border mx-3" />
|
||||||
|
|
||||||
<UserNav />
|
<UserNav />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="side-bar-arrangement bg-background fixed left-0 top-[53px] bottom-0 md:flex hidden">
|
<div className="side-bar-arrangement bg-background fixed left-0 top-[40px] bottom-0 md:flex hidden pt-1">
|
||||||
<Navigation
|
<Navigation
|
||||||
conversations={conversations}
|
conversations={conversations}
|
||||||
isConversationsLoading={isConversationsLoading}
|
isConversationsLoading={isConversationsLoading}
|
||||||
|
|
|
||||||
68
frontend/src/components/ui/buttonTheme.tsx
Normal file
68
frontend/src/components/ui/buttonTheme.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Monitor, Moon, Sun } from "lucide-react";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
|
export const ThemeButtons = () => {
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
|
const [selectedTheme, setSelectedTheme] = useState("dark");
|
||||||
|
|
||||||
|
// Sync local state with theme context
|
||||||
|
useEffect(() => {
|
||||||
|
if (theme) {
|
||||||
|
setSelectedTheme(theme);
|
||||||
|
}
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
const handleThemeChange = (newTheme: string) => {
|
||||||
|
setSelectedTheme(newTheme);
|
||||||
|
setTheme(newTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center border border-border rounded-full">
|
||||||
|
{/* Light Theme Button */}
|
||||||
|
<button
|
||||||
|
className={`h-6 w-6 rounded-full flex items-center justify-center ${
|
||||||
|
selectedTheme === "light"
|
||||||
|
? "bg-amber-400 text-primary"
|
||||||
|
: "text-foreground hover:bg-amber-400 hover:text-background"
|
||||||
|
}`}
|
||||||
|
onClick={() => handleThemeChange("light")}
|
||||||
|
data-testid="menu_light_button"
|
||||||
|
id="menu_light_button"
|
||||||
|
>
|
||||||
|
<Sun className="h-4 w-4 rounded-full" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Dark Theme Button */}
|
||||||
|
<button
|
||||||
|
className={`h-6 w-6 rounded-full flex items-center justify-center ${
|
||||||
|
selectedTheme === "dark"
|
||||||
|
? "bg-purple-500/20 text-purple-500 hover:bg-purple-500/20 hover:text-purple-500"
|
||||||
|
: "text-foreground hover:bg-purple-500/20 hover:text-purple-500"
|
||||||
|
}`}
|
||||||
|
onClick={() => handleThemeChange("dark")}
|
||||||
|
data-testid="menu_dark_button"
|
||||||
|
id="menu_dark_button"
|
||||||
|
>
|
||||||
|
<Moon className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* System Theme Button */}
|
||||||
|
<button
|
||||||
|
className={`h-6 w-6 rounded-full flex items-center justify-center ${
|
||||||
|
selectedTheme === "system"
|
||||||
|
? "bg-foreground text-background"
|
||||||
|
: "hover:bg-foreground hover:text-background"
|
||||||
|
}`}
|
||||||
|
onClick={() => handleThemeChange("system")}
|
||||||
|
data-testid="menu_system_button"
|
||||||
|
id="menu_system_button"
|
||||||
|
>
|
||||||
|
<Monitor className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeButtons;
|
||||||
|
|
@ -1,94 +1,100 @@
|
||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useAuth } from "@/contexts/auth-context"
|
import { useAuth } from "@/contexts/auth-context";
|
||||||
import { LogIn, LogOut, User, Moon, Sun, ChevronsUpDown } from "lucide-react"
|
import { LogOut, User, Moon, Sun, ChevronsUpDown } from "lucide-react";
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes";
|
||||||
|
import ThemeButtons from "./ui/buttonTheme";
|
||||||
|
|
||||||
export function UserNav() {
|
export function UserNav() {
|
||||||
const { user, isLoading, isAuthenticated, isNoAuthMode, login, logout } = useAuth()
|
const { user, isLoading, isAuthenticated, isNoAuthMode, login, logout } =
|
||||||
const { theme, setTheme } = useTheme()
|
useAuth();
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return <div className="h-8 w-8 rounded-full bg-muted animate-pulse" />;
|
||||||
<div className="h-8 w-8 rounded-full bg-muted animate-pulse" />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// In no-auth mode, show a simple theme switcher instead of auth UI
|
// In no-auth mode, show a simple theme switcher instead of auth UI
|
||||||
if (isNoAuthMode) {
|
if (isNoAuthMode) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<button
|
||||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||||
variant="outline"
|
className="flex justify-center items-center gap-2 h-8 w-8 mr-2 rounded-md hover:bg-muted rounded-lg "
|
||||||
size="sm"
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
>
|
>
|
||||||
{theme === 'dark' ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
{theme === "dark" ? (
|
||||||
</Button>
|
<Sun size={16} className="text-muted-foreground" />
|
||||||
)
|
) : (
|
||||||
|
<Moon size={16} className="text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<button
|
||||||
onClick={login}
|
onClick={login}
|
||||||
variant="outline"
|
className="flex items-center gap-2 h-7 px-3 mr-2 rounded-md bg-primary hover:bg-primary/90 text-primary-foreground text-[13px] line-height-[16px]"
|
||||||
size="sm"
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
>
|
>
|
||||||
<LogIn className="h-4 w-4" />
|
|
||||||
Sign In
|
Sign In
|
||||||
</Button>
|
</button>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" className="flex items-center gap-1 h-8 px-1 rounded-full">
|
<button className="hover:bg-accent rounded-lg pl-[4px] p-[3px] flex items-center justify-center">
|
||||||
<Avatar className="h-6 w-6">
|
<Avatar className="rounded-md w-7 h-7">
|
||||||
<AvatarImage src={user?.picture} alt={user?.name} />
|
<AvatarImage
|
||||||
<AvatarFallback className="text-xs">
|
width={16}
|
||||||
{user?.name ? user.name.charAt(0).toUpperCase() : <User className="h-3 w-3" />}
|
height={16}
|
||||||
|
src={user?.picture}
|
||||||
|
alt={user?.name}
|
||||||
|
className="rounded-md"
|
||||||
|
/>
|
||||||
|
<AvatarFallback className="text-xs rounded-md">
|
||||||
|
{user?.name ? (
|
||||||
|
user.name.charAt(0).toUpperCase()
|
||||||
|
) : (
|
||||||
|
<User className="h-3 w-3" />
|
||||||
|
)}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<ChevronsUpDown className="h-3 w-3 text-muted-foreground" />
|
<ChevronsUpDown size={16} className="text-muted-foreground mx-2" />
|
||||||
</Button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="w-56" align="end" forceMount>
|
<DropdownMenuContent className="w-56 p-0" align="end" forceMount>
|
||||||
<DropdownMenuLabel className="font-normal">
|
<DropdownMenuLabel className="font-normal">
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-2 px-1 py-1">
|
||||||
<p className="text-sm font-medium leading-none">{user?.name}</p>
|
<p className="text-sm font-medium leading-none">{user?.name}</p>
|
||||||
<p className="text-xs leading-none text-muted-foreground">
|
<p className="text-xs leading-none text-muted-foreground">
|
||||||
{user?.email}
|
{user?.email}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator className="m-0" />
|
||||||
<DropdownMenuItem onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
|
<div className="flex items-center justify-between pl-3 pr-2 h-9">
|
||||||
{theme === "light" ? (
|
<span className="text-sm">Theme</span>
|
||||||
<Moon className="mr-2 h-4 w-4" />
|
<ThemeButtons />
|
||||||
) : (
|
</div>
|
||||||
<Sun className="mr-2 h-4 w-4" />
|
<DropdownMenuSeparator className="m-0" />
|
||||||
)}
|
<button
|
||||||
<span>Toggle Theme</span>
|
onClick={logout}
|
||||||
</DropdownMenuItem>
|
className="flex items-center hover:bg-muted w-full h-9 px-3"
|
||||||
<DropdownMenuSeparator />
|
>
|
||||||
<DropdownMenuItem onClick={logout} className="text-red-600 focus:text-red-600">
|
<LogOut className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||||
<LogOut className="mr-2 h-4 w-4" />
|
<span className="text-sm">Logout</span>
|
||||||
<span>Log out</span>
|
</button>
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
||||||
const refreshConversations = useCallback((force = false) => {
|
const refreshConversations = useCallback((force = false) => {
|
||||||
if (force) {
|
if (force) {
|
||||||
// Immediate refresh for important updates like new conversations
|
// Immediate refresh for important updates like new conversations
|
||||||
setRefreshTrigger((prev) => prev + 1);
|
setRefreshTrigger(prev => prev + 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
||||||
|
|
||||||
// Set a new timeout to debounce multiple rapid refresh calls
|
// Set a new timeout to debounce multiple rapid refresh calls
|
||||||
refreshTimeoutRef.current = setTimeout(() => {
|
refreshTimeoutRef.current = setTimeout(() => {
|
||||||
setRefreshTrigger((prev) => prev + 1);
|
setRefreshTrigger(prev => prev + 1);
|
||||||
}, 250); // 250ms debounce
|
}, 250); // 250ms debounce
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -123,7 +123,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
||||||
// Silent refresh - updates data without loading states
|
// Silent refresh - updates data without loading states
|
||||||
const refreshConversationsSilent = useCallback(async () => {
|
const refreshConversationsSilent = useCallback(async () => {
|
||||||
// Trigger silent refresh that updates conversation data without showing loading states
|
// Trigger silent refresh that updates conversation data without showing loading states
|
||||||
setRefreshTriggerSilent((prev) => prev + 1);
|
setRefreshTriggerSilent(prev => prev + 1);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadConversation = useCallback((conversation: ConversationData) => {
|
const loadConversation = useCallback((conversation: ConversationData) => {
|
||||||
|
|
@ -164,7 +164,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
||||||
}, [endpoint, refreshConversations]);
|
}, [endpoint, refreshConversations]);
|
||||||
|
|
||||||
const addConversationDoc = useCallback((filename: string) => {
|
const addConversationDoc = useCallback((filename: string) => {
|
||||||
setConversationDocs((prev) => [
|
setConversationDocs(prev => [
|
||||||
...prev,
|
...prev,
|
||||||
{ filename, uploadTime: new Date() },
|
{ filename, uploadTime: new Date() },
|
||||||
]);
|
]);
|
||||||
|
|
@ -180,7 +180,7 @@ export function ChatProvider({ children }: ChatProviderProps) {
|
||||||
setCurrentConversationId(null); // Clear current conversation to indicate new conversation
|
setCurrentConversationId(null); // Clear current conversation to indicate new conversation
|
||||||
setConversationData(null); // Clear conversation data to prevent reloading
|
setConversationData(null); // Clear conversation data to prevent reloading
|
||||||
// Set the response ID that we're forking from as the previous response ID
|
// Set the response ID that we're forking from as the previous response ID
|
||||||
setPreviousResponseIds((prev) => ({
|
setPreviousResponseIds(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[endpoint]: responseId,
|
[endpoint]: responseId,
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -70,22 +70,22 @@ async def run_ingestion(
|
||||||
if settings:
|
if settings:
|
||||||
logger.debug("Applying ingestion settings", settings=settings)
|
logger.debug("Applying ingestion settings", settings=settings)
|
||||||
|
|
||||||
# Split Text component tweaks (SplitText-QIKhg)
|
# Split Text component tweaks (SplitText-PC36h)
|
||||||
if (
|
if (
|
||||||
settings.get("chunkSize")
|
settings.get("chunkSize")
|
||||||
or settings.get("chunkOverlap")
|
or settings.get("chunkOverlap")
|
||||||
or settings.get("separator")
|
or settings.get("separator")
|
||||||
):
|
):
|
||||||
if "SplitText-QIKhg" not in tweaks:
|
if "SplitText-PC36h" not in tweaks:
|
||||||
tweaks["SplitText-QIKhg"] = {}
|
tweaks["SplitText-PC36h"] = {}
|
||||||
if settings.get("chunkSize"):
|
if settings.get("chunkSize"):
|
||||||
tweaks["SplitText-QIKhg"]["chunk_size"] = settings["chunkSize"]
|
tweaks["SplitText-PC36h"]["chunk_size"] = settings["chunkSize"]
|
||||||
if settings.get("chunkOverlap"):
|
if settings.get("chunkOverlap"):
|
||||||
tweaks["SplitText-QIKhg"]["chunk_overlap"] = settings[
|
tweaks["SplitText-PC36h"]["chunk_overlap"] = settings[
|
||||||
"chunkOverlap"
|
"chunkOverlap"
|
||||||
]
|
]
|
||||||
if settings.get("separator"):
|
if settings.get("separator"):
|
||||||
tweaks["SplitText-QIKhg"]["separator"] = settings["separator"]
|
tweaks["SplitText-PC36h"]["separator"] = settings["separator"]
|
||||||
|
|
||||||
# OpenAI Embeddings component tweaks (OpenAIEmbeddings-joRJ6)
|
# OpenAI Embeddings component tweaks (OpenAIEmbeddings-joRJ6)
|
||||||
if settings.get("embeddingModel"):
|
if settings.get("embeddingModel"):
|
||||||
|
|
@ -275,4 +275,4 @@ async def delete_user_files(
|
||||||
status_code=status,
|
status_code=status,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JSONResponse({"error": str(e)}, status_code=500)
|
return JSONResponse({"error": str(e)}, status_code=500)
|
||||||
|
|
@ -140,6 +140,14 @@ async def langflow_upload_ingest_task(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create langflow upload task
|
# Create langflow upload task
|
||||||
|
print(f"tweaks: {tweaks}")
|
||||||
|
print(f"settings: {settings}")
|
||||||
|
print(f"jwt_token: {jwt_token}")
|
||||||
|
print(f"user_name: {user_name}")
|
||||||
|
print(f"user_email: {user_email}")
|
||||||
|
print(f"session_id: {session_id}")
|
||||||
|
print(f"delete_after_ingest: {delete_after_ingest}")
|
||||||
|
print(f"temp_file_paths: {temp_file_paths}")
|
||||||
task_id = await task_service.create_langflow_upload_task(
|
task_id = await task_service.create_langflow_upload_task(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
file_paths=temp_file_paths,
|
file_paths=temp_file_paths,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ from config.settings import (
|
||||||
LANGFLOW_CHAT_FLOW_ID,
|
LANGFLOW_CHAT_FLOW_ID,
|
||||||
LANGFLOW_INGEST_FLOW_ID,
|
LANGFLOW_INGEST_FLOW_ID,
|
||||||
LANGFLOW_PUBLIC_URL,
|
LANGFLOW_PUBLIC_URL,
|
||||||
DOCLING_COMPONENT_ID,
|
|
||||||
LOCALHOST_URL,
|
LOCALHOST_URL,
|
||||||
clients,
|
clients,
|
||||||
get_openrag_config,
|
get_openrag_config,
|
||||||
|
|
@ -206,7 +205,7 @@ async def update_settings(request, session_manager):
|
||||||
# Also update the chat flow with the new model
|
# Also update the chat flow with the new model
|
||||||
try:
|
try:
|
||||||
flows_service = _get_flows_service()
|
flows_service = _get_flows_service()
|
||||||
await flows_service.update_chat_flow_model(body["llm_model"])
|
await flows_service.update_chat_flow_model(body["llm_model"], current_config.provider.model_provider.lower())
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Successfully updated chat flow model to '{body['llm_model']}'"
|
f"Successfully updated chat flow model to '{body['llm_model']}'"
|
||||||
)
|
)
|
||||||
|
|
@ -223,7 +222,8 @@ async def update_settings(request, session_manager):
|
||||||
try:
|
try:
|
||||||
flows_service = _get_flows_service()
|
flows_service = _get_flows_service()
|
||||||
await flows_service.update_chat_flow_system_prompt(
|
await flows_service.update_chat_flow_system_prompt(
|
||||||
body["system_prompt"]
|
body["system_prompt"],
|
||||||
|
current_config.provider.model_provider.lower()
|
||||||
)
|
)
|
||||||
logger.info(f"Successfully updated chat flow system prompt")
|
logger.info(f"Successfully updated chat flow system prompt")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -248,7 +248,8 @@ async def update_settings(request, session_manager):
|
||||||
try:
|
try:
|
||||||
flows_service = _get_flows_service()
|
flows_service = _get_flows_service()
|
||||||
await flows_service.update_ingest_flow_embedding_model(
|
await flows_service.update_ingest_flow_embedding_model(
|
||||||
body["embedding_model"].strip()
|
body["embedding_model"].strip(),
|
||||||
|
current_config.provider.model_provider.lower()
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Successfully updated ingest flow embedding model to '{body['embedding_model'].strip()}'"
|
f"Successfully updated ingest flow embedding model to '{body['embedding_model'].strip()}'"
|
||||||
|
|
|
||||||
|
|
@ -543,38 +543,28 @@ OLLAMA_EMBEDDING_COMPONENT_PATH = os.getenv(
|
||||||
|
|
||||||
# Component IDs in flows
|
# Component IDs in flows
|
||||||
|
|
||||||
OPENAI_EMBEDDING_COMPONENT_ID = os.getenv(
|
OPENAI_EMBEDDING_COMPONENT_DISPLAY_NAME = os.getenv(
|
||||||
"OPENAI_EMBEDDING_COMPONENT_ID", "EmbeddingModel-eZ6bT"
|
"OPENAI_EMBEDDING_COMPONENT_DISPLAY_NAME", "Embedding Model"
|
||||||
)
|
)
|
||||||
OPENAI_LLM_COMPONENT_ID = os.getenv(
|
OPENAI_LLM_COMPONENT_DISPLAY_NAME = os.getenv(
|
||||||
"OPENAI_LLM_COMPONENT_ID", "LanguageModelComponent-0YME7"
|
"OPENAI_LLM_COMPONENT_DISPLAY_NAME", "Language Model"
|
||||||
)
|
|
||||||
OPENAI_LLM_TEXT_COMPONENT_ID = os.getenv(
|
|
||||||
"OPENAI_LLM_TEXT_COMPONENT_ID", "LanguageModelComponent-NSTA6"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Provider-specific component IDs
|
# Provider-specific component IDs
|
||||||
WATSONX_EMBEDDING_COMPONENT_ID = os.getenv(
|
WATSONX_EMBEDDING_COMPONENT_DISPLAY_NAME = os.getenv(
|
||||||
"WATSONX_EMBEDDING_COMPONENT_ID", "WatsonxEmbeddingsComponent-pJfXI"
|
"WATSONX_EMBEDDING_COMPONENT_DISPLAY_NAME", "IBM watsonx.ai Embeddings"
|
||||||
)
|
)
|
||||||
WATSONX_LLM_COMPONENT_ID = os.getenv(
|
WATSONX_LLM_COMPONENT_DISPLAY_NAME = os.getenv(
|
||||||
"WATSONX_LLM_COMPONENT_ID", "IBMwatsonxModel-jA4Nw"
|
"WATSONX_LLM_COMPONENT_DISPLAY_NAME", "IBM watsonx.ai"
|
||||||
)
|
|
||||||
WATSONX_LLM_TEXT_COMPONENT_ID = os.getenv(
|
|
||||||
"WATSONX_LLM_TEXT_COMPONENT_ID", "IBMwatsonxModel-18kmA"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OLLAMA_EMBEDDING_COMPONENT_DISPLAY_NAME = os.getenv(
|
||||||
OLLAMA_EMBEDDING_COMPONENT_ID = os.getenv(
|
"OLLAMA_EMBEDDING_COMPONENT_DISPLAY_NAME", "Ollama Model"
|
||||||
"OLLAMA_EMBEDDING_COMPONENT_ID", "OllamaEmbeddings-4ah5Q"
|
|
||||||
)
|
|
||||||
OLLAMA_LLM_COMPONENT_ID = os.getenv("OLLAMA_LLM_COMPONENT_ID", "OllamaModel-eCsJx")
|
|
||||||
OLLAMA_LLM_TEXT_COMPONENT_ID = os.getenv(
|
|
||||||
"OLLAMA_LLM_TEXT_COMPONENT_ID", "OllamaModel-XDGqZ"
|
|
||||||
)
|
)
|
||||||
|
OLLAMA_LLM_COMPONENT_DISPLAY_NAME = os.getenv("OLLAMA_LLM_COMPONENT_DISPLAY_NAME", "Ollama")
|
||||||
|
|
||||||
# Docling component ID for ingest flow
|
# Docling component ID for ingest flow
|
||||||
DOCLING_COMPONENT_ID = os.getenv("DOCLING_COMPONENT_ID", "DoclingRemote-78KoX")
|
DOCLING_COMPONENT_DISPLAY_NAME = os.getenv("DOCLING_COMPONENT_DISPLAY_NAME", "Docling Serve")
|
||||||
|
|
||||||
LOCALHOST_URL = get_container_host() or "localhost"
|
LOCALHOST_URL = get_container_host() or "localhost"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -382,6 +382,10 @@ async def _ingest_default_documents_langflow(services, file_paths):
|
||||||
settings=None, # Use default ingestion settings
|
settings=None, # Use default ingestion settings
|
||||||
jwt_token=effective_jwt, # Use JWT token (anonymous if needed)
|
jwt_token=effective_jwt, # Use JWT token (anonymous if needed)
|
||||||
delete_after_ingest=True, # Clean up after ingestion
|
delete_after_ingest=True, # Clean up after ingestion
|
||||||
|
owner=None,
|
||||||
|
owner_name=anonymous_user.name,
|
||||||
|
owner_email=anonymous_user.email,
|
||||||
|
connector_type="system_default",
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
@ -1189,4 +1193,4 @@ if __name__ == "__main__":
|
||||||
host="0.0.0.0",
|
host="0.0.0.0",
|
||||||
port=8000,
|
port=8000,
|
||||||
reload=False, # Disable reload since we're running from main
|
reload=False, # Disable reload since we're running from main
|
||||||
)
|
)
|
||||||
|
|
@ -625,7 +625,12 @@ class LangflowFileProcessor(TaskProcessor):
|
||||||
tweaks=final_tweaks,
|
tweaks=final_tweaks,
|
||||||
settings=self.settings,
|
settings=self.settings,
|
||||||
jwt_token=effective_jwt,
|
jwt_token=effective_jwt,
|
||||||
delete_after_ingest=self.delete_after_ingest
|
delete_after_ingest=self.delete_after_ingest,
|
||||||
|
owner=self.owner_user_id,
|
||||||
|
owner_name=self.owner_name,
|
||||||
|
owner_email=self.owner_email,
|
||||||
|
connector_type="local",
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update task with success
|
# Update task with success
|
||||||
|
|
@ -640,4 +645,4 @@ class LangflowFileProcessor(TaskProcessor):
|
||||||
file_task.error_message = str(e)
|
file_task.error_message = str(e)
|
||||||
file_task.updated_at = time.time()
|
file_task.updated_at = time.time()
|
||||||
upload_task.failed_files += 1
|
upload_task.failed_files += 1
|
||||||
raise
|
raise
|
||||||
|
|
@ -110,15 +110,14 @@ class ChatService:
|
||||||
filter_expression["score_threshold"] = score_threshold
|
filter_expression["score_threshold"] = score_threshold
|
||||||
|
|
||||||
# Pass the complete filter expression as a single header to Langflow (only if we have something to send)
|
# Pass the complete filter expression as a single header to Langflow (only if we have something to send)
|
||||||
if filter_expression:
|
logger.info(
|
||||||
logger.info(
|
"Sending OpenRAG query filter to Langflow",
|
||||||
"Sending OpenRAG query filter to Langflow",
|
filter_expression=filter_expression,
|
||||||
filter_expression=filter_expression,
|
)
|
||||||
)
|
extra_headers["X-LANGFLOW-GLOBAL-VAR-OPENRAG-QUERY-FILTER"] = json.dumps(
|
||||||
extra_headers["X-LANGFLOW-GLOBAL-VAR-OPENRAG-QUERY-FILTER"] = json.dumps(
|
filter_expression
|
||||||
filter_expression
|
)
|
||||||
)
|
logger.info(f"[LF] Extra headers {extra_headers}")
|
||||||
|
|
||||||
# Ensure the Langflow client exists; try lazy init if needed
|
# Ensure the Langflow client exists; try lazy init if needed
|
||||||
langflow_client = await clients.ensure_langflow_client()
|
langflow_client = await clients.ensure_langflow_client()
|
||||||
if not langflow_client:
|
if not langflow_client:
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,22 @@
|
||||||
import asyncio
|
|
||||||
from config.settings import (
|
from config.settings import (
|
||||||
DISABLE_INGEST_WITH_LANGFLOW,
|
DISABLE_INGEST_WITH_LANGFLOW,
|
||||||
NUDGES_FLOW_ID,
|
NUDGES_FLOW_ID,
|
||||||
LANGFLOW_URL,
|
LANGFLOW_URL,
|
||||||
LANGFLOW_CHAT_FLOW_ID,
|
LANGFLOW_CHAT_FLOW_ID,
|
||||||
LANGFLOW_INGEST_FLOW_ID,
|
LANGFLOW_INGEST_FLOW_ID,
|
||||||
OLLAMA_LLM_TEXT_COMPONENT_ID,
|
|
||||||
OLLAMA_LLM_TEXT_COMPONENT_PATH,
|
OLLAMA_LLM_TEXT_COMPONENT_PATH,
|
||||||
OPENAI_EMBEDDING_COMPONENT_ID,
|
OPENAI_EMBEDDING_COMPONENT_DISPLAY_NAME,
|
||||||
OPENAI_LLM_COMPONENT_ID,
|
OPENAI_LLM_COMPONENT_DISPLAY_NAME,
|
||||||
OPENAI_LLM_TEXT_COMPONENT_ID,
|
|
||||||
WATSONX_LLM_TEXT_COMPONENT_ID,
|
|
||||||
WATSONX_LLM_TEXT_COMPONENT_PATH,
|
WATSONX_LLM_TEXT_COMPONENT_PATH,
|
||||||
clients,
|
clients,
|
||||||
WATSONX_LLM_COMPONENT_PATH,
|
WATSONX_LLM_COMPONENT_PATH,
|
||||||
WATSONX_EMBEDDING_COMPONENT_PATH,
|
WATSONX_EMBEDDING_COMPONENT_PATH,
|
||||||
OLLAMA_LLM_COMPONENT_PATH,
|
OLLAMA_LLM_COMPONENT_PATH,
|
||||||
OLLAMA_EMBEDDING_COMPONENT_PATH,
|
OLLAMA_EMBEDDING_COMPONENT_PATH,
|
||||||
WATSONX_EMBEDDING_COMPONENT_ID,
|
WATSONX_EMBEDDING_COMPONENT_DISPLAY_NAME,
|
||||||
WATSONX_LLM_COMPONENT_ID,
|
WATSONX_LLM_COMPONENT_DISPLAY_NAME,
|
||||||
OLLAMA_EMBEDDING_COMPONENT_ID,
|
OLLAMA_EMBEDDING_COMPONENT_DISPLAY_NAME,
|
||||||
OLLAMA_LLM_COMPONENT_ID,
|
OLLAMA_LLM_COMPONENT_DISPLAY_NAME,
|
||||||
get_openrag_config,
|
get_openrag_config,
|
||||||
)
|
)
|
||||||
import json
|
import json
|
||||||
|
|
@ -277,23 +273,23 @@ class FlowsService:
|
||||||
{
|
{
|
||||||
"name": "nudges",
|
"name": "nudges",
|
||||||
"flow_id": NUDGES_FLOW_ID,
|
"flow_id": NUDGES_FLOW_ID,
|
||||||
"embedding_id": OPENAI_EMBEDDING_COMPONENT_ID,
|
"embedding_name": OPENAI_EMBEDDING_COMPONENT_DISPLAY_NAME,
|
||||||
"llm_id": OPENAI_LLM_COMPONENT_ID,
|
"llm_text_name": OPENAI_LLM_COMPONENT_DISPLAY_NAME,
|
||||||
"llm_text_id": OPENAI_LLM_TEXT_COMPONENT_ID,
|
"llm_name": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "retrieval",
|
"name": "retrieval",
|
||||||
"flow_id": LANGFLOW_CHAT_FLOW_ID,
|
"flow_id": LANGFLOW_CHAT_FLOW_ID,
|
||||||
"embedding_id": OPENAI_EMBEDDING_COMPONENT_ID,
|
"embedding_name": OPENAI_EMBEDDING_COMPONENT_DISPLAY_NAME,
|
||||||
"llm_id": OPENAI_LLM_COMPONENT_ID,
|
"llm_name": OPENAI_LLM_COMPONENT_DISPLAY_NAME,
|
||||||
"llm_text_id": None,
|
"llm_text_name": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ingest",
|
"name": "ingest",
|
||||||
"flow_id": LANGFLOW_INGEST_FLOW_ID,
|
"flow_id": LANGFLOW_INGEST_FLOW_ID,
|
||||||
"embedding_id": OPENAI_EMBEDDING_COMPONENT_ID,
|
"embedding_name": OPENAI_EMBEDDING_COMPONENT_DISPLAY_NAME,
|
||||||
"llm_id": None, # Ingestion flow might not have LLM
|
"llm_name": None, # Ingestion flow might not have LLM
|
||||||
"llm_text_id": None, # Ingestion flow might not have LLM Text
|
"llm_text_name": None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -389,9 +385,9 @@ class FlowsService:
|
||||||
"""Update components in a specific flow"""
|
"""Update components in a specific flow"""
|
||||||
flow_name = config["name"]
|
flow_name = config["name"]
|
||||||
flow_id = config["flow_id"]
|
flow_id = config["flow_id"]
|
||||||
old_embedding_id = config["embedding_id"]
|
old_embedding_name = config["embedding_name"]
|
||||||
old_llm_id = config["llm_id"]
|
old_llm_name = config["llm_name"]
|
||||||
old_llm_text_id = config["llm_text_id"]
|
old_llm_text_name = config["llm_text_name"]
|
||||||
# Extract IDs from templates
|
# Extract IDs from templates
|
||||||
new_llm_id = llm_template["data"]["id"]
|
new_llm_id = llm_template["data"]["id"]
|
||||||
new_embedding_id = embedding_template["data"]["id"]
|
new_embedding_id = embedding_template["data"]["id"]
|
||||||
|
|
@ -411,7 +407,7 @@ class FlowsService:
|
||||||
|
|
||||||
# Replace embedding component
|
# Replace embedding component
|
||||||
if not DISABLE_INGEST_WITH_LANGFLOW:
|
if not DISABLE_INGEST_WITH_LANGFLOW:
|
||||||
embedding_node = self._find_node_by_id(flow_data, old_embedding_id)
|
embedding_node, _ = self._find_node_in_flow(flow_data, display_name=old_embedding_name)
|
||||||
if embedding_node:
|
if embedding_node:
|
||||||
# Preserve position
|
# Preserve position
|
||||||
original_position = embedding_node.get("position", {})
|
original_position = embedding_node.get("position", {})
|
||||||
|
|
@ -421,16 +417,14 @@ class FlowsService:
|
||||||
new_embedding_node["position"] = original_position
|
new_embedding_node["position"] = original_position
|
||||||
|
|
||||||
# Replace in flow
|
# Replace in flow
|
||||||
self._replace_node_in_flow(
|
self._replace_node_in_flow(flow_data, old_embedding_name, new_embedding_node)
|
||||||
flow_data, old_embedding_id, new_embedding_node
|
|
||||||
)
|
|
||||||
components_updated.append(
|
components_updated.append(
|
||||||
f"embedding: {old_embedding_id} -> {new_embedding_id}"
|
f"embedding: {old_embedding_name} -> {new_embedding_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Replace LLM component (if exists in this flow)
|
# Replace LLM component (if exists in this flow)
|
||||||
if old_llm_id:
|
if old_llm_name:
|
||||||
llm_node = self._find_node_by_id(flow_data, old_llm_id)
|
llm_node, _ = self._find_node_in_flow(flow_data, display_name=old_llm_name)
|
||||||
if llm_node:
|
if llm_node:
|
||||||
# Preserve position
|
# Preserve position
|
||||||
original_position = llm_node.get("position", {})
|
original_position = llm_node.get("position", {})
|
||||||
|
|
@ -440,12 +434,12 @@ class FlowsService:
|
||||||
new_llm_node["position"] = original_position
|
new_llm_node["position"] = original_position
|
||||||
|
|
||||||
# Replace in flow
|
# Replace in flow
|
||||||
self._replace_node_in_flow(flow_data, old_llm_id, new_llm_node)
|
self._replace_node_in_flow(flow_data, old_llm_name, new_llm_node)
|
||||||
components_updated.append(f"llm: {old_llm_id} -> {new_llm_id}")
|
components_updated.append(f"llm: {old_llm_name} -> {new_llm_id}")
|
||||||
|
|
||||||
# Replace LLM component (if exists in this flow)
|
# Replace LLM component (if exists in this flow)
|
||||||
if old_llm_text_id:
|
if old_llm_text_name:
|
||||||
llm_text_node = self._find_node_by_id(flow_data, old_llm_text_id)
|
llm_text_node, _ = self._find_node_in_flow(flow_data, display_name=old_llm_text_name)
|
||||||
if llm_text_node:
|
if llm_text_node:
|
||||||
# Preserve position
|
# Preserve position
|
||||||
original_position = llm_text_node.get("position", {})
|
original_position = llm_text_node.get("position", {})
|
||||||
|
|
@ -455,12 +449,18 @@ class FlowsService:
|
||||||
new_llm_text_node["position"] = original_position
|
new_llm_text_node["position"] = original_position
|
||||||
|
|
||||||
# Replace in flow
|
# Replace in flow
|
||||||
self._replace_node_in_flow(
|
self._replace_node_in_flow(flow_data, old_llm_text_name, new_llm_text_node)
|
||||||
flow_data, old_llm_text_id, new_llm_text_node
|
components_updated.append(f"llm: {old_llm_text_name} -> {new_llm_text_id}")
|
||||||
)
|
|
||||||
components_updated.append(
|
old_embedding_id = None
|
||||||
f"llm: {old_llm_text_id} -> {new_llm_text_id}"
|
old_llm_id = None
|
||||||
)
|
old_llm_text_id = None
|
||||||
|
if embedding_node:
|
||||||
|
old_embedding_id = embedding_node.get("data", {}).get("id")
|
||||||
|
if old_llm_name and llm_node:
|
||||||
|
old_llm_id = llm_node.get("data", {}).get("id")
|
||||||
|
if old_llm_text_name and llm_text_node:
|
||||||
|
old_llm_text_id = llm_text_node.get("data", {}).get("id")
|
||||||
|
|
||||||
# Update all edge references using regex replacement
|
# Update all edge references using regex replacement
|
||||||
flow_json_str = json.dumps(flow_data)
|
flow_json_str = json.dumps(flow_data)
|
||||||
|
|
@ -478,17 +478,27 @@ class FlowsService:
|
||||||
|
|
||||||
# Replace LLM ID references (if applicable)
|
# Replace LLM ID references (if applicable)
|
||||||
if old_llm_id:
|
if old_llm_id:
|
||||||
flow_json_str = re.sub(re.escape(old_llm_id), new_llm_id, flow_json_str)
|
flow_json_str = re.sub(
|
||||||
if old_llm_text_id:
|
re.escape(old_llm_id), new_llm_id, flow_json_str
|
||||||
flow_json_str = re.sub(
|
)
|
||||||
re.escape(old_llm_text_id), new_llm_text_id, flow_json_str
|
|
||||||
)
|
|
||||||
|
|
||||||
flow_json_str = re.sub(
|
flow_json_str = re.sub(
|
||||||
re.escape(old_llm_id.split("-")[0]),
|
re.escape(old_llm_id.split("-")[0]),
|
||||||
new_llm_id.split("-")[0],
|
new_llm_id.split("-")[0],
|
||||||
flow_json_str,
|
flow_json_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Replace text LLM ID references (if applicable)
|
||||||
|
if old_llm_text_id:
|
||||||
|
flow_json_str = re.sub(
|
||||||
|
re.escape(old_llm_text_id), new_llm_text_id, flow_json_str
|
||||||
|
)
|
||||||
|
|
||||||
|
flow_json_str = re.sub(
|
||||||
|
re.escape(old_llm_text_id.split("-")[0]),
|
||||||
|
new_llm_text_id.split("-")[0],
|
||||||
|
flow_json_str,
|
||||||
|
)
|
||||||
|
|
||||||
# Convert back to JSON
|
# Convert back to JSON
|
||||||
flow_data = json.loads(flow_json_str)
|
flow_data = json.loads(flow_json_str)
|
||||||
|
|
@ -510,14 +520,6 @@ class FlowsService:
|
||||||
"flow_id": flow_id,
|
"flow_id": flow_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _find_node_by_id(self, flow_data, node_id):
|
|
||||||
"""Find a node by ID in the flow data"""
|
|
||||||
nodes = flow_data.get("data", {}).get("nodes", [])
|
|
||||||
for node in nodes:
|
|
||||||
if node.get("id") == node_id:
|
|
||||||
return node
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _find_node_in_flow(self, flow_data, node_id=None, display_name=None):
|
def _find_node_in_flow(self, flow_data, node_id=None, display_name=None):
|
||||||
"""
|
"""
|
||||||
Helper function to find a node in flow data by ID or display name.
|
Helper function to find a node in flow data by ID or display name.
|
||||||
|
|
@ -539,14 +541,7 @@ class FlowsService:
|
||||||
|
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
async def _update_flow_field(
|
async def _update_flow_field(self, flow_id: str, field_name: str, field_value: str, node_display_name: str = None):
|
||||||
self,
|
|
||||||
flow_id: str,
|
|
||||||
field_name: str,
|
|
||||||
field_value: str,
|
|
||||||
node_display_name: str = None,
|
|
||||||
node_id: str = None,
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Generic helper function to update any field in any Langflow component.
|
Generic helper function to update any field in any Langflow component.
|
||||||
|
|
||||||
|
|
@ -577,23 +572,18 @@ class FlowsService:
|
||||||
flow_data, display_name=node_display_name
|
flow_data, display_name=node_display_name
|
||||||
)
|
)
|
||||||
|
|
||||||
if target_node is None and node_id:
|
|
||||||
target_node, target_node_index = self._find_node_in_flow(
|
|
||||||
flow_data, node_id=node_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if target_node is None:
|
if target_node is None:
|
||||||
identifier = node_display_name or node_id
|
identifier = node_display_name
|
||||||
raise Exception(f"Component '{identifier}' not found in flow {flow_id}")
|
raise Exception(f"Component '{identifier}' not found in flow {flow_id}")
|
||||||
|
|
||||||
# Update the field value directly in the existing node
|
# Update the field value directly in the existing node
|
||||||
template = target_node.get("data", {}).get("node", {}).get("template", {})
|
template = target_node.get("data", {}).get("node", {}).get("template", {})
|
||||||
if template.get(field_name):
|
if template.get(field_name):
|
||||||
flow_data["data"]["nodes"][target_node_index]["data"]["node"]["template"][
|
flow_data["data"]["nodes"][target_node_index]["data"]["node"]["template"][field_name]["value"] = field_value
|
||||||
field_name
|
if "options" in flow_data["data"]["nodes"][target_node_index]["data"]["node"]["template"][field_name] and field_value not in flow_data["data"]["nodes"][target_node_index]["data"]["node"]["template"][field_name]["options"]:
|
||||||
]["value"] = field_value
|
flow_data["data"]["nodes"][target_node_index]["data"]["node"]["template"][field_name]["options"].append(field_value)
|
||||||
else:
|
else:
|
||||||
identifier = node_display_name or node_id
|
identifier = node_display_name
|
||||||
raise Exception(f"{field_name} field not found in {identifier} component")
|
raise Exception(f"{field_name} field not found in {identifier} component")
|
||||||
|
|
||||||
# Update the flow via PATCH request
|
# Update the flow via PATCH request
|
||||||
|
|
@ -606,41 +596,36 @@ class FlowsService:
|
||||||
f"Failed to update flow: HTTP {patch_response.status_code} - {patch_response.text}"
|
f"Failed to update flow: HTTP {patch_response.status_code} - {patch_response.text}"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def update_chat_flow_model(self, model_name: str):
|
async def update_chat_flow_model(self, model_name: str, provider: str):
|
||||||
"""Helper function to update the model in the chat flow"""
|
"""Helper function to update the model in the chat flow"""
|
||||||
if not LANGFLOW_CHAT_FLOW_ID:
|
if not LANGFLOW_CHAT_FLOW_ID:
|
||||||
raise ValueError("LANGFLOW_CHAT_FLOW_ID is not configured")
|
raise ValueError("LANGFLOW_CHAT_FLOW_ID is not configured")
|
||||||
await self._update_flow_field(
|
|
||||||
LANGFLOW_CHAT_FLOW_ID,
|
|
||||||
"model_name",
|
|
||||||
model_name,
|
|
||||||
node_display_name="Language Model",
|
|
||||||
)
|
|
||||||
|
|
||||||
async def update_chat_flow_system_prompt(self, system_prompt: str):
|
# Determine target component IDs based on provider
|
||||||
|
target_llm_id = self._get_provider_component_ids(provider)[1]
|
||||||
|
|
||||||
|
await self._update_flow_field(LANGFLOW_CHAT_FLOW_ID, "model_name", model_name,
|
||||||
|
node_display_name=target_llm_id)
|
||||||
|
|
||||||
|
async def update_chat_flow_system_prompt(self, system_prompt: str, provider: str):
|
||||||
"""Helper function to update the system prompt in the chat flow"""
|
"""Helper function to update the system prompt in the chat flow"""
|
||||||
if not LANGFLOW_CHAT_FLOW_ID:
|
if not LANGFLOW_CHAT_FLOW_ID:
|
||||||
raise ValueError("LANGFLOW_CHAT_FLOW_ID is not configured")
|
raise ValueError("LANGFLOW_CHAT_FLOW_ID is not configured")
|
||||||
await self._update_flow_field(
|
|
||||||
LANGFLOW_CHAT_FLOW_ID,
|
# Determine target component IDs based on provider
|
||||||
"system_prompt",
|
target_agent_id = self._get_provider_component_ids(provider)[1]
|
||||||
system_prompt,
|
|
||||||
node_display_name="Agent",
|
await self._update_flow_field(LANGFLOW_CHAT_FLOW_ID, "system_prompt", system_prompt,
|
||||||
)
|
node_display_name=target_agent_id)
|
||||||
|
|
||||||
async def update_flow_docling_preset(self, preset: str, preset_config: dict):
|
async def update_flow_docling_preset(self, preset: str, preset_config: dict):
|
||||||
"""Helper function to update docling preset in the ingest flow"""
|
"""Helper function to update docling preset in the ingest flow"""
|
||||||
if not LANGFLOW_INGEST_FLOW_ID:
|
if not LANGFLOW_INGEST_FLOW_ID:
|
||||||
raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured")
|
raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured")
|
||||||
|
|
||||||
from config.settings import DOCLING_COMPONENT_ID
|
from config.settings import DOCLING_COMPONENT_DISPLAY_NAME
|
||||||
|
await self._update_flow_field(LANGFLOW_INGEST_FLOW_ID, "docling_serve_opts", preset_config,
|
||||||
await self._update_flow_field(
|
node_display_name=DOCLING_COMPONENT_DISPLAY_NAME)
|
||||||
LANGFLOW_INGEST_FLOW_ID,
|
|
||||||
"docling_serve_opts",
|
|
||||||
preset_config,
|
|
||||||
node_id=DOCLING_COMPONENT_ID,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def update_ingest_flow_chunk_size(self, chunk_size: int):
|
async def update_ingest_flow_chunk_size(self, chunk_size: int):
|
||||||
"""Helper function to update chunk size in the ingest flow"""
|
"""Helper function to update chunk size in the ingest flow"""
|
||||||
|
|
@ -664,22 +649,22 @@ class FlowsService:
|
||||||
node_display_name="Split Text",
|
node_display_name="Split Text",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def update_ingest_flow_embedding_model(self, embedding_model: str):
|
async def update_ingest_flow_embedding_model(self, embedding_model: str, provider: str):
|
||||||
"""Helper function to update embedding model in the ingest flow"""
|
"""Helper function to update embedding model in the ingest flow"""
|
||||||
if not LANGFLOW_INGEST_FLOW_ID:
|
if not LANGFLOW_INGEST_FLOW_ID:
|
||||||
raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured")
|
raise ValueError("LANGFLOW_INGEST_FLOW_ID is not configured")
|
||||||
await self._update_flow_field(
|
|
||||||
LANGFLOW_INGEST_FLOW_ID,
|
|
||||||
"model",
|
|
||||||
embedding_model,
|
|
||||||
node_display_name="Embedding Model",
|
|
||||||
)
|
|
||||||
|
|
||||||
def _replace_node_in_flow(self, flow_data, old_id, new_node):
|
# Determine target component IDs based on provider
|
||||||
|
target_embedding_id = self._get_provider_component_ids(provider)[0]
|
||||||
|
|
||||||
|
await self._update_flow_field(LANGFLOW_INGEST_FLOW_ID, "model", embedding_model,
|
||||||
|
node_display_name=target_embedding_id)
|
||||||
|
|
||||||
|
def _replace_node_in_flow(self, flow_data, old_display_name, new_node):
|
||||||
"""Replace a node in the flow data"""
|
"""Replace a node in the flow data"""
|
||||||
nodes = flow_data.get("data", {}).get("nodes", [])
|
nodes = flow_data.get("data", {}).get("nodes", [])
|
||||||
for i, node in enumerate(nodes):
|
for i, node in enumerate(nodes):
|
||||||
if node.get("id") == old_id:
|
if node.get("data", {}).get("node", {}).get("display_name") == old_display_name:
|
||||||
nodes[i] = new_node
|
nodes[i] = new_node
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
@ -734,8 +719,8 @@ class FlowsService:
|
||||||
]
|
]
|
||||||
|
|
||||||
# Determine target component IDs based on provider
|
# Determine target component IDs based on provider
|
||||||
target_embedding_id, target_llm_id, target_llm_text_id = (
|
target_embedding_name, target_llm_name = self._get_provider_component_ids(
|
||||||
self._get_provider_component_ids(provider)
|
provider
|
||||||
)
|
)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
@ -746,9 +731,8 @@ class FlowsService:
|
||||||
result = await self._update_provider_components(
|
result = await self._update_provider_components(
|
||||||
config,
|
config,
|
||||||
provider,
|
provider,
|
||||||
target_embedding_id,
|
target_embedding_name,
|
||||||
target_llm_id,
|
target_llm_name,
|
||||||
target_llm_text_id,
|
|
||||||
embedding_model,
|
embedding_model,
|
||||||
llm_model,
|
llm_model,
|
||||||
endpoint,
|
endpoint,
|
||||||
|
|
@ -791,24 +775,12 @@ class FlowsService:
|
||||||
def _get_provider_component_ids(self, provider: str):
|
def _get_provider_component_ids(self, provider: str):
|
||||||
"""Get the component IDs for a specific provider"""
|
"""Get the component IDs for a specific provider"""
|
||||||
if provider == "watsonx":
|
if provider == "watsonx":
|
||||||
return (
|
return WATSONX_EMBEDDING_COMPONENT_DISPLAY_NAME, WATSONX_LLM_COMPONENT_DISPLAY_NAME
|
||||||
WATSONX_EMBEDDING_COMPONENT_ID,
|
|
||||||
WATSONX_LLM_COMPONENT_ID,
|
|
||||||
WATSONX_LLM_TEXT_COMPONENT_ID,
|
|
||||||
)
|
|
||||||
elif provider == "ollama":
|
elif provider == "ollama":
|
||||||
return (
|
return OLLAMA_EMBEDDING_COMPONENT_DISPLAY_NAME, OLLAMA_LLM_COMPONENT_DISPLAY_NAME
|
||||||
OLLAMA_EMBEDDING_COMPONENT_ID,
|
|
||||||
OLLAMA_LLM_COMPONENT_ID,
|
|
||||||
OLLAMA_LLM_TEXT_COMPONENT_ID,
|
|
||||||
)
|
|
||||||
elif provider == "openai":
|
elif provider == "openai":
|
||||||
# OpenAI components are the default ones
|
# OpenAI components are the default ones
|
||||||
return (
|
return OPENAI_EMBEDDING_COMPONENT_DISPLAY_NAME, OPENAI_LLM_COMPONENT_DISPLAY_NAME
|
||||||
OPENAI_EMBEDDING_COMPONENT_ID,
|
|
||||||
OPENAI_LLM_COMPONENT_ID,
|
|
||||||
OPENAI_LLM_TEXT_COMPONENT_ID,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported provider: {provider}")
|
raise ValueError(f"Unsupported provider: {provider}")
|
||||||
|
|
||||||
|
|
@ -816,9 +788,8 @@ class FlowsService:
|
||||||
self,
|
self,
|
||||||
config,
|
config,
|
||||||
provider: str,
|
provider: str,
|
||||||
target_embedding_id: str,
|
target_embedding_name: str,
|
||||||
target_llm_id: str,
|
target_llm_name: str,
|
||||||
target_llm_text_id: str,
|
|
||||||
embedding_model: str,
|
embedding_model: str,
|
||||||
llm_model: str,
|
llm_model: str,
|
||||||
endpoint: str = None,
|
endpoint: str = None,
|
||||||
|
|
@ -841,7 +812,7 @@ class FlowsService:
|
||||||
|
|
||||||
# Update embedding component
|
# Update embedding component
|
||||||
if not DISABLE_INGEST_WITH_LANGFLOW:
|
if not DISABLE_INGEST_WITH_LANGFLOW:
|
||||||
embedding_node = self._find_node_by_id(flow_data, target_embedding_id)
|
embedding_node, _ = self._find_node_in_flow(flow_data, display_name=target_embedding_name)
|
||||||
if embedding_node:
|
if embedding_node:
|
||||||
if self._update_component_fields(
|
if self._update_component_fields(
|
||||||
embedding_node, provider, embedding_model, endpoint
|
embedding_node, provider, embedding_model, endpoint
|
||||||
|
|
@ -849,22 +820,14 @@ class FlowsService:
|
||||||
updates_made.append(f"embedding model: {embedding_model}")
|
updates_made.append(f"embedding model: {embedding_model}")
|
||||||
|
|
||||||
# Update LLM component (if exists in this flow)
|
# Update LLM component (if exists in this flow)
|
||||||
if target_llm_id:
|
if target_llm_name:
|
||||||
llm_node = self._find_node_by_id(flow_data, target_llm_id)
|
llm_node, _ = self._find_node_in_flow(flow_data, display_name=target_llm_name)
|
||||||
if llm_node:
|
if llm_node:
|
||||||
if self._update_component_fields(
|
if self._update_component_fields(
|
||||||
llm_node, provider, llm_model, endpoint
|
llm_node, provider, llm_model, endpoint
|
||||||
):
|
):
|
||||||
updates_made.append(f"llm model: {llm_model}")
|
updates_made.append(f"llm model: {llm_model}")
|
||||||
|
|
||||||
if target_llm_text_id:
|
|
||||||
llm_text_node = self._find_node_by_id(flow_data, target_llm_text_id)
|
|
||||||
if llm_text_node:
|
|
||||||
if self._update_component_fields(
|
|
||||||
llm_text_node, provider, llm_model, endpoint
|
|
||||||
):
|
|
||||||
updates_made.append(f"llm model: {llm_model}")
|
|
||||||
|
|
||||||
# If no updates were made, return skip message
|
# If no updates were made, return skip message
|
||||||
if not updates_made:
|
if not updates_made:
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ class LangflowFileService:
|
||||||
|
|
||||||
# Pass metadata via tweaks to OpenSearch component
|
# Pass metadata via tweaks to OpenSearch component
|
||||||
metadata_tweaks = []
|
metadata_tweaks = []
|
||||||
if owner:
|
if owner or owner is None:
|
||||||
metadata_tweaks.append({"key": "owner", "value": owner})
|
metadata_tweaks.append({"key": "owner", "value": owner})
|
||||||
if owner_name:
|
if owner_name:
|
||||||
metadata_tweaks.append({"key": "owner_name", "value": owner_name})
|
metadata_tweaks.append({"key": "owner_name", "value": owner_name})
|
||||||
|
|
@ -106,17 +106,18 @@ class LangflowFileService:
|
||||||
metadata_tweaks.append({"key": "owner_email", "value": owner_email})
|
metadata_tweaks.append({"key": "owner_email", "value": owner_email})
|
||||||
if connector_type:
|
if connector_type:
|
||||||
metadata_tweaks.append({"key": "connector_type", "value": connector_type})
|
metadata_tweaks.append({"key": "connector_type", "value": connector_type})
|
||||||
|
logger.info(f"[LF] Metadata tweaks {metadata_tweaks}")
|
||||||
if metadata_tweaks:
|
# if metadata_tweaks:
|
||||||
# Initialize the OpenSearch component tweaks if not already present
|
# # Initialize the OpenSearch component tweaks if not already present
|
||||||
if "OpenSearchHybrid-Ve6bS" not in tweaks:
|
# if "OpenSearchHybrid-Ve6bS" not in tweaks:
|
||||||
tweaks["OpenSearchHybrid-Ve6bS"] = {}
|
# tweaks["OpenSearchHybrid-Ve6bS"] = {}
|
||||||
tweaks["OpenSearchHybrid-Ve6bS"]["docs_metadata"] = metadata_tweaks
|
# tweaks["OpenSearchHybrid-Ve6bS"]["docs_metadata"] = metadata_tweaks
|
||||||
logger.debug(
|
# logger.debug(
|
||||||
"[LF] Added metadata to tweaks", metadata_count=len(metadata_tweaks)
|
# "[LF] Added metadata to tweaks", metadata_count=len(metadata_tweaks)
|
||||||
)
|
# )
|
||||||
if tweaks:
|
if tweaks:
|
||||||
payload["tweaks"] = tweaks
|
payload["tweaks"] = tweaks
|
||||||
|
logger.debug(f"[LF] Tweaks {tweaks}")
|
||||||
if session_id:
|
if session_id:
|
||||||
payload["session_id"] = session_id
|
payload["session_id"] = session_id
|
||||||
|
|
||||||
|
|
@ -137,9 +138,13 @@ class LangflowFileService:
|
||||||
"X-Langflow-Global-Var-OWNER_EMAIL": str(owner_email),
|
"X-Langflow-Global-Var-OWNER_EMAIL": str(owner_email),
|
||||||
"X-Langflow-Global-Var-CONNECTOR_TYPE": str(connector_type),
|
"X-Langflow-Global-Var-CONNECTOR_TYPE": str(connector_type),
|
||||||
}
|
}
|
||||||
|
logger.info(f"[LF] Headers {headers}")
|
||||||
|
logger.info(f"[LF] Payload {payload}")
|
||||||
resp = await clients.langflow_request(
|
resp = await clients.langflow_request(
|
||||||
"POST", f"/api/v1/run/{self.flow_id_ingest}", json=payload, headers=headers
|
"POST",
|
||||||
|
f"/api/v1/run/{self.flow_id_ingest}",
|
||||||
|
json=payload,
|
||||||
|
headers=headers,
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"[LF] Run response", status_code=resp.status_code, reason=resp.reason_phrase
|
"[LF] Run response", status_code=resp.status_code, reason=resp.reason_phrase
|
||||||
|
|
@ -160,6 +165,7 @@ class LangflowFileService:
|
||||||
body=resp.text[:1000],
|
body=resp.text[:1000],
|
||||||
error=str(e),
|
error=str(e),
|
||||||
)
|
)
|
||||||
|
|
||||||
raise
|
raise
|
||||||
return resp_json
|
return resp_json
|
||||||
|
|
||||||
|
|
@ -171,6 +177,10 @@ class LangflowFileService:
|
||||||
settings: Optional[Dict[str, Any]] = None,
|
settings: Optional[Dict[str, Any]] = None,
|
||||||
jwt_token: Optional[str] = None,
|
jwt_token: Optional[str] = None,
|
||||||
delete_after_ingest: bool = True,
|
delete_after_ingest: bool = True,
|
||||||
|
owner: Optional[str] = None,
|
||||||
|
owner_name: Optional[str] = None,
|
||||||
|
owner_email: Optional[str] = None,
|
||||||
|
connector_type: Optional[str] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Combined upload, ingest, and delete operation.
|
Combined upload, ingest, and delete operation.
|
||||||
|
|
@ -257,6 +267,10 @@ class LangflowFileService:
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
tweaks=final_tweaks,
|
tweaks=final_tweaks,
|
||||||
jwt_token=jwt_token,
|
jwt_token=jwt_token,
|
||||||
|
owner=owner,
|
||||||
|
owner_name=owner_name,
|
||||||
|
owner_email=owner_email,
|
||||||
|
connector_type=connector_type,
|
||||||
)
|
)
|
||||||
logger.debug("[LF] Ingestion completed successfully")
|
logger.debug("[LF] Ingestion completed successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue