Merge branch 'main' into docs-env-vars
This commit is contained in:
commit
7b9b09da46
25 changed files with 520 additions and 465 deletions
|
|
@ -33,8 +33,8 @@ RUN uv sync --frozen --no-install-project --no-editable --extra postgresql
|
|||
|
||||
# Build frontend
|
||||
WORKDIR /app/src/frontend
|
||||
RUN npm ci && \
|
||||
npm run build && \
|
||||
RUN NODE_OPTIONS=--max_old_space_size=4096 npm ci && \
|
||||
NODE_OPTIONS=--max_old_space_size=4096 npm run build && \
|
||||
mkdir -p /app/src/backend/base/langflow/frontend && \
|
||||
cp -r build/* /app/src/backend/base/langflow/frontend/
|
||||
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ import Icon from "@site/src/components/icon/icon";
|
|||
|
||||
All flows included with OpenRAG are designed to be modular, performant, and provider-agnostic.
|
||||
To modify a flow, click <Icon name="Settings2" aria-hidden="true"/> **Settings**, and click **Edit in Langflow**.
|
||||
Flows are edited in the same way as in the [Langflow visual editor](https://docs.langflow.org/concepts-overview).
|
||||
OpenRAG's visual editor is based on the [Langflow visual editor](https://docs.langflow.org/concepts-overview), so you can edit your flows to match your specific use case.
|
||||
|
|
@ -1,51 +1,49 @@
|
|||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
### Application onboarding
|
||||
## Application onboarding
|
||||
|
||||
The first time you start OpenRAG, whether using the TUI or a `.env` file, a `config.yaml` file is generated if OpenRAG detects one doesn't exist.
|
||||
The `config.yaml` file controls application configuration, including language model and embedding model provider, Docling ingestion settings, and API keys.
|
||||
The first time you start OpenRAG, whether using the TUI or a `.env` file, you must complete application onboarding.
|
||||
|
||||
Values input during onboarding can be changed later in the OpenRAG **Settings** page, except for the language model and embedding model _provider_. The provider can only be selected during onboarding, and you must use the same provider for your language model and embedding model.
|
||||
Values input during onboarding can be changed later in the OpenRAG **Settings** page, except for the language model and embedding model _provider_.
|
||||
**Your provider can only be selected once, and you must use the same provider for your language model and embedding model.**
|
||||
The language model can be changed, but the embeddings model cannot be changed.
|
||||
To change your provider selection, you must completely reinstall OpenRAG.
|
||||
|
||||
1. Select your language model and embedding model provider, and complete the required fields.
|
||||
**Your provider can only be selected once, and you must use the same provider for your language model and embedding model.**
|
||||
The language model can be changed, but the embeddings model cannot be changed.
|
||||
To change your provider selection, you must restart OpenRAG and delete the `config.yml` file.
|
||||
<Tabs groupId="Provider">
|
||||
<TabItem value="OpenAI" label="OpenAI" default>
|
||||
1. Enable **Get API key from environment variable** to automatically enter your key from the TUI-generated `.env` file.
|
||||
2. Under **Advanced settings**, select your **Embedding Model** and **Language Model**.
|
||||
3. To load 2 sample PDFs, enable **Sample dataset**.
|
||||
This is recommended, but not required.
|
||||
4. Click **Complete**.
|
||||
5. Continue with the [Quickstart](/quickstart).
|
||||
|
||||
<Tabs groupId="Embedding provider">
|
||||
<TabItem value="OpenAI" label="OpenAI" default>
|
||||
2. If you already entered a value for `OPENAI_API_KEY` in the TUI in Step 5, enable **Get API key from environment variable**.
|
||||
3. Under **Advanced settings**, select your **Embedding Model** and **Language Model**.
|
||||
4. To load 2 sample PDFs, enable **Sample dataset**.
|
||||
This is recommended, but not required.
|
||||
5. Click **Complete**.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="IBM watsonx.ai" label="IBM watsonx.ai">
|
||||
2. Complete the fields for **watsonx.ai API Endpoint**, **IBM API key**, and **IBM Project ID**.
|
||||
These values are found in your IBM watsonx deployment.
|
||||
3. Under **Advanced settings**, select your **Embedding Model** and **Language Model**.
|
||||
4. To load 2 sample PDFs, enable **Sample dataset**.
|
||||
This is recommended, but not required.
|
||||
5. Click **Complete**.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Ollama" label="Ollama">
|
||||
:::tip
|
||||
Ollama is not included with OpenRAG. To install Ollama, see the [Ollama documentation](https://docs.ollama.com/).
|
||||
:::
|
||||
2. Enter your Ollama server's base URL address.
|
||||
The default Ollama server address is `http://localhost:11434`.
|
||||
Since OpenRAG is running in a container, you may need to change `localhost` to access services outside of the container. For example, change `http://localhost:11434` to `http://host.docker.internal:11434` to connect to Ollama.
|
||||
OpenRAG automatically sends a test connection to your Ollama server to confirm connectivity.
|
||||
3. Select the **Embedding Model** and **Language Model** your Ollama server is running.
|
||||
OpenRAG automatically lists the available models from your Ollama server.
|
||||
4. To load 2 sample PDFs, enable **Sample dataset**.
|
||||
This is recommended, but not required.
|
||||
5. Click **Complete**.
|
||||
</TabItem>
|
||||
<TabItem value="IBM watsonx.ai" label="IBM watsonx.ai">
|
||||
1. Complete the fields for **watsonx.ai API Endpoint**, **IBM API key**, and **IBM Project ID**.
|
||||
These values are found in your IBM watsonx deployment.
|
||||
2. Under **Advanced settings**, select your **Embedding Model** and **Language Model**.
|
||||
3. To load 2 sample PDFs, enable **Sample dataset**.
|
||||
This is recommended, but not required.
|
||||
4. Click **Complete**.
|
||||
5. Continue with the [Quickstart](/quickstart).
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem value="Ollama" label="Ollama">
|
||||
:::tip
|
||||
Ollama is not included with OpenRAG. To install Ollama, see the [Ollama documentation](https://docs.ollama.com/).
|
||||
:::
|
||||
1. Enter your Ollama server's base URL address.
|
||||
The default Ollama server address is `http://localhost:11434`.
|
||||
Since OpenRAG is running in a container, you may need to change `localhost` to access services outside of the container. For example, change `http://localhost:11434` to `http://host.docker.internal:11434` to connect to Ollama.
|
||||
OpenRAG automatically sends a test connection to your Ollama server to confirm connectivity.
|
||||
2. Select the **Embedding Model** and **Language Model** your Ollama server is running.
|
||||
OpenRAG automatically lists the available models from your Ollama server.
|
||||
3. To load 2 sample PDFs, enable **Sample dataset**.
|
||||
This is recommended, but not required.
|
||||
4. Click **Complete**.
|
||||
5. Continue with the [Quickstart](/quickstart).
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
6. Continue with the [Quickstart](/quickstart).
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: Agents powered by Langflow
|
||||
title: Langflow Agents
|
||||
slug: /agents
|
||||
---
|
||||
|
||||
|
|
@ -13,9 +13,13 @@ import PartialExternalPreview from '@site/docs/_partial-external-preview.mdx';
|
|||
|
||||
OpenRAG leverages Langflow's Agent component to power the OpenRAG OpenSearch Agent flow.
|
||||
|
||||
This flow intelligently chats with your knowledge by embedding your query, comparing it the vector database embeddings, and generating a response with the LLM.
|
||||
[Flows](https://docs.langflow.org/concepts-overview) in Langflow are functional representations of application workflows, with multiple [component](https://docs.langflow.org/concepts-components) nodes connected as single steps in a workflow.
|
||||
|
||||
The Agent component shines here in its ability to make decisions on not only what query should be sent, but when a query is necessary to solve the problem at hand.
|
||||
In the OpenRAG OpenSearch Agent flow, components like the Langflow [**Agent** component](https://docs.langflow.org/agents) and [**OpenSearch** component](https://docs.langflow.org/bundles-elastic#opensearch) are connected to intelligently chat with your knowledge by embedding your query, comparing it the vector database embeddings, and generating a response with the LLM.
|
||||
|
||||

|
||||
|
||||
The Agent component shines here in its ability to make decisions on not only what query should be sent, but when a query is necessary to solve the problem at hand.
|
||||
|
||||
<details closed>
|
||||
<summary>How do agents work?</summary>
|
||||
|
|
@ -33,22 +37,32 @@ In an agentic context, tools are functions that the agent can run to perform tas
|
|||
## Use the OpenRAG OpenSearch Agent flow
|
||||
|
||||
If you've chatted with your knowledge in OpenRAG, you've already experienced the OpenRAG OpenSearch Agent chat flow.
|
||||
To view the flow, click <Icon name="Settings2" aria-hidden="true"/> **Settings**, and then click **Edit in Langflow**.
|
||||
This flow contains seven components:
|
||||
To switch OpenRAG over to the [Langflow visual editor](https://docs.langflow.org/concepts-overview) and view the OpenRAG OpenSearch Agentflow, click <Icon name="Settings2" aria-hidden="true"/> **Settings**, and then click **Edit in Langflow**.
|
||||
This flow contains seven components connected together to chat with your data:
|
||||
|
||||
* The Agent component orchestrates the entire flow by deciding when to search the knowledge base, how to formulate search queries, and how to combine retrieved information with the user's question to generate a comprehensive response.
|
||||
The Agent behaves according to the prompt in the **Agent Instructions** field.
|
||||
* The Chat Input component is connected to the Agent component's Input port. This allows to flow to be triggered by an incoming prompt from a user or application.
|
||||
* The OpenSearch component is connected to the Agent component's Tools port. The agent may not use this database for every request; the agent only uses this connection if it decides the knowledge can help respond to the prompt.
|
||||
* The Language Model component is connected to the Agent component's Language Model port. The agent uses the connected LLM to reason through the request sent through Chat Input.
|
||||
* The Embedding Model component is connected to the OpenSearch component's Embedding port. This component converts text queries into vector representations that are compared with document embeddings stored in OpenSearch for semantic similarity matching. This gives your Agent's queries context.
|
||||
* The Text Input component is populated with the global variable `OPENRAG-QUERY-FILTER`.
|
||||
This filter is the Knowledge filter, and filters which knowledge sources to search through.
|
||||
* The Agent component's Output port is connected to the Chat Output component, which returns the final response to the user or application.
|
||||
* The [**Agent** component](https://docs.langflow.org/agents) orchestrates the entire flow by deciding when to search the knowledge base, how to formulate search queries, and how to combine retrieved information with the user's question to generate a comprehensive response.
|
||||
The **Agent** behaves according to the prompt in the **Agent Instructions** field.
|
||||
* The [**Chat Input** component](https://docs.langflow.org/components-io) is connected to the Agent component's Input port. This allows to flow to be triggered by an incoming prompt from a user or application.
|
||||
* The [**OpenSearch** component](https://docs.langflow.org/bundles-elastic#opensearch) is connected to the Agent component's Tools port. The agent may not use this database for every request; the agent only uses this connection if it decides the knowledge can help respond to the prompt.
|
||||
* The [**Language Model** component](https://docs.langflow.org/components-models) is connected to the Agent component's Language Model port. The agent uses the connected LLM to reason through the request sent through Chat Input.
|
||||
* The [**Embedding Model** component](https://docs.langflow.org/components-embedding-models) is connected to the OpenSearch component's Embedding port. This component converts text queries into vector representations that are compared with document embeddings stored in OpenSearch for semantic similarity matching. This gives your Agent's queries context.
|
||||
* The [**Text Input** component](https://docs.langflow.org/components-io) is populated with the global variable `OPENRAG-QUERY-FILTER`.
|
||||
This filter is the [Knowledge filter](/knowledge#create-knowledge-filters), and filters which knowledge sources to search through.
|
||||
* The **Agent** component's Output port is connected to the [**Chat Output** component](https://docs.langflow.org/components-io), which returns the final response to the user or application.
|
||||
|
||||
<PartialModifyFlows />
|
||||
|
||||
For an example of changing out the agent's LLM in OpenRAG, see the [Quickstart](/quickstart#change-components).
|
||||
|
||||
To restore the flow to its initial state, in OpenRAG, click <Icon name="Settings" aria-hidden="true"/> **Settings**, and then click **Restore Flow**.
|
||||
OpenRAG warns you that this discards all custom settings. Click **Restore** to restore the flow.
|
||||
OpenRAG warns you that this discards all custom settings. Click **Restore** to restore the flow.
|
||||
|
||||
## Additional Langflow functionality
|
||||
|
||||
Langflow includes features beyond Agents to help you integrate OpenRAG into your application, and all Langflow features are included in OpenRAG.
|
||||
|
||||
* Langflow can serve your flows as an [MCP server](https://docs.langflow.org/mcp-server), or consume other MCP servers as an [MCP client](https://docs.langflow.org/mcp-client). Get started with the [MCP tutorial](https://docs.langflow.org/mcp-tutorial).
|
||||
|
||||
* If you don't see the component you need, extend Langflow's functionality by creating [custom Python components](https://docs.langflow.org/components-custom-components).
|
||||
|
||||
* Langflow offers component [bundles](https://docs.langflow.org/components-bundle-components) to integrate with many popular vector stores, AI/ML providers, and search APIs.
|
||||
|
|
@ -12,19 +12,9 @@ import PartialExternalPreview from '@site/docs/_partial-external-preview.mdx';
|
|||
<PartialExternalPreview />
|
||||
|
||||
OpenRAG uses [OpenSearch](https://docs.opensearch.org/latest/) for its vector-backed knowledge store.
|
||||
This is a specialized database for storing and retrieving embeddings, which helps your Agent efficiently find relevant information.
|
||||
OpenSearch provides powerful hybrid search capabilities with enterprise-grade security and multi-tenancy support.
|
||||
|
||||
## Explore knowledge
|
||||
|
||||
The Knowledge page lists the documents OpenRAG has ingested into the OpenSearch vector database's `documents` index.
|
||||
|
||||
To explore your current knowledge, click <Icon name="Library" aria-hidden="true"/> **Knowledge**.
|
||||
Click on a document to display the chunks derived from splitting the default documents into the vector database.
|
||||
|
||||
Documents are processed with the default **Knowledge Ingest** flow, so if you want to split your documents differently, edit the **Knowledge Ingest** flow.
|
||||
|
||||
<PartialModifyFlows />
|
||||
|
||||
## Ingest knowledge
|
||||
|
||||
OpenRAG supports knowledge ingestion through direct file uploads and OAuth connectors.
|
||||
|
|
@ -33,7 +23,7 @@ OpenRAG supports knowledge ingestion through direct file uploads and OAuth conne
|
|||
|
||||
The **Knowledge Ingest** flow uses Langflow's [**File** component](https://docs.langflow.org/components-data#file) to split and embed files loaded from your local machine into the OpenSearch database.
|
||||
|
||||
The default path to your local folder is mounted from the `./documents` folder in your OpenRAG project directory to the `/app/documents/` directory inside the Docker container. Files added to the host or the container will be visible in both locations. To configure this location, modify the **Documents Paths** variable in either the TUI's [Advanced Setup](/install#advanced-setup) or in the `.env` used by Docker Compose.
|
||||
The default path to your local folder is mounted from the `./documents` folder in your OpenRAG project directory to the `/app/documents/` directory inside the Docker container. Files added to the host or the container will be visible in both locations. To configure this location, modify the **Documents Paths** variable in either the TUI's [Advanced Setup](/install#setup) menu or in the `.env` used by Docker Compose.
|
||||
|
||||
To load and process a single file from the mapped location, click <Icon name="Plus" aria-hidden="true"/> **Add Knowledge**, and then click **Add File**.
|
||||
The file is loaded into your OpenSearch database, and appears in the Knowledge page.
|
||||
|
|
@ -57,7 +47,7 @@ If you wish to use another provider, add the secrets to another provider.
|
|||
<TabItem value="TUI" label="TUI" default>
|
||||
1. If OpenRAG is running, stop it with **Status** > **Stop Services**.
|
||||
2. Click **Advanced Setup**.
|
||||
3. Add the OAuth provider's client and secret key in the [Advanced Setup](/install#advanced-setup) menu.
|
||||
3. Add the OAuth provider's client and secret key in the [Advanced Setup](/install#setup) menu.
|
||||
4. Click **Save Configuration**.
|
||||
The TUI generates a new `.env` file with your OAuth values.
|
||||
5. Click **Start Container Services**.
|
||||
|
|
@ -100,6 +90,17 @@ You can monitor the sync progress in the <Icon name="Bell" aria-hidden="true"/>
|
|||
|
||||
Once processing is complete, the synced documents become available in your knowledge base and can be searched through the chat interface or Knowledge page.
|
||||
|
||||
## Explore knowledge
|
||||
|
||||
The **Knowledge** page lists the documents OpenRAG has ingested into the OpenSearch vector database's `documents` index.
|
||||
|
||||
To explore your current knowledge, click <Icon name="Library" aria-hidden="true"/> **Knowledge**.
|
||||
Click on a document to display the chunks derived from splitting the default documents into the vector database.
|
||||
|
||||
Documents are processed with the default **Knowledge Ingest** flow, so if you want to split your documents differently, edit the **Knowledge Ingest** flow.
|
||||
|
||||
<PartialModifyFlows />
|
||||
|
||||
### Knowledge ingestion settings
|
||||
|
||||
To configure the knowledge ingestion pipeline parameters, see [Docling Ingestion](/ingestion).
|
||||
|
|
@ -139,7 +140,7 @@ A new filter is created with default settings that match everything.
|
|||
|
||||
OpenRAG automatically detects and configures the correct vector dimensions for embedding models, ensuring optimal search performance and compatibility.
|
||||
|
||||
The complete list of supported models is available at [/src/services/models_service.py](https://github.com/langflow-ai/openrag/blob/main/src/services/models_service.py).
|
||||
The complete list of supported models is available at [`models_service.py` in the OpenRAG repository](https://github.com/langflow-ai/openrag/blob/main/src/services/models_service.py).
|
||||
|
||||
You can use custom embedding models by specifying them in your configuration.
|
||||
|
||||
|
|
@ -147,4 +148,4 @@ If you use an unknown embedding model, OpenRAG will automatically fall back to `
|
|||
|
||||
The default embedding dimension is `1536` and the default model is `text-embedding-3-small`.
|
||||
|
||||
For models with known vector dimensions, see [/src/config/settings.py](https://github.com/langflow-ai/openrag/blob/main/src/config/settings.py).
|
||||
For models with known vector dimensions, see [`settings.py` in the OpenRAG repository](https://github.com/langflow-ai/openrag/blob/main/src/config/settings.py).
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
title: Docker deployment
|
||||
title: Deploy with Docker
|
||||
slug: /get-started/docker
|
||||
---
|
||||
|
||||
import PartialOnboarding from '@site/docs/_partial-onboarding.mdx';
|
||||
import PartialOnboarding from '@site/docs/_partial-onboarding.mdx';
|
||||
import PartialExternalPreview from '@site/docs/_partial-external-preview.mdx';
|
||||
|
||||
<PartialExternalPreview />
|
||||
|
|
@ -15,7 +15,18 @@ They deploy the same applications and containers, but to different environments.
|
|||
|
||||
- [`docker-compose-cpu.yml`](https://github.com/langflow-ai/openrag/blob/main/docker-compose-cpu.yml) is a CPU-only version of OpenRAG for systems without GPU support. Use this Docker compose file for environments where GPU drivers aren't available.
|
||||
|
||||
To install OpenRAG with Docker Compose:
|
||||
## Prerequisites
|
||||
|
||||
- [Python Version 3.10 to 3.13](https://www.python.org/downloads/release/python-3100/)
|
||||
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
||||
- [Podman](https://podman.io/docs/installation) (recommended) or [Docker](https://docs.docker.com/get-docker/) installed
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/) installed. If you're using Podman, use [podman-compose](https://docs.podman.io/en/latest/markdown/podman-compose.1.html) or alias Docker compose commands to Podman commands.
|
||||
- Create an [OpenAI API key](https://platform.openai.com/api-keys). This key is **required** to start OpenRAG, but you can choose a different model provider during [Application Onboarding](#application-onboarding).
|
||||
- Optional: GPU support requires an NVIDIA GPU with CUDA support and compatible NVIDIA drivers installed on the OpenRAG host machine. If you don't have GPU capabilities, OpenRAG provides an alternate CPU-only deployment.
|
||||
|
||||
## Deploy OpenRAG with Docker Compose
|
||||
|
||||
To install OpenRAG with Docker Compose, do the following:
|
||||
|
||||
1. Clone the OpenRAG repository.
|
||||
```bash
|
||||
|
|
@ -23,7 +34,7 @@ To install OpenRAG with Docker Compose:
|
|||
cd openrag
|
||||
```
|
||||
|
||||
2. Copy the example `.env` file that is included in the repository root.
|
||||
2. Copy the example `.env` file included in the repository root.
|
||||
The example file includes all environment variables with comments to guide you in finding and setting their values.
|
||||
```bash
|
||||
cp .env.example .env
|
||||
|
|
@ -34,7 +45,8 @@ To install OpenRAG with Docker Compose:
|
|||
touch .env
|
||||
```
|
||||
|
||||
3. Set environment variables. The Docker Compose files are populated with values from your `.env`, so the following values are **required** to be set:
|
||||
3. Set environment variables. The Docker Compose files will be populated with values from your `.env`.
|
||||
The following values are **required** to be set:
|
||||
|
||||
```bash
|
||||
OPENSEARCH_PASSWORD=your_secure_password
|
||||
|
|
@ -43,8 +55,8 @@ To install OpenRAG with Docker Compose:
|
|||
LANGFLOW_SUPERUSER_PASSWORD=your_langflow_password
|
||||
LANGFLOW_SECRET_KEY=your_secret_key
|
||||
```
|
||||
|
||||
For more information on configuring OpenRAG with environment variables, see [Environment variables](/reference/configuration).
|
||||
For additional configuration values, including `config.yaml`, see [Configuration](/reference/configuration).
|
||||
|
||||
4. Deploy OpenRAG with Docker Compose based on your deployment type.
|
||||
|
||||
|
|
@ -79,7 +91,7 @@ To install OpenRAG with Docker Compose:
|
|||
- **Backend API**: http://localhost:8000
|
||||
- **Langflow**: http://localhost:7860
|
||||
|
||||
6. To use the OpenRAG application and continue with application onboarding, access the frontend at `http://localhost:3000`.
|
||||
6. Continue with [Application Onboarding](#application-onboarding).
|
||||
|
||||
<PartialOnboarding />
|
||||
|
||||
|
|
|
|||
|
|
@ -10,37 +10,47 @@ import PartialExternalPreview from '@site/docs/_partial-external-preview.mdx';
|
|||
|
||||
<PartialExternalPreview />
|
||||
|
||||
OpenRAG can be installed in multiple ways:
|
||||
[Install the OpenRAG Python wheel](#install-python-wheel) and then use the [OpenRAG Terminal User Interface(TUI)](#setup) to run and configure your OpenRAG deployment with a guided setup process.
|
||||
|
||||
* [**Python wheel**](#install-python-wheel): Install the OpenRAG Python wheel and use the [OpenRAG Terminal User Interface (TUI)](/get-started/tui) to install, run, and configure your OpenRAG deployment without running Docker commands.
|
||||
|
||||
* [**Docker Compose**](get-started/docker): Clone the OpenRAG repository and deploy OpenRAG with Docker Compose, including all services and dependencies.
|
||||
If you prefer running Docker commands and manually editing `.env` files, see [Deploy with Docker](/get-started/docker).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Python Version 3.10 to 3.13](https://www.python.org/downloads/release/python-3100/)
|
||||
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
||||
- [Docker](https://docs.docker.com/get-docker/) or [Podman](https://podman.io/docs/installation) installed
|
||||
- [Podman](https://podman.io/docs/installation) (recommended) or [Docker](https://docs.docker.com/get-docker/) installed
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/) installed. If using Podman, use [podman-compose](https://docs.podman.io/en/latest/markdown/podman-compose.1.html) or alias Docker compose commands to Podman commands.
|
||||
- For GPU support: (TBD)
|
||||
- Create an [OpenAI API key](https://platform.openai.com/api-keys). This key is **required** to start OpenRAG, but you can choose a different model provider during [Application Onboarding](#application-onboarding).
|
||||
- Optional: GPU support requires an NVIDIA GPU with [CUDA](https://docs.nvidia.com/cuda/) support and compatible NVIDIA drivers installed on the OpenRAG host machine. If you don't have GPU capabilities, OpenRAG provides an alternate CPU-only deployment.
|
||||
|
||||
## Install the OpenRAG Python wheel {#install-python-wheel}
|
||||
|
||||
The Python wheel is currently available internally, but will be available on PyPI at launch.
|
||||
The wheel installs the OpenRAG wheel, which includes the TUI for installing, running, and managing OpenRAG.
|
||||
For more information on virtual environments, see [uv](https://docs.astral.sh/uv/pip/environments).
|
||||
:::important
|
||||
The `.whl` file is currently available as an internal download during public preview, and will be published to PyPI in a future release.
|
||||
:::
|
||||
|
||||
1. Create a new project with a virtual environment using [uv](https://docs.astral.sh/uv/pip/environments).
|
||||
The OpenRAG wheel installs the Terminal User Interface (TUI) for running and managing OpenRAG.
|
||||
|
||||
1. Create a new project with a virtual environment using `uv`.
|
||||
This creates and activates a virtual environment for your project.
|
||||
|
||||
```bash
|
||||
uv init YOUR_PROJECT_NAME
|
||||
cd YOUR_PROJECT_NAME
|
||||
```
|
||||
2. Add the OpenRAG wheel to your project and install it in the virtual environment.
|
||||
Replace `PATH/TO/` and `VERSION` with your OpenRAG wheel location and version.
|
||||
|
||||
The terminal prompt won't change like it would when using `venv`, but the `uv` commands will use the project's virtual environment.
|
||||
For more information on virtual environments, see the [uv documentation](https://docs.astral.sh/uv/pip/environments).
|
||||
|
||||
2. Add the local OpenRAG wheel to your project's virtual environment.
|
||||
|
||||
```bash
|
||||
uv add PATH/TO/openrag-VERSION-py3-none-any.whl
|
||||
```
|
||||
Replace `PATH/TO/` and `VERSION` with the path and version of your downloaded OpenRAG `.whl` file.
|
||||
|
||||
For example, if your `.whl` file is in the `~/Downloads` directory, the command is `uv add ~/Downloads/openrag-0.1.8-py3-none-any.whl`.
|
||||
|
||||
3. Ensure all dependencies are installed and updated in your virtual environment.
|
||||
```bash
|
||||
uv sync
|
||||
|
|
@ -51,59 +61,64 @@ For more information on virtual environments, see [uv](https://docs.astral.sh/uv
|
|||
uv run openrag
|
||||
```
|
||||
|
||||
The OpenRAG TUI opens.
|
||||
5. Continue with [Setup OpenRAG with the TUI](#setup).
|
||||
|
||||
5. To install OpenRAG with Basic Setup, click **Basic Setup** or press <kbd>1</kbd>. Basic Setup does not set up OAuth connections for ingestion from Google Drive, OneDrive, or AWS. For OAuth setup, see [Advanced Setup](#advanced-setup).
|
||||
The TUI prompts you for the required startup values.
|
||||
Click **Generate Passwords** to autocomplete fields that contain **Auto-generated Secure Password**, or bring your own passwords.
|
||||
<details closed>
|
||||
<summary>Where do I find the required startup values?</summary>
|
||||
|
||||
| Variable | Where to Find | Description |
|
||||
|----------|---------------|-------------|
|
||||
| `OPENSEARCH_PASSWORD` | Auto-generated secure password | The password for OpenSearch database access. Must be at least 8 characters and must contain at least one uppercase letter, one lowercase letter, one digit, and one special character. |
|
||||
| `OPENAI_API_KEY` | [OpenAI Platform](https://platform.openai.com/api-keys) | API key from your OpenAI account. |
|
||||
| `LANGFLOW_SUPERUSER` | User generated | Username for Langflow admin access. For more, see [Langflow docs](https://docs.langflow.org/api-keys-and-authentication#langflow-superuser). |
|
||||
| `LANGFLOW_SUPERUSER_PASSWORD` | Auto-generated secure password | Password for Langflow admin access. For more, see the [Langflow docs](https://docs.langflow.org/api-keys-and-authentication#langflow-superuser). |
|
||||
| `LANGFLOW_SECRET_KEY` | Auto-generated secure key | Secret key for Langflow security. For more, see the [Langflow docs](https://docs.langflow.org/api-keys-and-authentication#langflow-secret-key). |
|
||||
| `LANGFLOW_AUTO_LOGIN` | Auto-generated or manual | Auto-login configuration. For more, see the [Langflow docs](https://docs.langflow.org/api-keys-and-authentication#langflow-auto-login). |
|
||||
| `LANGFLOW_NEW_USER_IS_ACTIVE` | Langflow | New user activation setting. For more, see the [Langflow docs](https://docs.langflow.org/api-keys-and-authentication#langflow-new-user-is-active). |
|
||||
| `LANGFLOW_ENABLE_SUPERUSER_CLI` | Langflow server | Superuser CLI access setting. For more, see the [Langflow docs](https://docs.langflow.org/api-keys-and-authentication#langflow-enable-superuser-cli). |
|
||||
| `DOCUMENTS_PATH` | Set your local path | Path to your document storage directory. |
|
||||
|
||||
</details>
|
||||
|
||||
To complete credentials, click **Save Configuration**.
|
||||
## Set up OpenRAG with the TUI {#setup}
|
||||
|
||||
6. To start OpenRAG with your credentials, click **Start Container Services**.
|
||||
Startup pulls container images and starts them, so it can take some time.
|
||||
The operation has completed when the **Close** button is available and the terminal displays:
|
||||
```bash
|
||||
Services started successfully
|
||||
Command completed successfully
|
||||
```
|
||||
**Basic Setup** completes or auto-generates most of the required values to start OpenRAG.
|
||||
**Basic Setup** does not set up OAuth connections for ingestion from Google Drive, OneDrive, or AWS.
|
||||
For OAuth setup, use **Advanced Setup**.
|
||||
|
||||
7. To open the OpenRAG application and continue with application onboarding, click **Open App**, press <kbd>6</kbd>, or navigate to `http://localhost:3000`.
|
||||
The application opens.
|
||||
If the TUI detects OAuth credentials, it enforces the **Advanced Setup** path.
|
||||
If the TUI detects a `.env` file in the OpenRAG root directory, it will source any variables from the `.env` file.
|
||||
<Tabs groupId="Setup method">
|
||||
<TabItem value="Basic setup" label="Basic setup" default>
|
||||
|
||||
1. To install OpenRAG with **Basic Setup**, click **Basic Setup** or press <kbd>1</kbd>.
|
||||
2. Click **Generate Passwords** to generate passwords for OpenSearch and Langflow.
|
||||
3. Paste your OpenAI API key in the OpenAI API key field.
|
||||
4. Click **Save Configuration**.
|
||||
5. To start OpenRAG, click **Start Container Services**.
|
||||
Startup pulls container images and runs them, so it can take some time.
|
||||
When startup is complete, the TUI displays the following:
|
||||
```bash
|
||||
Services started successfully
|
||||
Command completed successfully
|
||||
```
|
||||
6. To open the OpenRAG application, click **Open App**.
|
||||
7. Continue with [Application Onboarding](#application-onboarding).
|
||||
</TabItem>
|
||||
<TabItem value="Advanced setup" label="Advanced setup">
|
||||
1. To install OpenRAG with **Advanced Setup**, click **Advanced Setup** or press <kbd>2</kbd>.
|
||||
2. Click **Generate Passwords** to generate passwords for OpenSearch and Langflow.
|
||||
3. Paste your OpenAI API key in the OpenAI API key field.
|
||||
4. Add your client and secret values for Google, Azure, or AWS OAuth.
|
||||
These values can be found in your OAuth provider.
|
||||
5. The OpenRAG TUI presents redirect URIs for your OAuth app.
|
||||
These are the URLs your OAuth provider will redirect back to after user sign-in.
|
||||
Register these redirect values with your OAuth provider as they are presented in the TUI.
|
||||
6. Click **Save Configuration**.
|
||||
7. To start OpenRAG, click **Start Container Services**.
|
||||
Startup pulls container images and runs them, so it can take some time.
|
||||
When startup is complete, the TUI displays the following:
|
||||
```bash
|
||||
Services started successfully
|
||||
Command completed successfully
|
||||
```
|
||||
8. To open the OpenRAG application, click **Open App**, press <kbd>6</kbd>, or navigate to `http://localhost:3000`.
|
||||
You will be presented with your provider's OAuth sign-in screen, and be redirected to the redirect URI after sign-in.
|
||||
Continue with Application Onboarding.
|
||||
|
||||
Two additional variables are available for Advanced Setup:
|
||||
|
||||
The `LANGFLOW_PUBLIC_URL` controls where the Langflow web interface can be accessed. This is where users interact with their flows in a browser.
|
||||
|
||||
The `WEBHOOK_BASE_URL` controls where the endpoint for `/connectors/CONNECTOR_TYPE/webhook` will be available.
|
||||
This connection enables real-time document synchronization with external services.
|
||||
For example, for Google Drive file synchronization the webhook URL is `/connectors/google_drive/webhook`.
|
||||
|
||||
9. Continue with [Application Onboarding](#application-onboarding).
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<PartialOnboarding />
|
||||
|
||||
### Advanced Setup {#advanced-setup}
|
||||
|
||||
**Advanced Setup** includes the required values from **Basic Setup**, with additional settings for OAuth credentials.
|
||||
If the OpenRAG TUI detects OAuth credentials, it enforces the Advanced Setup path.
|
||||
1. Add your client and secret values for Google, Azure, or AWS OAuth.
|
||||
These values can be found in your OAuth provider.
|
||||
2. The OpenRAG TUI presents redirect URIs for your OAuth app.
|
||||
These are the URLs your OAuth provider will redirect back to after user sign-in.
|
||||
Register these redirect values with your OAuth provider as they are presented in the TUI.
|
||||
3. To open the OpenRAG application, click **Open App** or press <kbd>6</kbd>.
|
||||
You will be presented with your provider's OAuth sign-in screen, and be redirected to the redirect URI after sign-in.
|
||||
|
||||
Two additional variables are available for Advanced Setup:
|
||||
|
||||
The `LANGFLOW_PUBLIC_URL` controls where the Langflow web interface can be accessed. This is where users interact with their flows in a browser.
|
||||
|
||||
The `WEBHOOK_BASE_URL` controls where the endpoint for `/connectors/CONNECTOR_TYPE/webhook` will be available.
|
||||
This connection enables real-time document synchronization with external services.
|
||||
For example, for Google Drive file synchronization the webhook URL is `/connectors/google_drive/webhook`.
|
||||
|
|
@ -15,40 +15,6 @@ Get started with OpenRAG by loading your knowledge, swapping out your language m
|
|||
## Prerequisites
|
||||
|
||||
- [Install and start OpenRAG](/install)
|
||||
- Create a [Langflow API key](https://docs.langflow.org/api-keys-and-authentication)
|
||||
<details>
|
||||
<summary>Create a Langflow API key</summary>
|
||||
|
||||
A Langflow API key is a user-specific token you can use with Langflow.
|
||||
It is **only** used for sending requests to the Langflow server.
|
||||
It does **not** access to OpenRAG.
|
||||
|
||||
To create a Langflow API key, do the following:
|
||||
|
||||
1. In Langflow, click your user icon, and then select **Settings**.
|
||||
2. Click **Langflow API Keys**, and then click <Icon name="Plus" aria-hidden="true"/> **Add New**.
|
||||
3. Name your key, and then click **Create API Key**.
|
||||
4. Copy the API key and store it securely.
|
||||
5. To use your Langflow API key in a request, set a `LANGFLOW_API_KEY` environment variable in your terminal, and then include an `x-api-key` header or query parameter with your request.
|
||||
For example:
|
||||
|
||||
```bash
|
||||
# Set variable
|
||||
export LANGFLOW_API_KEY="sk..."
|
||||
|
||||
# Send request
|
||||
curl --request POST \
|
||||
--url "http://LANGFLOW_SERVER_ADDRESS/api/v1/run/FLOW_ID" \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "x-api-key: $LANGFLOW_API_KEY" \
|
||||
--data '{
|
||||
"output_type": "chat",
|
||||
"input_type": "chat",
|
||||
"input_value": "Hello"
|
||||
}'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Find your way around
|
||||
|
||||
|
|
@ -99,12 +65,44 @@ You can more quickly access the **Language Model** and **Agent Instructions** fi
|
|||
## Integrate OpenRAG into your application
|
||||
|
||||
To integrate OpenRAG into your application, use the [Langflow API](https://docs.langflow.org/api-reference-api-examples).
|
||||
Make requests with Python, TypeScript, or any HTTP client to run one of OpenRAG's default flows and get a response, and then modify the flow further to improve results.
|
||||
Make requests with Python, TypeScript, or any HTTP client to run one of OpenRAG's default flows and get a response, and then modify the flow further to improve results. Langflow provides code snippets to help you get started.
|
||||
|
||||
Langflow provides code snippets to help you get started with the Langflow API.
|
||||
|
||||
1. To navigate to the OpenRAG OpenSearch Agent flow, click <Icon name="Settings2" aria-hidden="true"/> **Settings**, and then click **Edit in Langflow** in the OpenRAG OpenSearch Agent flow.
|
||||
2. Click **Share**, and then click **API access**.
|
||||
1. Create a [Langflow API key](https://docs.langflow.org/api-keys-and-authentication).
|
||||
<details>
|
||||
<summary>Create a Langflow API key</summary>
|
||||
|
||||
A Langflow API key is a user-specific token you can use with Langflow.
|
||||
It is **only** used for sending requests to the Langflow server.
|
||||
It does **not** access to OpenRAG.
|
||||
|
||||
To create a Langflow API key, do the following:
|
||||
|
||||
1. In Langflow, click your user icon, and then select **Settings**.
|
||||
2. Click **Langflow API Keys**, and then click <Icon name="Plus" aria-hidden="true"/> **Add New**.
|
||||
3. Name your key, and then click **Create API Key**.
|
||||
4. Copy the API key and store it securely.
|
||||
5. To use your Langflow API key in a request, set a `LANGFLOW_API_KEY` environment variable in your terminal, and then include an `x-api-key` header or query parameter with your request.
|
||||
For example:
|
||||
|
||||
```bash
|
||||
# Set variable
|
||||
export LANGFLOW_API_KEY="sk..."
|
||||
|
||||
# Send request
|
||||
curl --request POST \
|
||||
--url "http://LANGFLOW_SERVER_ADDRESS/api/v1/run/FLOW_ID" \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "x-api-key: $LANGFLOW_API_KEY" \
|
||||
--data '{
|
||||
"output_type": "chat",
|
||||
"input_type": "chat",
|
||||
"input_value": "Hello"
|
||||
}'
|
||||
```
|
||||
|
||||
</details>
|
||||
2. To navigate to the OpenRAG OpenSearch Agent flow, click <Icon name="Settings2" aria-hidden="true"/> **Settings**, and then click **Edit in Langflow** in the OpenRAG OpenSearch Agent flow.
|
||||
3. Click **Share**, and then click **API access**.
|
||||
|
||||
The default code in the API access pane constructs a request with the Langflow server `url`, `headers`, and a `payload` of request data. The code snippets automatically include the `LANGFLOW_SERVER_ADDRESS` and `FLOW_ID` values for the flow. Replace these values if you're using the code for a different server or flow. The default Langflow server address is http://localhost:7860.
|
||||
|
||||
|
|
@ -189,7 +187,7 @@ Langflow provides code snippets to help you get started with the Langflow API.
|
|||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
3. Copy the snippet, paste it in a script file, and then run the script to send the request. If you are using the curl snippet, you can run the command directly in your terminal.
|
||||
4. Copy the snippet, paste it in a script file, and then run the script to send the request. If you are using the curl snippet, you can run the command directly in your terminal.
|
||||
|
||||
If the request is successful, the response includes many details about the flow run, including the session ID, inputs, outputs, components, durations, and more.
|
||||
The following is an example of a response from running the **Simple Agent** template flow:
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@ import PartialExternalPreview from '@site/docs/_partial-external-preview.mdx';
|
|||
|
||||
<PartialExternalPreview />
|
||||
|
||||
The OpenRAG Terminal User Interface (TUI) provides a streamlined way to set up, configure, and monitor your OpenRAG deployment directly from the terminal, on any operating system.
|
||||
The OpenRAG Terminal User Interface (TUI) allows you to set up, configure, and monitor your OpenRAG deployment directly from the terminal, on any operating system.
|
||||
|
||||

|
||||
|
||||
The TUI offers an easier way to use OpenRAG without sacrificing control.
|
||||
Instead of starting OpenRAG using Docker commands and manually editing values in the `.env` file, the TUI walks you through the setup. It prompts for variables where required, creates a `.env` file for you, and then starts OpenRAG.
|
||||
|
||||
Once OpenRAG is running, use the TUI to monitor your application, control your containers, and retrieve logs.
|
||||
|
|
@ -19,7 +18,6 @@ Once OpenRAG is running, use the TUI to monitor your application, control your c
|
|||
## Start the TUI
|
||||
|
||||
To start the TUI, run the following commands from the directory where you installed OpenRAG.
|
||||
For more information, see [Install OpenRAG](/install).
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ OpenRAG connects and amplifies three popular, proven open-source projects into o
|
|||
|
||||
* [Docling](https://docling-project.github.io/docling/) - Docling simplifies document processing, parsing diverse formats — including advanced PDF understanding — and providing seamless integrations with the gen AI ecosystem.
|
||||
|
||||
OpenRAG builds on Langflow's familiar interface while adding OpenSearch for vector storage and Docling for simplified document parsing, with opinionated flows that serve as ready-to-use recipes for ingestion, retrieval, and generation from popular sources like OneDrive, Google Drive, and AWS. And don't fear: every part of the stack is swappable. Write your own custom components in Python, try different language models, and customize your flows to build an agentic RAG system that solves problems.
|
||||
OpenRAG builds on Langflow's familiar interface while adding OpenSearch for vector storage and Docling for simplified document parsing, with opinionated flows that serve as ready-to-use recipes for ingestion, retrieval, and generation from popular sources like OneDrive, Google Drive, and AWS.
|
||||
|
||||
Ready to get started? Install OpenRAG and then run the Quickstart to create a powerful RAG pipeline.
|
||||
What's more, every part of the stack is swappable. Write your own custom components in Python, try different language models, and customize your flows to build an agentic RAG system.
|
||||
|
||||
Ready to get started? [Install OpenRAG](/install) and then run the [Quickstart](/quickstart) to create a powerful RAG pipeline.
|
||||
|
|
@ -13,12 +13,12 @@ This page provides troubleshooting advice for issues you might encounter when us
|
|||
|
||||
## OpenSearch fails to start
|
||||
|
||||
Check that `OPENSEARCH_PASSWORD` is set and meets requirements.
|
||||
Check that `OPENSEARCH_PASSWORD` set in [Environment variables](/configure/configuration) meets requirements.
|
||||
The password must contain at least 8 characters, and must contain at least one uppercase letter, one lowercase letter, one digit, and one special character that is strong.
|
||||
|
||||
## Langflow connection issues
|
||||
|
||||
Verify the `LANGFLOW_SUPERUSER` credentials are correct.
|
||||
Verify the `LANGFLOW_SUPERUSER` credentials set in [Environment variables](/configure/configuration) are correct.
|
||||
|
||||
## Memory errors
|
||||
|
||||
|
|
@ -51,57 +51,58 @@ To reset your local containers and pull new images, do the following:
|
|||
|
||||
1. Stop your containers and completely remove them.
|
||||
|
||||
<Tabs groupId="Container software">
|
||||
<TabItem value="Docker" label="Docker" default>
|
||||
|
||||
```bash
|
||||
# Stop all running containers
|
||||
docker stop $(docker ps -q)
|
||||
<Tabs groupId="Container software">
|
||||
<TabItem value="Podman" label="Podman">
|
||||
|
||||
# Remove all containers (including stopped ones)
|
||||
docker rm --force $(docker ps -aq)
|
||||
```bash
|
||||
# Stop all running containers
|
||||
podman stop --all
|
||||
|
||||
# Remove all containers (including stopped ones)
|
||||
podman rm --all --force
|
||||
|
||||
# Remove all images
|
||||
podman rmi --all --force
|
||||
|
||||
# Remove all volumes
|
||||
podman volume prune --force
|
||||
|
||||
# Remove all networks (except default)
|
||||
podman network prune --force
|
||||
|
||||
# Clean up any leftover data
|
||||
podman system prune --all --force --volumes
|
||||
```
|
||||
|
||||
# Remove all images
|
||||
docker rmi --force $(docker images -q)
|
||||
</TabItem>
|
||||
<TabItem value="Docker" label="Docker" default>
|
||||
|
||||
# Remove all volumes
|
||||
docker volume prune --force
|
||||
```bash
|
||||
# Stop all running containers
|
||||
docker stop $(docker ps -q)
|
||||
|
||||
# Remove all containers (including stopped ones)
|
||||
docker rm --force $(docker ps -aq)
|
||||
|
||||
# Remove all images
|
||||
docker rmi --force $(docker images -q)
|
||||
|
||||
# Remove all volumes
|
||||
docker volume prune --force
|
||||
|
||||
# Remove all networks (except default)
|
||||
docker network prune --force
|
||||
|
||||
# Clean up any leftover data
|
||||
docker system prune --all --force --volumes
|
||||
```
|
||||
|
||||
# Remove all networks (except default)
|
||||
docker network prune --force
|
||||
|
||||
# Clean up any leftover data
|
||||
docker system prune --all --force --volumes
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="Podman" label="Podman">
|
||||
|
||||
```bash
|
||||
# Stop all running containers
|
||||
podman stop --all
|
||||
|
||||
# Remove all containers (including stopped ones)
|
||||
podman rm --all --force
|
||||
|
||||
# Remove all images
|
||||
podman rmi --all --force
|
||||
|
||||
# Remove all volumes
|
||||
podman volume prune --force
|
||||
|
||||
# Remove all networks (except default)
|
||||
podman network prune --force
|
||||
|
||||
# Clean up any leftover data
|
||||
podman system prune --all --force --volumes
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
2. Restart OpenRAG and upgrade to get the latest images for your containers.
|
||||
```bash
|
||||
uv sync
|
||||
uv run openrag
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -28,22 +28,22 @@ const sidebars = {
|
|||
{
|
||||
type: "doc",
|
||||
id: "get-started/install",
|
||||
label: "Installation"
|
||||
label: "Install OpenRAG"
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
id: "get-started/docker",
|
||||
label: "Deploy with Docker"
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
id: "get-started/quickstart",
|
||||
label: "Quickstart"
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
id: "get-started/docker",
|
||||
label: "Docker Deployment"
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
id: "get-started/tui",
|
||||
label: "Terminal Interface (TUI)"
|
||||
label: "Terminal User Interface (TUI)"
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,6 +29,34 @@
|
|||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Tabs Styling */
|
||||
.tabs-container {
|
||||
border: 1px solid var(--ifm-color-emphasis-300);
|
||||
border-radius: var(--ifm-global-radius);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tabs__item {
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--ifm-color-emphasis-200);
|
||||
margin-right: 0rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.tabs__item:hover {
|
||||
background-color: var(--ifm-hover-overlay);
|
||||
}
|
||||
|
||||
.tabs__item--active {
|
||||
border-bottom-color: var(--ifm-tabs-color-active);
|
||||
}
|
||||
|
||||
/* GitHub Icon Button */
|
||||
.header-github-link:hover {
|
||||
opacity: 0.6;
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export function FilterIconPopover({
|
|||
type="button"
|
||||
onClick={() => onColorChange(c)}
|
||||
className={cn(
|
||||
"flex items-center justify-center h-6 w-6 rounded-sm transition-colors text-primary",
|
||||
"flex items-center justify-center h-6 w-6 rounded-sm transition-colors text-white",
|
||||
colorSwatchClasses[c]
|
||||
)}
|
||||
aria-label={c}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export function KnowledgeFilterList({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1 px-3 !mb-12 mt-0 h-full overflow-y-auto">
|
||||
<div className="flex flex-col gap-2 px-3 !mb-12 mt-0 h-full overflow-y-auto">
|
||||
<div className="flex items-center w-full justify-between pl-3">
|
||||
<div className="text-sm font-medium text-muted-foreground">
|
||||
Knowledge Filters
|
||||
|
|
@ -76,7 +76,7 @@ export function KnowledgeFilterList({
|
|||
size="sm"
|
||||
onClick={handleCreateNew}
|
||||
title="Create New Filter"
|
||||
className="h-8 px-3 text-muted-foreground"
|
||||
className="!h-8 w-8 px-0 text-muted-foreground"
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
|
|
@ -106,12 +106,14 @@ export function KnowledgeFilterList({
|
|||
<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 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",
|
||||
"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"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { X, Save, RefreshCw } from "lucide-react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { X, RefreshCw } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
|
@ -61,6 +67,8 @@ export function KnowledgeFilterPanel() {
|
|||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [color, setColor] = useState<FilterColor>("zinc");
|
||||
const [iconKey, setIconKey] = useState<IconKey>("filter");
|
||||
const [nameError, setNameError] = useState<string | null>(null);
|
||||
const nameInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Filter configuration states (mirror search page exactly)
|
||||
const [query, setQuery] = useState("");
|
||||
|
|
@ -146,27 +154,12 @@ export function KnowledgeFilterPanel() {
|
|||
// Don't render if panel is closed or we don't have any data
|
||||
if (!isPanelOpen || !parsedFilterData) return null;
|
||||
|
||||
const selectAllFilters = () => {
|
||||
// Use wildcards instead of listing all specific items
|
||||
setSelectedFilters({
|
||||
data_sources: ["*"],
|
||||
document_types: ["*"],
|
||||
owners: ["*"],
|
||||
connector_types: ["*"],
|
||||
});
|
||||
};
|
||||
|
||||
const clearAllFilters = () => {
|
||||
setSelectedFilters({
|
||||
data_sources: [],
|
||||
document_types: [],
|
||||
owners: [],
|
||||
connector_types: [],
|
||||
});
|
||||
};
|
||||
|
||||
const handleSaveConfiguration = async () => {
|
||||
if (!name.trim()) return;
|
||||
if (!name.trim()) {
|
||||
setNameError("Name is required");
|
||||
nameInputRef.current?.focus();
|
||||
return;
|
||||
}
|
||||
const filterData = {
|
||||
query,
|
||||
filters: selectedFilters,
|
||||
|
|
@ -238,8 +231,8 @@ export function KnowledgeFilterPanel() {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="fixed right-0 top-14 bottom-0 w-80 bg-background border-l border-border/40 z-40 overflow-y-auto">
|
||||
<Card className="h-full rounded-none border-0 shadow-lg">
|
||||
<div className="fixed right-0 top-14 bottom-0 w-80 bg-background border-l z-40 overflow-y-auto">
|
||||
<Card className="h-full rounded-none border-0 shadow-lg flex flex-col">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
|
|
@ -248,7 +241,10 @@ export function KnowledgeFilterPanel() {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={closePanelOnly}
|
||||
onClick={() => {
|
||||
setSelectedFilter(null);
|
||||
closePanelOnly();
|
||||
}}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
|
|
@ -260,7 +256,10 @@ export function KnowledgeFilterPanel() {
|
|||
{/* Filter Name and Description */}
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="filter-name">Filter name</Label>
|
||||
<Label htmlFor="filter-name" className="gap-1">
|
||||
Filter name
|
||||
<span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<FilterIconPopover
|
||||
color={color}
|
||||
|
|
@ -271,8 +270,17 @@ export function KnowledgeFilterPanel() {
|
|||
<Input
|
||||
id="filter-name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
setName(v);
|
||||
if (nameError && v.trim()) {
|
||||
setNameError(null);
|
||||
}
|
||||
}}
|
||||
required
|
||||
placeholder="Filter name"
|
||||
ref={nameInputRef}
|
||||
aria-invalid={!!nameError}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -282,13 +290,19 @@ export function KnowledgeFilterPanel() {
|
|||
{formatDate(selectedFilter.created_at)}
|
||||
</div>
|
||||
)}
|
||||
{createMode && (
|
||||
<div className="space-y-2 text-xs text-right text-muted-foreground">
|
||||
<span className="text-placeholder-foreground">Created</span>{" "}
|
||||
{formatDate(new Date().toISOString())}
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="filter-description">Description</Label>
|
||||
<Textarea
|
||||
id="filter-description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Optional description"
|
||||
placeholder="Provide a brief description of your knowledge filter..."
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -301,17 +315,17 @@ export function KnowledgeFilterPanel() {
|
|||
</Label>
|
||||
<Textarea
|
||||
id="search-query"
|
||||
placeholder="e.g., 'financial reports from Q4'"
|
||||
placeholder="Enter your search query..."
|
||||
value={query}
|
||||
className="font-mono placeholder:font-mono"
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
rows={3}
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Filter Dropdowns */}
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">Data Sources</Label>
|
||||
<MultiSelect
|
||||
options={(availableFacets.data_sources || []).map((bucket) => ({
|
||||
value: bucket.key,
|
||||
|
|
@ -322,13 +336,12 @@ export function KnowledgeFilterPanel() {
|
|||
onValueChange={(values) =>
|
||||
handleFilterChange("data_sources", values)
|
||||
}
|
||||
placeholder="Select data sources..."
|
||||
allOptionLabel="All Data Sources"
|
||||
placeholder="Select sources..."
|
||||
allOptionLabel="All sources"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">Document Types</Label>
|
||||
<MultiSelect
|
||||
options={(availableFacets.document_types || []).map(
|
||||
(bucket) => ({
|
||||
|
|
@ -341,13 +354,12 @@ export function KnowledgeFilterPanel() {
|
|||
onValueChange={(values) =>
|
||||
handleFilterChange("document_types", values)
|
||||
}
|
||||
placeholder="Select document types..."
|
||||
allOptionLabel="All Document Types"
|
||||
placeholder="Select types..."
|
||||
allOptionLabel="All types"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">Owners</Label>
|
||||
<MultiSelect
|
||||
options={(availableFacets.owners || []).map((bucket) => ({
|
||||
value: bucket.key,
|
||||
|
|
@ -362,7 +374,6 @@ export function KnowledgeFilterPanel() {
|
|||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">Sources</Label>
|
||||
<MultiSelect
|
||||
options={(availableFacets.connector_types || []).map(
|
||||
(bucket) => ({
|
||||
|
|
@ -375,33 +386,13 @@ export function KnowledgeFilterPanel() {
|
|||
onValueChange={(values) =>
|
||||
handleFilterChange("connector_types", values)
|
||||
}
|
||||
placeholder="Select sources..."
|
||||
allOptionLabel="All Sources"
|
||||
placeholder="Select connectors..."
|
||||
allOptionLabel="All connectors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* All/None buttons */}
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={selectAllFilters}
|
||||
className="h-auto px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 border-border/50"
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={clearAllFilters}
|
||||
className="h-auto px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 border-border/50"
|
||||
>
|
||||
None
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Result Limit Control - exactly like search page */}
|
||||
<div className="space-y-4 pt-4 border-t border-border/50">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium text-nowrap">
|
||||
|
|
@ -462,39 +453,45 @@ export function KnowledgeFilterPanel() {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save Configuration Button */}
|
||||
<div className="flex flex-col gap-3 pt-4 border-t border-border/50">
|
||||
<Button
|
||||
onClick={handleSaveConfiguration}
|
||||
disabled={isSaving}
|
||||
className="w-full"
|
||||
size="sm"
|
||||
>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<RefreshCw className="h-3 w-3 mr-2 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-3 w-3 mr-2" />
|
||||
Save Configuration
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
{!createMode && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
onClick={handleDeleteFilter}
|
||||
>
|
||||
Delete Filter
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="mt-auto align-bottom justify-end gap-2">
|
||||
{/* Save Configuration Button */}
|
||||
{createMode && (
|
||||
<Button
|
||||
onClick={closePanelOnly}
|
||||
disabled={isSaving}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
{!createMode && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={handleDeleteFilter}
|
||||
disabled={isSaving}
|
||||
>
|
||||
Delete Filter
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={handleSaveConfiguration}
|
||||
disabled={isSaving}
|
||||
size="sm"
|
||||
className="relative"
|
||||
>
|
||||
{isSaving && (
|
||||
<>
|
||||
<RefreshCw className="h-3 w-3 animate-spin" />
|
||||
Saving...
|
||||
</>
|
||||
)}
|
||||
{!isSaving && (createMode ? "Create Filter" : "Update Filter")}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const buttonVariants = cva(
|
|||
"border border-input hover:bg-muted hover:text-accent-foreground disabled:bg-muted disabled:!border-none",
|
||||
primary:
|
||||
"border bg-background text-secondary-foreground hover:bg-muted hover:shadow-sm",
|
||||
warning: "bg-warning text-secondary hover:bg-warning/90",
|
||||
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
|
||||
secondary:
|
||||
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
|
||||
ghost:
|
||||
|
|
|
|||
|
|
@ -1,39 +1,39 @@
|
|||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import { ChevronDown, Check } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import * as React from "react";
|
||||
import { ChevronDown, Check } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command"
|
||||
} from "@/components/ui/command";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
} from "@/components/ui/popover";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
|
||||
interface Option {
|
||||
value: string
|
||||
label: string
|
||||
count?: number
|
||||
value: string;
|
||||
label: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
interface MultiSelectProps {
|
||||
options: Option[]
|
||||
value: string[]
|
||||
onValueChange: (value: string[]) => void
|
||||
placeholder?: string
|
||||
className?: string
|
||||
maxSelection?: number
|
||||
searchPlaceholder?: string
|
||||
showAllOption?: boolean
|
||||
allOptionLabel?: string
|
||||
options: Option[];
|
||||
value: string[];
|
||||
onValueChange: (value: string[]) => void;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
maxSelection?: number;
|
||||
searchPlaceholder?: string;
|
||||
showAllOption?: boolean;
|
||||
allOptionLabel?: string;
|
||||
}
|
||||
|
||||
export function MultiSelect({
|
||||
|
|
@ -43,60 +43,61 @@ export function MultiSelect({
|
|||
placeholder = "Select items...",
|
||||
className,
|
||||
maxSelection,
|
||||
searchPlaceholder = "Search...",
|
||||
searchPlaceholder = "Search options...",
|
||||
showAllOption = true,
|
||||
allOptionLabel = "All"
|
||||
allOptionLabel = "All",
|
||||
}: MultiSelectProps) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [searchValue, setSearchValue] = React.useState("")
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [searchValue, setSearchValue] = React.useState("");
|
||||
|
||||
const isAllSelected = value.includes("*")
|
||||
|
||||
const filteredOptions = options.filter(option =>
|
||||
const isAllSelected = value.includes("*");
|
||||
|
||||
const filteredOptions = options.filter((option) =>
|
||||
option.label.toLowerCase().includes(searchValue.toLowerCase())
|
||||
)
|
||||
);
|
||||
|
||||
const handleSelect = (optionValue: string) => {
|
||||
if (optionValue === "*") {
|
||||
// Toggle "All" selection
|
||||
if (isAllSelected) {
|
||||
onValueChange([])
|
||||
onValueChange([]);
|
||||
} else {
|
||||
onValueChange(["*"])
|
||||
onValueChange(["*"]);
|
||||
}
|
||||
} else {
|
||||
let newValue: string[]
|
||||
let newValue: string[];
|
||||
if (value.includes(optionValue)) {
|
||||
// Remove the item
|
||||
newValue = value.filter(v => v !== optionValue && v !== "*")
|
||||
newValue = value.filter((v) => v !== optionValue && v !== "*");
|
||||
} else {
|
||||
// Add the item and remove "All" if present
|
||||
newValue = [...value.filter(v => v !== "*"), optionValue]
|
||||
|
||||
newValue = [...value.filter((v) => v !== "*"), optionValue];
|
||||
|
||||
// Check max selection limit
|
||||
if (maxSelection && newValue.length > maxSelection) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
}
|
||||
onValueChange(newValue)
|
||||
onValueChange(newValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getDisplayText = () => {
|
||||
if (isAllSelected) {
|
||||
return allOptionLabel
|
||||
return allOptionLabel;
|
||||
}
|
||||
|
||||
|
||||
if (value.length === 0) {
|
||||
return placeholder
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
|
||||
// Extract the noun from placeholder (e.g., "Select data sources..." -> "data sources")
|
||||
const noun = placeholder.toLowerCase().replace('select ', '').replace('...', '')
|
||||
return `${value.length} ${noun}`
|
||||
}
|
||||
|
||||
|
||||
const noun = placeholder
|
||||
.toLowerCase()
|
||||
.replace("select ", "")
|
||||
.replace("...", "");
|
||||
return `${value.length} ${noun}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
|
|
@ -106,17 +107,15 @@ export function MultiSelect({
|
|||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className={cn(
|
||||
"w-full justify-between min-h-[40px] h-auto text-left",
|
||||
"w-full justify-between h-8 py-0 text-left",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<span className="text-foreground text-sm">
|
||||
{getDisplayText()}
|
||||
</span>
|
||||
<span className="text-foreground text-sm">{getDisplayText()}</span>
|
||||
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-full p-0" align="start">
|
||||
<PopoverContent className="p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder={searchPlaceholder}
|
||||
|
|
@ -132,16 +131,13 @@ export function MultiSelect({
|
|||
onSelect={() => handleSelect("*")}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span className="flex-1">{allOptionLabel}</span>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
isAllSelected ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
<span className="flex-1">{allOptionLabel}</span>
|
||||
<span className="text-xs text-blue-500 bg-blue-500/10 px-1.5 py-0.5 rounded ml-2">
|
||||
*
|
||||
</span>
|
||||
</CommandItem>
|
||||
)}
|
||||
{filteredOptions.map((option) => (
|
||||
|
|
@ -149,20 +145,19 @@ export function MultiSelect({
|
|||
key={option.value}
|
||||
onSelect={() => handleSelect(option.value)}
|
||||
className="cursor-pointer"
|
||||
disabled={isAllSelected}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
value.includes(option.value) ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
<span className="flex-1">{option.label}</span>
|
||||
{option.count !== undefined && (
|
||||
<span className="text-xs text-muted-foreground bg-muted/50 px-1.5 py-0.5 rounded ml-2">
|
||||
{option.count}
|
||||
</span>
|
||||
)}
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
value.includes(option.value) ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))}
|
||||
</ScrollArea>
|
||||
|
|
@ -170,5 +165,5 @@ export function MultiSelect({
|
|||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ const Slider = React.forwardRef<
|
|||
)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-muted/40">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
||||
<SliderPrimitive.Track className="relative h-[5px] w-full grow overflow-hidden rounded-full bg-muted">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-muted-foreground" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
))
|
||||
Slider.displayName = SliderPrimitive.Root.displayName
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
"primary-input placeholder:font-mono placeholder:text-placeholder-foreground min-h-fit",
|
||||
"primary-input placeholder:text-placeholder-foreground min-h-fit",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
9
frontend/package-lock.json
generated
9
frontend/package-lock.json
generated
|
|
@ -28,7 +28,6 @@
|
|||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/react-query": "^5.86.0",
|
||||
"ag-grid-community": "^34.2.0",
|
||||
|
|
@ -2318,14 +2317,6 @@
|
|||
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/line-clamp": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.4.4.tgz",
|
||||
"integrity": "sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@
|
|||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/react-query": "^5.86.0",
|
||||
"ag-grid-community": "^34.2.0",
|
||||
|
|
|
|||
|
|
@ -25,27 +25,28 @@
|
|||
--muted-foreground: 240 4% 46%;
|
||||
--accent: 240 5% 96%;
|
||||
--accent-foreground: 0 0% 0%;
|
||||
--destructive: 0 72% 51%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--warning: 48, 96%, 89%;
|
||||
--warning-foreground: 26, 90%, 37%;
|
||||
--border: 240 4.8% 95.9%;
|
||||
--input: 240 6% 90%;
|
||||
--ring: 0 0% 0%;
|
||||
--placeholder-foreground: 240 5% 65%;
|
||||
|
||||
--accent-amber: 48, 96%, 89%; /* amber-100 #fef3c7 */
|
||||
--accent-amber-foreground: 26, 90%, 37%; /* amber-700 #b45309 */
|
||||
--accent-emerald: 149, 80%, 90%; /* emerald-100 #d1fae5 */
|
||||
--accent-emerald-foreground: 161, 94%, 30%; /* emerald-600 #059669 */
|
||||
--accent-red: 0, 93%, 94%; /* red-100 #fee2e2 */
|
||||
--accent-red-foreground: 0, 72%, 51%; /* red-600 #dc2626 */
|
||||
--accent-indigo: 226, 100%, 94%; /* indigo-100 #e0e7ff */
|
||||
--accent-indigo-foreground: 243, 75%, 59%; /* indigo-600 #4f46e5 */
|
||||
--accent-pink: 326, 78%, 95%; /* pink-100 #fce7f3 */
|
||||
--accent-pink-foreground: 333, 71%, 51%; /* pink-600 #db2777 */
|
||||
--accent-purple: 269, 100%, 95%; /* purple-100 #f3e8ff */
|
||||
--accent-purple-foreground: 271, 81%, 56%; /* purple-600 #7c3aed */
|
||||
--accent-amber: 48 96% 89%; /* amber-100 #fef3c7 */
|
||||
--accent-amber-foreground: 26 90% 37%; /* amber-700 #b45309 */
|
||||
--accent-emerald: 149 80% 90%; /* emerald-100 #d1fae5 */
|
||||
--accent-emerald-foreground: 161 94% 30%; /* emerald-600 #059669 */
|
||||
--accent-red: 0 93% 94%; /* red-100 #fee2e2 */
|
||||
--accent-red-foreground: 0 72% 51%; /* red-600 #dc2626 */
|
||||
--accent-indigo: 226 100% 94%; /* indigo-100 #e0e7ff */
|
||||
--accent-indigo-foreground: 243 75% 59%; /* indigo-600 #4f46e5 */
|
||||
--accent-pink: 326 78% 95%; /* pink-100 #fce7f3 */
|
||||
--accent-pink-foreground: 333 71% 51%; /* pink-600 #db2777 */
|
||||
--accent-purple: 269 100% 95%; /* purple-100 #f3e8ff */
|
||||
--accent-purple-foreground: 271 81% 56%; /* purple-600 #7c3aed */
|
||||
|
||||
--destructive: 0 72% 51%; /* red-600 #dc2626 */
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--warning: 26 90% 37%; /* amber-700 #b45309 */
|
||||
--warning-foreground: 0 0% 100%;
|
||||
|
||||
/* Component Colors */
|
||||
--component-icon: #d8598a;
|
||||
|
|
@ -71,27 +72,28 @@
|
|||
--muted-foreground: 240 5% 65%;
|
||||
--accent: 240 4% 16%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
--destructive: 0 84% 60%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--warning: 22, 78%, 26%;
|
||||
--warning-foreground: 46, 97%, 65%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 5% 34%;
|
||||
--ring: 0 0% 100%;
|
||||
--placeholder-foreground: 240 4% 46%;
|
||||
|
||||
--accent-amber: 22, 78%, 26%; /* amber-900 #78350f */
|
||||
--accent-amber-foreground: 46, 97%, 65%; /* amber-300 #fcd34d */
|
||||
--accent-emerald: 164, 86%, 16%; /* emerald-900 #064e3b */
|
||||
--accent-emerald-foreground: 158, 64%, 52%; /* emerald-400 #34d399 */
|
||||
--accent-red: 0, 63%, 31%; /* red-900 #7f1d1d */
|
||||
--accent-red-foreground: 0, 91%, 71%; /* red-400 #f87171 */
|
||||
--accent-indigo: 242, 47%, 34%; /* indigo-900 #312e81 */
|
||||
--accent-indigo-foreground: 234, 89%, 74%; /* indigo-400 #818cf8 */
|
||||
--accent-pink: 336, 69%, 30%; /* pink-900 #831843 */
|
||||
--accent-pink-foreground: 329, 86%, 70%; /* pink-400 #f472b6 */
|
||||
--accent-purple: 274, 66%, 32%; /* purple-900 #4c1d95 */
|
||||
--accent-purple-foreground: 270, 95%, 75%; /* purple-400 #a78bfa */
|
||||
--accent-amber: 22 78% 26%; /* amber-900 #78350f */
|
||||
--accent-amber-foreground: 46 97% 65%; /* amber-300 #fcd34d */
|
||||
--accent-emerald: 164 86% 16%; /* emerald-900 #064e3b */
|
||||
--accent-emerald-foreground: 158 64% 52%; /* emerald-400 #34d399 */
|
||||
--accent-red: 0 63% 31%; /* red-900 #7f1d1d */
|
||||
--accent-red-foreground: 0 91% 71%; /* red-400 #f87171 */
|
||||
--accent-indigo: 242 47% 34%; /* indigo-900 #312e81 */
|
||||
--accent-indigo-foreground: 234 89% 74%; /* indigo-400 #818cf8 */
|
||||
--accent-pink: 336 69% 30%; /* pink-900 #831843 */
|
||||
--accent-pink-foreground: 329 86% 70%; /* pink-400 #f472b6 */
|
||||
--accent-purple: 274 66% 32%; /* purple-900 #4c1d95 */
|
||||
--accent-purple-foreground: 270 95% 75%; /* purple-400 #a78bfa */
|
||||
|
||||
--destructive: 0 84% 60%; /* red-500 #ef4444 */
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--warning: 46 97% 65%; /* amber-300 #fcd34d */
|
||||
--warning-foreground: 0 0% 0%;
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ function SearchPage() {
|
|||
};
|
||||
|
||||
// Convert TaskFiles to File format and merge with backend results
|
||||
const taskFilesAsFiles: File[] = taskFiles.map(taskFile => {
|
||||
const taskFilesAsFiles: File[] = taskFiles.map((taskFile) => {
|
||||
return {
|
||||
filename: taskFile.filename,
|
||||
mimetype: taskFile.mimetype,
|
||||
|
|
@ -77,11 +77,11 @@ function SearchPage() {
|
|||
|
||||
const backendFiles = data as File[];
|
||||
|
||||
const filteredTaskFiles = taskFilesAsFiles.filter(taskFile => {
|
||||
const filteredTaskFiles = taskFilesAsFiles.filter((taskFile) => {
|
||||
return (
|
||||
taskFile.status !== "active" &&
|
||||
!backendFiles.some(
|
||||
backendFile => backendFile.filename === taskFile.filename
|
||||
(backendFile) => backendFile.filename === taskFile.filename
|
||||
)
|
||||
);
|
||||
});
|
||||
|
|
@ -123,7 +123,7 @@ function SearchPage() {
|
|||
{
|
||||
field: "size",
|
||||
headerName: "Size",
|
||||
valueFormatter: params =>
|
||||
valueFormatter: (params) =>
|
||||
params.value ? `${Math.round(params.value / 1024)} KB` : "-",
|
||||
},
|
||||
{
|
||||
|
|
@ -133,13 +133,13 @@ function SearchPage() {
|
|||
{
|
||||
field: "owner",
|
||||
headerName: "Owner",
|
||||
valueFormatter: params =>
|
||||
valueFormatter: (params) =>
|
||||
params.data?.owner_name || params.data?.owner_email || "—",
|
||||
},
|
||||
{
|
||||
field: "chunkCount",
|
||||
headerName: "Chunks",
|
||||
valueFormatter: params => params.data?.chunkCount?.toString() || "-",
|
||||
valueFormatter: (params) => params.data?.chunkCount?.toString() || "-",
|
||||
},
|
||||
{
|
||||
field: "avgScore",
|
||||
|
|
@ -201,7 +201,7 @@ function SearchPage() {
|
|||
|
||||
try {
|
||||
// Delete each file individually since the API expects one filename at a time
|
||||
const deletePromises = selectedRows.map(row =>
|
||||
const deletePromises = selectedRows.map((row) =>
|
||||
deleteDocumentMutation.mutateAsync({ filename: row.filename })
|
||||
);
|
||||
|
||||
|
|
@ -255,7 +255,7 @@ function SearchPage() {
|
|||
<div className="primary-input min-h-10 !flex items-center flex-nowrap focus-within:border-foreground transition-colors !p-[0.3rem]">
|
||||
{selectedFilter?.name && (
|
||||
<div
|
||||
className={`flex items-center gap-1 h-full px-1.5 py-0.5 rounded max-w-[300px] ${
|
||||
className={`flex items-center gap-1 h-full px-1.5 py-0.5 mr-1 rounded max-w-[25%] ${
|
||||
filterAccentClasses[parsedFilterData?.color || "zinc"]
|
||||
}`}
|
||||
>
|
||||
|
|
@ -267,8 +267,12 @@ function SearchPage() {
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
<Search
|
||||
className="h-4 w-4 ml-1 flex-shrink-0 text-placeholder-foreground"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<input
|
||||
className="bg-transparent w-full h-full ml-2 focus:outline-none focus-visible:outline-none placeholder:font-mono"
|
||||
className="bg-transparent w-full h-full ml-2 focus:outline-none focus-visible:outline-none font-mono placeholder:font-mono"
|
||||
name="search-query"
|
||||
id="search-query"
|
||||
type="text"
|
||||
|
|
@ -318,7 +322,7 @@ function SearchPage() {
|
|||
rowSelection="multiple"
|
||||
rowMultiSelectWithClick={false}
|
||||
suppressRowClickSelection={true}
|
||||
getRowId={params => params.data.filename}
|
||||
getRowId={(params) => params.data.filename}
|
||||
domLayout="normal"
|
||||
onSelectionChanged={onSelectionChanged}
|
||||
noRowsOverlayComponent={() => (
|
||||
|
|
@ -346,7 +350,7 @@ function SearchPage() {
|
|||
}? This will remove all chunks and data associated with these documents. This action cannot be undone.
|
||||
|
||||
Documents to be deleted:
|
||||
${selectedRows.map(row => `• ${row.filename}`).join("\n")}`}
|
||||
${selectedRows.map((row) => `• ${row.filename}`).join("\n")}`}
|
||||
confirmText="Delete All"
|
||||
onConfirm={handleBulkDelete}
|
||||
isLoading={deleteDocumentMutation.isPending}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import tailwindcssTypography from "@tailwindcss/typography";
|
|||
import { fontFamily } from "tailwindcss/defaultTheme";
|
||||
import plugin from "tailwindcss/plugin";
|
||||
import tailwindcssAnimate from "tailwindcss-animate";
|
||||
import tailwindcssLineClamp from "@tailwindcss/line-clamp";
|
||||
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
|
|
@ -167,7 +166,6 @@ const config = {
|
|||
},
|
||||
plugins: [
|
||||
tailwindcssAnimate,
|
||||
tailwindcssLineClamp,
|
||||
tailwindcssForms({
|
||||
strategy: "class",
|
||||
}),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue