Merge branch 'main' into remove-sync-connectors
This commit is contained in:
commit
dbf2d2b47b
38 changed files with 1197 additions and 679 deletions
|
|
@ -19,11 +19,8 @@ jobs:
|
|||
runs-on: ${{ matrix.runs-on }}
|
||||
|
||||
steps:
|
||||
- name: Checkout langflow load_flows_autologin_false branch
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: langflow-ai/langflow
|
||||
ref: load_flows_autologin_false
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
|
@ -38,6 +35,7 @@ jobs:
|
|||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.langflow
|
||||
platforms: ${{ matrix.platform }}
|
||||
push: true
|
||||
tags: phact/langflow:responses-${{ matrix.arch }}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ podman machine start
|
|||
|
||||
### Common Issues
|
||||
|
||||
See common issues and fixes: [docs/reference/troubleshooting.mdx](docs/docs/reference/troubleshooting.mdx)
|
||||
See common issues and fixes: [docs/support/troubleshoot.mdx](docs/docs/reference/troubleshoot.mdx)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
111
docs/VERSIONING_SETUP.md
Normal file
111
docs/VERSIONING_SETUP.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# Docusaurus versioning setup
|
||||
|
||||
Docs versioning is currently **DISABLED** but configured and ready to enable.
|
||||
The configuration is found in `docusaurus.config.js` with commented-out sections.
|
||||
|
||||
To enable versioning, do the following:
|
||||
|
||||
1. Open `docusaurus.config.js`
|
||||
2. Find the versioning configuration section (around line 57)
|
||||
3. Uncomment the versioning configuration:
|
||||
|
||||
```javascript
|
||||
docs: {
|
||||
// ... other config
|
||||
lastVersion: 'current', // Use 'current' to make ./docs the latest version
|
||||
versions: {
|
||||
current: {
|
||||
label: 'Next (unreleased)',
|
||||
path: 'next',
|
||||
},
|
||||
},
|
||||
onlyIncludeVersions: ['current'], // Limit versions for faster builds
|
||||
},
|
||||
```
|
||||
|
||||
## Create docs versions
|
||||
|
||||
See the [Docusaurus docs](https://docusaurus.io/docs/versioning) for more info.
|
||||
|
||||
1. Use the Docusaurus CLI command to create a version.
|
||||
You can use `yarn` instead of `npm`.
|
||||
```bash
|
||||
# Create version 1.0.0 from current docs
|
||||
npm run docusaurus docs:version 1.0.0
|
||||
```
|
||||
|
||||
This command will:
|
||||
- Copy the full `docs/` folder contents into `versioned_docs/version-1.0.0/`
|
||||
- Create a versioned sidebar file at `versioned_sidebars/version-1.0.0-sidebars.json`
|
||||
- Append the new version to `versions.json`
|
||||
|
||||
3. After creating a version, update the Docusaurus configuration to include multiple versions.
|
||||
`lastVersion:'1.0.0'` makes the '1.0.0' release the `latest` version.
|
||||
`current` is the work-in-progress docset, accessible at `/docs/next`.
|
||||
To remove a version, remove it from `onlyIncludeVersions`.
|
||||
|
||||
```javascript
|
||||
docs: {
|
||||
// ... other config
|
||||
lastVersion: '1.0.0', // Make 1.0.0 the latest version
|
||||
versions: {
|
||||
current: {
|
||||
label: 'Next (unreleased)',
|
||||
path: 'next',
|
||||
},
|
||||
'1.0.0': {
|
||||
label: '1.0.0',
|
||||
path: '1.0.0',
|
||||
},
|
||||
},
|
||||
onlyIncludeVersions: ['current', '1.0.0'], // Include both versions
|
||||
},
|
||||
```
|
||||
|
||||
4. Test the deployment locally.
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm run serve
|
||||
```
|
||||
|
||||
5. To add subsequent versions, repeat the process, first running the CLI command then updating `docusaurus.config.js`.
|
||||
|
||||
```bash
|
||||
# Create version 2.0.0 from current docs
|
||||
npm run docusaurus docs:version 2.0.0
|
||||
```
|
||||
|
||||
After creating a new version, update `docusaurus.config.js`.
|
||||
|
||||
```javascript
|
||||
docs: {
|
||||
lastVersion: '2.0.0', // Make 2.0.0 the latest version
|
||||
versions: {
|
||||
current: {
|
||||
label: 'Next (unreleased)',
|
||||
path: 'next',
|
||||
},
|
||||
'2.0.0': {
|
||||
label: '2.0.0',
|
||||
path: '2.0.0',
|
||||
},
|
||||
'1.0.0': {
|
||||
label: '1.0.0',
|
||||
path: '1.0.0',
|
||||
},
|
||||
},
|
||||
onlyIncludeVersions: ['current', '2.0.0', '1.0.0'], // Include all versions
|
||||
},
|
||||
```
|
||||
|
||||
## Disable versioning
|
||||
|
||||
1. Remove the `versions` configuration from `docusaurus.config.js`.
|
||||
2. Delete the `docs/versioned_docs/` and `docs/versioned_sidebars/` directories.
|
||||
3. Delete `docs/versions.json`.
|
||||
|
||||
## References
|
||||
|
||||
- [Official Docusaurus Versioning Documentation](https://docusaurus.io/docs/versioning)
|
||||
- [Docusaurus Versioning Best Practices](https://docusaurus.io/docs/versioning#recommended-practices)
|
||||
50
docs/docs/core-components/ingestion.mdx
Normal file
50
docs/docs/core-components/ingestion.mdx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
title: Docling Ingestion
|
||||
slug: /ingestion
|
||||
---
|
||||
|
||||
import Icon from "@site/src/components/icon/icon";
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import PartialModifyFlows from '@site/docs/_partial-modify-flows.mdx';
|
||||
|
||||
OpenRAG uses [Docling](https://docling-project.github.io/docling/) for its document ingestion pipeline.
|
||||
More specifically, OpenRAG uses [Docling Serve](https://github.com/docling-project/docling-serve), which starts a `docling-serve` process on your local machine and runs Docling ingestion through an API service.
|
||||
|
||||
Docling ingests documents from your local machine or OAuth connectors, splits them into chunks, and stores them as separate, structured documents in the OpenSearch `documents` index.
|
||||
|
||||
OpenRAG chose Docling for its support for a wide variety of file formats, high performance, and advanced understanding of tables and images.
|
||||
|
||||
## Docling ingestion settings
|
||||
|
||||
These settings configure the Docling ingestion parameters.
|
||||
|
||||
OpenRAG will warn you if `docling-serve` is not running.
|
||||
To start or stop `docling-serve` or any other native services, in the TUI main menu, click **Start Native Services** or **Stop Native Services**.
|
||||
|
||||
**Embedding model** determines which AI model is used to create vector embeddings. The default is `text-embedding-3-small`.
|
||||
|
||||
**Chunk size** determines how large each text chunk is in number of characters.
|
||||
Larger chunks yield more context per chunk, but may include irrelevant information. Smaller chunks yield more precise semantic search, but may lack context.
|
||||
The default value of `1000` characters provides a good starting point that balances these considerations.
|
||||
|
||||
**Chunk overlap** controls the number of characters that overlap over chunk boundaries.
|
||||
Use larger overlap values for documents where context is most important, and use smaller overlap values for simpler documents, or when optimization is most important.
|
||||
The default value of 200 characters of overlap with a chunk size of 1000 (20% overlap) is suitable for general use cases. Decrease the overlap to 10% for a more efficient pipeline, or increase to 40% for more complex documents.
|
||||
|
||||
**OCR** enables or disabled OCR processing when extracting text from images and scanned documents.
|
||||
OCR is disabled by default. This setting is best suited for processing text-based documents as quickly as possible with Docling's [`DocumentConverter`](https://docling-project.github.io/docling/reference/document_converter/). Images are ignored and not processed.
|
||||
|
||||
Enable OCR when you are processing documents containing images with text that requires extraction, or for scanned documents. Enabling OCR can slow ingestion performance.
|
||||
|
||||
If OpenRAG detects that the local machine is running on macOS, OpenRAG uses the [ocrmac](https://www.piwheels.org/project/ocrmac/) OCR engine. Other platforms use [easyocr](https://www.jaided.ai/easyocr/).
|
||||
|
||||
**Picture descriptions** adds image descriptions generated by the [SmolVLM-256M-Instruct](https://huggingface.co/HuggingFaceTB/SmolVLM-Instruct) model to OCR processing. Enabling picture descriptions can slow ingestion performance.
|
||||
|
||||
## Use OpenRAG default ingestion instead of Docling serve
|
||||
|
||||
If you want to use OpenRAG's built-in pipeline instead of Docling serve, set `DISABLE_INGEST_WITH_LANGFLOW=true` in [Environment variables](/configure/configuration#ingestion-configuration).
|
||||
|
||||
The built-in pipeline still uses the Docling processor, but uses it directly without the Docling Serve API.
|
||||
|
||||
For more information, see [`processors.py` in the OpenRAG repository](https://github.com/langflow-ai/openrag/blob/main/src/models/processors.py#L58).
|
||||
|
|
@ -97,6 +97,10 @@ 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.
|
||||
|
||||
### Knowledge ingestion settings
|
||||
|
||||
To configure the knowledge ingestion pipeline parameters, see [Docling Ingestion](/ingestion).
|
||||
|
||||
## Create knowledge filters
|
||||
|
||||
OpenRAG includes a knowledge filter system for organizing and managing document collections.
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
title: Troubleshooting
|
||||
slug: /reference/troubleshooting
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## Podman on macOS
|
||||
|
||||
If using Podman on macOS, you may need to increase VM memory:
|
||||
|
||||
```bash
|
||||
podman machine stop
|
||||
podman machine rm
|
||||
podman machine init --memory 8192 # 8 GB example
|
||||
podman machine start
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
1. OpenSearch fails to start: Check that `OPENSEARCH_PASSWORD` is set and meets requirements
|
||||
2. Langflow connection issues: Verify `LANGFLOW_SUPERUSER` credentials are correct
|
||||
3. Out of memory errors: Increase Docker memory allocation or use CPU-only mode
|
||||
4. Port conflicts: Ensure ports 3000, 7860, 8000, 9200, 5601 are available
|
||||
107
docs/docs/support/troubleshoot.mdx
Normal file
107
docs/docs/support/troubleshoot.mdx
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
---
|
||||
title: Troubleshoot
|
||||
slug: /support/troubleshoot
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
This page provides troubleshooting advice for issues you might encounter when using OpenRAG or contributing to OpenRAG.
|
||||
|
||||
## OpenSearch fails to start
|
||||
|
||||
Check that `OPENSEARCH_PASSWORD` is set and 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.
|
||||
|
||||
## Memory errors
|
||||
|
||||
### Container out of memory errors
|
||||
|
||||
Increase Docker memory allocation or use [docker-compose-cpu.yml](https://github.com/langflow-ai/openrag/blob/main/docker-compose-cpu.yml) to deploy OpenRAG.
|
||||
|
||||
### Podman on macOS memory issues
|
||||
|
||||
If you're using Podman on macOS, you may need to increase VM memory on your Podman machine.
|
||||
This example increases the machine size to 8 GB of RAM, which should be sufficient to run OpenRAG.
|
||||
```bash
|
||||
podman machine stop
|
||||
podman machine rm
|
||||
podman machine init --memory 8192 # 8 GB example
|
||||
podman machine start
|
||||
```
|
||||
|
||||
## Port conflicts
|
||||
|
||||
Ensure ports 3000, 7860, 8000, 9200, 5601 are available.
|
||||
|
||||
## Langflow container already exists
|
||||
|
||||
If you are running other versions of Langflow containers on your machine, you may encounter an issue where Docker or Podman thinks Langflow is already up.
|
||||
|
||||
Remove just the problem container, or clean up all containers and start fresh.
|
||||
|
||||
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)
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
</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>
|
||||
|
||||
2. Restart OpenRAG and upgrade to get the latest images for your containers.
|
||||
```bash
|
||||
uv run openrag
|
||||
```
|
||||
|
||||
3. In the OpenRAG TUI, click **Status**, and then click **Upgrade**.
|
||||
When the **Close** button is active, the upgrade is complete.
|
||||
Close the window and open the OpenRAG appplication.
|
||||
|
|
@ -53,6 +53,16 @@ const config = {
|
|||
editUrl:
|
||||
'https://github.com/openrag/openrag/tree/main/docs/',
|
||||
routeBasePath: '/',
|
||||
// Versioning configuration - see VERSIONING_SETUP.md
|
||||
// To enable versioning, uncomment the following lines:
|
||||
// lastVersion: 'current',
|
||||
// versions: {
|
||||
// current: {
|
||||
// label: 'Next (unreleased)',
|
||||
// path: 'next',
|
||||
// },
|
||||
// },
|
||||
// onlyIncludeVersions: ['current'],
|
||||
},
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
|
|
@ -65,12 +75,13 @@ const config = {
|
|||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||
({
|
||||
// Replace with your project's social card
|
||||
image: 'img/docusaurus-social-card.jpg',
|
||||
// image: 'img/docusaurus-social-card.jpg',
|
||||
navbar: {
|
||||
title: 'OpenRAG',
|
||||
// title: 'OpenRAG',
|
||||
logo: {
|
||||
alt: 'OpenRAG Logo',
|
||||
src: 'img/logo.svg',
|
||||
src: "img/logo-openrag-light.svg",
|
||||
srcDark: "img/logo-openrag-dark.svg",
|
||||
href: '/',
|
||||
},
|
||||
items: [
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ const sidebars = {
|
|||
type: "doc",
|
||||
id: "core-components/knowledge",
|
||||
label: "OpenSearch Knowledge"
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
id: "core-components/ingestion",
|
||||
label: "Docling Ingestion"
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
@ -76,12 +81,12 @@ const sidebars = {
|
|||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Reference",
|
||||
label: "Support",
|
||||
items: [
|
||||
{
|
||||
type: "doc",
|
||||
id: "reference/troubleshooting",
|
||||
label: "Troubleshooting"
|
||||
id: "support/troubleshoot",
|
||||
label: "Troubleshoot"
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
16
docs/static/img/logo-openrag-dark.svg
vendored
Normal file
16
docs/static/img/logo-openrag-dark.svg
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<svg width="1335" height="185" viewBox="0 0 1335 185" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M172.336 97.5947H201.395C207.095 97.5948 211.713 102.212 211.713 107.912V125.055C211.713 130.755 207.095 135.372 201.395 135.372H176.05C173.312 135.372 170.687 136.462 168.752 138.397L125.259 181.883C123.324 183.819 120.699 184.908 117.961 184.908H97.0534C91.4612 184.908 86.8796 180.449 86.736 174.856L86.2848 157.354C86.1343 151.554 90.7947 146.765 96.6022 146.765H114.598C117.337 146.765 119.961 145.675 121.897 143.739L165.023 100.613C166.959 98.6775 169.583 97.5879 172.322 97.5879L172.336 97.5947Z" fill="white"/>
|
||||
<path d="M201.395 22.083C207.095 22.083 211.713 26.7004 211.713 32.4004V49.543C211.713 55.2429 207.095 59.8604 201.395 59.8604H176.05C173.312 59.8604 170.687 60.95 168.752 62.8857L125.259 106.378C123.324 108.314 120.699 109.403 117.961 109.403H92.5582C89.8983 109.403 87.339 110.429 85.4176 112.271L36.5914 159.061C34.6699 160.903 32.1098 161.929 29.4498 161.929H11.6549C5.95497 161.929 1.33753 157.304 1.33753 151.611V133.995C1.33777 128.295 5.95512 123.679 11.6549 123.679H29.3209C32.0598 123.679 34.6839 122.588 36.6198 120.652L82.9869 74.2861C84.9228 72.3503 87.5469 71.2598 90.2858 71.2598H114.598C117.337 71.2598 119.961 70.1702 121.897 68.2344L165.023 25.1084C166.959 23.1726 169.583 22.083 172.322 22.083H201.395Z" fill="white"/>
|
||||
<path d="M115.114 0C120.814 0 125.431 4.61743 125.431 10.3174V27.46C125.431 33.1599 120.814 37.7773 115.114 37.7773H89.7692C87.0304 37.7773 84.4062 38.867 82.4703 40.8027L38.9782 84.2949C37.0423 86.2306 34.418 87.3203 31.6793 87.3203H10.7731C5.1807 87.3203 0.599138 82.8609 0.455697 77.2686L0.0035481 59.7676C-0.147015 53.9673 4.5135 49.1847 10.3209 49.1846H28.317C31.0558 49.1846 33.68 48.0949 35.6159 46.1592L78.7418 3.0332C80.6776 1.09742 83.3019 0.00689882 86.0407 0.00683594L86.0553 0H115.114Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M445.716 55.0117C452.121 55.0117 457.897 56.6483 463.043 59.9229C468.225 63.1614 472.327 67.9658 475.35 74.335C478.408 80.6683 479.937 88.4412 479.938 97.6533C479.938 106.757 478.445 114.494 475.458 120.863C472.471 127.233 468.405 132.091 463.259 135.438C458.113 138.784 452.284 140.457 445.771 140.457C441.021 140.457 437.08 139.665 433.949 138.082C430.819 136.499 428.3 134.591 426.393 132.36C424.521 130.093 423.064 127.953 422.021 125.938H421.211V170.091H401.671V56.0908H420.887V69.8008H422.021C423.028 67.7858 424.449 65.6448 426.284 63.3779C428.119 61.0749 430.603 59.1135 433.733 57.4941C436.864 55.8389 440.858 55.0118 445.716 55.0117ZM440.318 71.043C436.144 71.043 432.599 72.1399 429.685 74.335C426.77 76.53 424.557 79.6072 423.046 83.5654C421.571 87.5238 420.833 92.1842 420.833 97.5459C420.833 102.907 421.588 107.604 423.1 111.634C424.647 115.664 426.861 118.812 429.739 121.079C432.654 123.31 436.18 124.426 440.318 124.426C444.637 124.426 448.253 123.275 451.168 120.972C454.083 118.633 456.278 115.448 457.753 111.418C459.264 107.352 460.02 102.728 460.021 97.5459C460.021 92.4001 459.283 87.8293 457.808 83.835C456.332 79.8407 454.136 76.7104 451.222 74.4434C448.307 72.1764 444.672 71.043 440.318 71.043Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M526.27 55.0117C531.379 55.0117 536.201 55.8389 540.735 57.4941C545.305 59.1134 549.336 61.6324 552.826 65.0508C556.353 68.4692 559.124 72.8237 561.139 78.1133C563.154 83.367 564.162 89.6286 564.162 96.8975V102.89H506.411C506.469 107.504 507.347 111.48 509.051 114.818C510.814 118.237 513.28 120.864 516.446 122.699C519.613 124.498 523.319 125.397 527.565 125.397C530.408 125.397 532.981 125.002 535.284 124.21C537.587 123.382 539.584 122.177 541.275 120.594C542.967 119.01 544.244 117.049 545.107 114.71L563.353 116.762C562.201 121.584 560.005 125.793 556.767 129.392C553.564 132.954 549.462 135.725 544.46 137.704C539.458 139.647 533.736 140.619 527.295 140.619C518.983 140.619 511.804 138.892 505.759 135.438C499.749 131.947 495.125 127.017 491.887 120.647C488.648 114.242 487.028 106.704 487.028 98.0312C487.028 89.5029 488.648 82.0184 491.887 75.5771C495.161 69.0999 499.731 64.0614 505.597 60.4629C511.462 56.8286 518.353 55.0118 526.27 55.0117ZM526.54 70.2334C522.546 70.2334 519.036 71.2043 516.014 73.1475C512.991 75.0546 510.635 77.5737 508.943 80.7041C507.466 83.4653 506.636 86.4706 506.448 89.7188H545.432C545.396 85.9765 544.586 82.6481 543.003 79.7334C541.42 76.7826 539.206 74.4608 536.363 72.7695C533.557 71.0784 530.282 70.2335 526.54 70.2334Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M338.89 26.9434C348.569 26.9434 357.224 29.1924 364.853 33.6904C372.517 38.1885 378.562 44.6836 382.988 53.1758C387.45 61.6322 389.682 71.8166 389.682 83.7275C389.682 95.6384 387.45 105.84 382.988 114.332C378.562 122.788 372.517 129.267 364.853 133.765C357.224 138.263 348.569 140.512 338.89 140.512C329.21 140.512 320.537 138.263 312.872 133.765C305.243 129.231 299.198 122.735 294.736 114.278C290.31 105.786 288.097 95.6024 288.097 83.7275C288.097 71.8166 290.31 61.6322 294.736 53.1758C299.198 44.6836 305.244 38.1885 312.872 33.6904C320.537 29.1923 329.21 26.9434 338.89 26.9434ZM338.89 44.918C332.88 44.918 327.572 46.4107 322.966 49.3975C318.36 52.3482 314.743 56.7211 312.116 62.5146C309.525 68.2721 308.23 75.3433 308.23 83.7275C308.23 92.1118 309.525 99.2007 312.116 104.994C314.743 110.752 318.36 115.124 322.966 118.11C327.572 121.061 332.88 122.537 338.89 122.537C344.899 122.537 350.207 121.061 354.812 118.11C359.418 115.124 363.018 110.752 365.608 104.994C368.235 99.2007 369.549 92.1118 369.549 83.7275C369.549 75.3433 368.235 68.2721 365.608 62.5146C363.017 56.7211 359.419 52.3482 354.812 49.3975C350.207 46.4109 344.899 44.918 338.89 44.918Z" fill="white"/>
|
||||
<path d="M901.341 26.9434C907.458 26.9434 913.162 27.8424 918.452 29.6416C923.742 31.4049 928.438 33.9246 932.54 37.1992C936.678 40.4738 940.079 44.3604 942.742 48.8584C945.405 53.3564 947.15 58.3224 947.978 63.7559H927.574C926.675 60.8411 925.433 58.2317 923.85 55.9287C922.302 53.5899 920.431 51.5927 918.236 49.9375C916.077 48.2822 913.594 47.0405 910.787 46.2129C907.98 45.3493 904.922 44.918 901.611 44.918C895.674 44.918 890.384 46.4109 885.742 49.3975C881.1 52.3842 877.447 56.7748 874.784 62.5684C872.157 68.3259 870.844 75.3428 870.844 83.6191C870.844 91.9675 872.157 99.0385 874.784 104.832C877.411 110.626 881.064 115.034 885.742 118.057C890.42 121.043 895.854 122.537 902.043 122.537C907.657 122.537 912.515 121.457 916.617 119.298C920.755 117.139 923.94 114.08 926.171 110.122C928.304 106.303 929.414 101.859 929.508 96.79H904.04V81.1367H948.896V94.415C948.895 103.879 946.88 112.066 942.85 118.975C938.819 125.884 933.278 131.21 926.225 134.952C919.172 138.659 911.075 140.512 901.935 140.512C891.751 140.512 882.809 138.226 875.108 133.656C867.444 129.05 861.452 122.519 857.134 114.062C852.852 105.57 850.711 95.4941 850.711 83.835C850.711 74.9108 851.97 66.9398 854.489 59.9229C857.044 52.906 860.607 46.9505 865.177 42.0566C869.747 37.1269 875.108 33.384 881.262 30.8291C887.415 28.2383 894.108 26.9434 901.341 26.9434Z" fill="white"/>
|
||||
<path d="M618.981 55.0117C624.703 55.0117 629.687 56.2347 633.934 58.6816C638.216 61.1286 641.526 64.6734 643.865 69.3154C646.24 73.9574 647.41 79.5889 647.374 86.21V139H627.834V89.2334C627.834 83.6917 626.394 79.3553 623.516 76.2246C620.673 73.0939 616.732 71.5283 611.694 71.5283C608.276 71.5284 605.235 72.2846 602.572 73.7959C599.946 75.2713 597.877 77.4121 596.365 80.2188C594.89 83.0256 594.152 86.4266 594.152 90.4209V139H574.612V56.0908H593.288V70.1787H594.26C596.167 65.5368 599.208 61.8481 603.382 59.1133C607.592 56.3785 612.792 55.0118 618.981 55.0117Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M703.476 28.4541C711.968 28.4541 719.093 29.9301 724.851 32.8809C730.644 35.8316 735.016 39.9702 737.967 45.2959C740.953 50.5856 742.447 56.7567 742.447 63.8096C742.447 70.8985 740.936 77.0518 737.913 82.2695C734.926 87.4513 730.518 91.4638 724.688 94.3066C724.005 94.6355 723.304 94.944 722.584 95.2344L746.495 139H724.148L702.424 98.5166H682.047V139H662.021V28.4541H703.476ZM682.047 81.8916H700.507C705.473 81.8916 709.539 81.2082 712.706 79.8408C715.873 78.4374 718.211 76.404 719.723 73.7412C721.27 71.0424 722.044 67.7318 722.044 63.8096C722.044 59.8873 721.27 56.5403 719.723 53.7695C718.175 50.9629 715.818 48.8397 712.651 47.4004C709.485 45.9251 705.4 45.1875 700.398 45.1875H682.047V81.8916Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M850.232 139H828.857L819.698 111.741H778.139L768.996 139H747.621L786.539 28.4541H811.261L850.232 139ZM783.533 95.6562H814.294L799.332 51.125H798.468L783.533 95.6562Z" fill="white"/>
|
||||
<path d="M1037.86 139.705H1008.9V54.1694H1038.44C1046.93 54.1694 1054.22 55.8818 1060.31 59.3066C1066.43 62.7035 1071.13 67.5901 1074.41 73.9663C1077.7 80.3425 1079.34 87.9717 1079.34 96.8538C1079.34 105.764 1077.68 113.421 1074.37 119.825C1071.09 126.229 1066.34 131.143 1060.14 134.568C1053.96 137.993 1046.54 139.705 1037.86 139.705ZM1024.38 126.299H1037.11C1043.06 126.299 1048.02 125.213 1052 123.041C1055.98 120.841 1058.97 117.57 1060.98 113.226C1062.98 108.854 1063.98 103.397 1063.98 96.8538C1063.98 90.3105 1062.98 84.881 1060.98 80.5653C1058.97 76.2216 1056.01 72.9779 1052.09 70.8339C1048.19 68.6621 1043.35 67.5762 1037.56 67.5762H1024.38V126.299Z" fill="#E0E0E0"/>
|
||||
<path d="M1170.41 96.9374C1170.41 106.154 1168.69 114.047 1165.24 120.618C1161.82 127.162 1157.14 132.174 1151.22 135.654C1145.32 139.134 1138.63 140.875 1131.15 140.875C1123.66 140.875 1116.96 139.134 1111.03 135.654C1105.13 132.146 1100.46 127.12 1097.01 120.577C1093.59 114.006 1091.88 106.126 1091.88 96.9374C1091.88 87.7211 1093.59 79.8413 1097.01 73.2981C1100.46 66.7269 1105.13 61.7012 1111.03 58.2207C1116.96 54.7402 1123.66 53 1131.15 53C1138.63 53 1145.32 54.7402 1151.22 58.2207C1157.14 61.7012 1161.82 66.7269 1165.24 73.2981C1168.69 79.8413 1170.41 87.7211 1170.41 96.9374ZM1154.85 96.9374C1154.85 90.4498 1153.83 84.9785 1151.8 80.5235C1149.8 76.0407 1147.02 72.6577 1143.46 70.3745C1139.9 68.0634 1135.79 66.9079 1131.15 66.9079C1126.5 66.9079 1122.4 68.0634 1118.83 70.3745C1115.27 72.6577 1112.48 76.0407 1110.45 80.5235C1108.44 84.9785 1107.44 90.4498 1107.44 96.9374C1107.44 103.425 1108.44 108.91 1110.45 113.393C1112.48 117.848 1115.27 121.231 1118.83 123.542C1122.4 125.825 1126.5 126.967 1131.15 126.967C1135.79 126.967 1139.9 125.825 1143.46 123.542C1147.02 121.231 1149.8 117.848 1151.8 113.393C1153.83 108.91 1154.85 103.425 1154.85 96.9374Z" fill="#E0E0E0"/>
|
||||
<path d="M1258.2 83.0294H1242.6C1242.15 80.4678 1241.33 78.1985 1240.14 76.2216C1238.94 74.2169 1237.45 72.5184 1235.67 71.1262C1233.89 69.7341 1231.86 68.6899 1229.58 67.9938C1227.32 67.2699 1224.89 66.9079 1222.27 66.9079C1217.63 66.9079 1213.51 68.0774 1209.92 70.4162C1206.33 72.7273 1203.52 76.1242 1201.49 80.607C1199.46 85.062 1198.45 90.5055 1198.45 96.9374C1198.45 103.481 1199.46 108.994 1201.49 113.477C1203.55 117.931 1206.36 121.301 1209.92 123.584C1213.51 125.839 1217.61 126.967 1222.23 126.967C1224.79 126.967 1227.19 126.633 1229.41 125.964C1231.66 125.268 1233.68 124.252 1235.46 122.916C1237.27 121.579 1238.79 119.936 1240.01 117.987C1241.26 116.038 1242.12 113.811 1242.6 111.305L1258.2 111.388C1257.62 115.453 1256.35 119.268 1254.41 122.832C1252.49 126.396 1249.97 129.542 1246.85 132.271C1243.74 134.972 1240.09 137.088 1235.92 138.619C1231.75 140.123 1227.12 140.875 1222.02 140.875C1214.51 140.875 1207.81 139.134 1201.91 135.654C1196.01 132.174 1191.37 127.148 1187.97 120.577C1184.58 114.006 1182.88 106.126 1182.88 96.9374C1182.88 87.7211 1184.59 79.8413 1188.01 73.2981C1191.44 66.7269 1196.1 61.7012 1201.99 58.2207C1207.89 54.7402 1214.57 53 1222.02 53C1226.78 53 1231.21 53.6682 1235.29 55.0047C1239.38 56.3412 1243.03 58.3042 1246.23 60.8937C1249.43 63.4553 1252.06 66.6016 1254.12 70.3327C1256.2 74.0359 1257.57 78.2682 1258.2 83.0294Z" fill="#E0E0E0"/>
|
||||
<path d="M1318.83 77.6834C1318.44 74.0359 1316.8 71.1959 1313.91 69.1633C1311.04 67.1307 1307.31 66.1144 1302.72 66.1144C1299.49 66.1144 1296.73 66.6016 1294.42 67.5762C1292.11 68.5507 1290.34 69.8733 1289.12 71.5439C1287.89 73.2145 1287.27 75.1218 1287.24 77.2658C1287.24 79.0478 1287.64 80.5931 1288.45 81.9018C1289.28 83.2104 1290.41 84.3242 1291.83 85.243C1293.25 86.134 1294.82 86.8858 1296.55 87.4983C1298.27 88.1109 1300.01 88.626 1301.76 89.0437L1309.77 91.0484C1313 91.8002 1316.1 92.8165 1319.08 94.0973C1322.08 95.3781 1324.77 96.993 1327.13 98.9421C1329.53 100.891 1331.42 103.244 1332.81 106C1334.2 108.757 1334.9 111.987 1334.9 115.69C1334.9 120.702 1333.62 125.115 1331.06 128.93C1328.5 132.716 1324.8 135.682 1319.96 137.826C1315.14 139.942 1309.31 141 1302.47 141C1295.82 141 1290.05 139.97 1285.15 137.909C1280.28 135.849 1276.47 132.842 1273.72 128.888C1270.99 124.934 1269.52 120.117 1269.29 114.437H1284.53C1284.75 117.416 1285.67 119.894 1287.28 121.871C1288.89 123.848 1290.99 125.324 1293.58 126.299C1296.2 127.273 1299.12 127.76 1302.35 127.76C1305.71 127.76 1308.66 127.259 1311.19 126.257C1313.75 125.227 1315.76 123.807 1317.2 121.997C1318.65 120.159 1319.39 118.015 1319.41 115.565C1319.39 113.337 1318.73 111.5 1317.45 110.052C1316.17 108.576 1314.38 107.351 1312.07 106.376C1309.79 105.374 1307.12 104.483 1304.06 103.703L1294.33 101.197C1287.29 99.3876 1281.73 96.645 1277.64 92.9696C1273.58 89.2664 1271.55 84.352 1271.55 78.2264C1271.55 73.1867 1272.91 68.7735 1275.64 64.9867C1278.39 61.2 1282.13 58.2625 1286.86 56.1742C1291.59 54.0581 1296.95 53 1302.93 53C1308.99 53 1314.31 54.0581 1318.87 56.1742C1323.46 58.2625 1327.06 61.1721 1329.68 64.9032C1332.29 68.6064 1333.64 72.8665 1333.73 77.6834H1318.83Z" fill="#E0E0E0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
16
docs/static/img/logo-openrag-light.svg
vendored
Normal file
16
docs/static/img/logo-openrag-light.svg
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<svg width="1335" height="185" viewBox="0 0 1335 185" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M172.336 97.5947H201.395C207.095 97.5948 211.713 102.212 211.713 107.912V125.055C211.713 130.755 207.095 135.372 201.395 135.372H176.05C173.312 135.372 170.687 136.462 168.752 138.397L125.259 181.883C123.324 183.819 120.699 184.908 117.961 184.908H97.0534C91.4612 184.908 86.8796 180.449 86.736 174.856L86.2848 157.354C86.1343 151.554 90.7947 146.765 96.6022 146.765H114.598C117.337 146.765 119.961 145.675 121.897 143.739L165.023 100.613C166.959 98.6775 169.583 97.5879 172.322 97.5879L172.336 97.5947Z" fill="black"/>
|
||||
<path d="M201.395 22.083C207.095 22.083 211.713 26.7004 211.713 32.4004V49.543C211.713 55.2429 207.095 59.8604 201.395 59.8604H176.05C173.312 59.8604 170.687 60.95 168.752 62.8857L125.259 106.378C123.324 108.314 120.699 109.403 117.961 109.403H92.5582C89.8983 109.403 87.339 110.429 85.4176 112.271L36.5914 159.061C34.6699 160.903 32.1098 161.929 29.4498 161.929H11.6549C5.95497 161.929 1.33753 157.304 1.33753 151.611V133.995C1.33777 128.295 5.95512 123.679 11.6549 123.679H29.3209C32.0598 123.679 34.6839 122.588 36.6198 120.652L82.9869 74.2861C84.9228 72.3503 87.5469 71.2598 90.2858 71.2598H114.598C117.337 71.2598 119.961 70.1702 121.897 68.2344L165.023 25.1084C166.959 23.1726 169.583 22.083 172.322 22.083H201.395Z" fill="black"/>
|
||||
<path d="M115.114 0C120.814 0 125.431 4.61743 125.431 10.3174V27.46C125.431 33.1599 120.814 37.7773 115.114 37.7773H89.7692C87.0304 37.7773 84.4062 38.867 82.4703 40.8027L38.9782 84.2949C37.0423 86.2306 34.418 87.3203 31.6793 87.3203H10.7731C5.1807 87.3203 0.599138 82.8609 0.455697 77.2686L0.0035481 59.7676C-0.147015 53.9673 4.5135 49.1847 10.3209 49.1846H28.317C31.0558 49.1846 33.68 48.0949 35.6159 46.1592L78.7418 3.0332C80.6776 1.09742 83.3019 0.00689882 86.0407 0.00683594L86.0553 0H115.114Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M445.716 55.0117C452.121 55.0117 457.897 56.6483 463.043 59.9229C468.225 63.1614 472.327 67.9658 475.35 74.335C478.408 80.6683 479.937 88.4412 479.938 97.6533C479.938 106.757 478.445 114.494 475.458 120.863C472.471 127.233 468.405 132.091 463.259 135.438C458.113 138.784 452.284 140.457 445.771 140.457C441.021 140.457 437.08 139.665 433.949 138.082C430.819 136.499 428.3 134.591 426.393 132.36C424.521 130.093 423.064 127.953 422.021 125.938H421.211V170.091H401.671V56.0908H420.887V69.8008H422.021C423.028 67.7858 424.449 65.6448 426.284 63.3779C428.119 61.0749 430.603 59.1135 433.733 57.4941C436.864 55.8389 440.858 55.0118 445.716 55.0117ZM440.318 71.043C436.144 71.043 432.599 72.1399 429.685 74.335C426.77 76.53 424.557 79.6072 423.046 83.5654C421.571 87.5238 420.833 92.1842 420.833 97.5459C420.833 102.907 421.588 107.604 423.1 111.634C424.647 115.664 426.861 118.812 429.739 121.079C432.654 123.31 436.18 124.426 440.318 124.426C444.637 124.426 448.253 123.275 451.168 120.972C454.083 118.633 456.278 115.448 457.753 111.418C459.264 107.352 460.02 102.728 460.021 97.5459C460.021 92.4001 459.283 87.8293 457.808 83.835C456.332 79.8407 454.136 76.7104 451.222 74.4434C448.307 72.1764 444.672 71.043 440.318 71.043Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M526.27 55.0117C531.379 55.0117 536.201 55.8389 540.735 57.4941C545.305 59.1134 549.336 61.6324 552.826 65.0508C556.353 68.4692 559.124 72.8237 561.139 78.1133C563.154 83.367 564.162 89.6286 564.162 96.8975V102.89H506.411C506.469 107.504 507.347 111.48 509.051 114.818C510.814 118.237 513.28 120.864 516.446 122.699C519.613 124.498 523.319 125.397 527.565 125.397C530.408 125.397 532.981 125.002 535.284 124.21C537.587 123.382 539.584 122.177 541.275 120.594C542.967 119.01 544.244 117.049 545.107 114.71L563.353 116.762C562.201 121.584 560.005 125.793 556.767 129.392C553.564 132.954 549.462 135.725 544.46 137.704C539.458 139.647 533.736 140.619 527.295 140.619C518.983 140.619 511.804 138.892 505.759 135.438C499.749 131.947 495.125 127.017 491.887 120.647C488.648 114.242 487.028 106.704 487.028 98.0312C487.028 89.5029 488.648 82.0184 491.887 75.5771C495.161 69.0999 499.731 64.0614 505.597 60.4629C511.462 56.8286 518.353 55.0118 526.27 55.0117ZM526.54 70.2334C522.546 70.2334 519.036 71.2043 516.014 73.1475C512.991 75.0546 510.635 77.5737 508.943 80.7041C507.466 83.4653 506.636 86.4706 506.448 89.7188H545.432C545.396 85.9765 544.586 82.6481 543.003 79.7334C541.42 76.7826 539.206 74.4608 536.363 72.7695C533.557 71.0784 530.282 70.2335 526.54 70.2334Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M338.89 26.9434C348.569 26.9434 357.224 29.1924 364.853 33.6904C372.517 38.1885 378.562 44.6836 382.988 53.1758C387.45 61.6322 389.682 71.8166 389.682 83.7275C389.682 95.6384 387.45 105.84 382.988 114.332C378.562 122.788 372.517 129.267 364.853 133.765C357.224 138.263 348.569 140.512 338.89 140.512C329.21 140.512 320.537 138.263 312.872 133.765C305.243 129.231 299.198 122.735 294.736 114.278C290.31 105.786 288.097 95.6024 288.097 83.7275C288.097 71.8166 290.31 61.6322 294.736 53.1758C299.198 44.6836 305.244 38.1885 312.872 33.6904C320.537 29.1923 329.21 26.9434 338.89 26.9434ZM338.89 44.918C332.88 44.918 327.572 46.4107 322.966 49.3975C318.36 52.3482 314.743 56.7211 312.116 62.5146C309.525 68.2721 308.23 75.3433 308.23 83.7275C308.23 92.1118 309.525 99.2007 312.116 104.994C314.743 110.752 318.36 115.124 322.966 118.11C327.572 121.061 332.88 122.537 338.89 122.537C344.899 122.537 350.207 121.061 354.812 118.11C359.418 115.124 363.018 110.752 365.608 104.994C368.235 99.2007 369.549 92.1118 369.549 83.7275C369.549 75.3433 368.235 68.2721 365.608 62.5146C363.017 56.7211 359.419 52.3482 354.812 49.3975C350.207 46.4109 344.899 44.918 338.89 44.918Z" fill="black"/>
|
||||
<path d="M901.341 26.9434C907.458 26.9434 913.162 27.8424 918.452 29.6416C923.742 31.4049 928.438 33.9246 932.54 37.1992C936.678 40.4738 940.079 44.3604 942.742 48.8584C945.405 53.3564 947.15 58.3224 947.978 63.7559H927.574C926.675 60.8411 925.433 58.2317 923.85 55.9287C922.302 53.5899 920.431 51.5927 918.236 49.9375C916.077 48.2822 913.594 47.0405 910.787 46.2129C907.98 45.3493 904.922 44.918 901.611 44.918C895.674 44.918 890.384 46.4109 885.742 49.3975C881.1 52.3842 877.447 56.7748 874.784 62.5684C872.157 68.3259 870.844 75.3428 870.844 83.6191C870.844 91.9675 872.157 99.0385 874.784 104.832C877.411 110.626 881.064 115.034 885.742 118.057C890.42 121.043 895.854 122.537 902.043 122.537C907.657 122.537 912.515 121.457 916.617 119.298C920.755 117.139 923.94 114.08 926.171 110.122C928.304 106.303 929.414 101.859 929.508 96.79H904.04V81.1367H948.896V94.415C948.895 103.879 946.88 112.066 942.85 118.975C938.819 125.884 933.278 131.21 926.225 134.952C919.172 138.659 911.075 140.512 901.935 140.512C891.751 140.512 882.809 138.226 875.108 133.656C867.444 129.05 861.452 122.519 857.134 114.062C852.852 105.57 850.711 95.4941 850.711 83.835C850.711 74.9108 851.97 66.9398 854.489 59.9229C857.044 52.906 860.607 46.9505 865.177 42.0566C869.747 37.1269 875.108 33.384 881.262 30.8291C887.415 28.2383 894.108 26.9434 901.341 26.9434Z" fill="black"/>
|
||||
<path d="M618.981 55.0117C624.703 55.0117 629.687 56.2347 633.934 58.6816C638.216 61.1286 641.526 64.6734 643.865 69.3154C646.24 73.9574 647.41 79.5889 647.374 86.21V139H627.834V89.2334C627.834 83.6917 626.394 79.3553 623.516 76.2246C620.673 73.0939 616.732 71.5283 611.694 71.5283C608.276 71.5284 605.235 72.2846 602.572 73.7959C599.946 75.2713 597.877 77.4121 596.365 80.2188C594.89 83.0256 594.152 86.4266 594.152 90.4209V139H574.612V56.0908H593.288V70.1787H594.26C596.167 65.5368 599.208 61.8481 603.382 59.1133C607.592 56.3785 612.792 55.0118 618.981 55.0117Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M703.476 28.4541C711.968 28.4541 719.093 29.9301 724.851 32.8809C730.644 35.8316 735.016 39.9702 737.967 45.2959C740.953 50.5856 742.447 56.7567 742.447 63.8096C742.447 70.8985 740.936 77.0518 737.913 82.2695C734.926 87.4513 730.518 91.4638 724.688 94.3066C724.005 94.6355 723.304 94.944 722.584 95.2344L746.495 139H724.148L702.424 98.5166H682.047V139H662.021V28.4541H703.476ZM682.047 81.8916H700.507C705.473 81.8916 709.539 81.2082 712.706 79.8408C715.873 78.4374 718.211 76.404 719.723 73.7412C721.27 71.0424 722.044 67.7318 722.044 63.8096C722.044 59.8873 721.27 56.5403 719.723 53.7695C718.175 50.9629 715.818 48.8397 712.651 47.4004C709.485 45.9251 705.4 45.1875 700.398 45.1875H682.047V81.8916Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M850.232 139H828.857L819.698 111.741H778.139L768.996 139H747.621L786.539 28.4541H811.261L850.232 139ZM783.533 95.6562H814.294L799.332 51.125H798.468L783.533 95.6562Z" fill="black"/>
|
||||
<path d="M1037.86 139.705H1008.9V54.1694H1038.44C1046.93 54.1694 1054.22 55.8818 1060.31 59.3066C1066.43 62.7035 1071.13 67.5901 1074.41 73.9663C1077.7 80.3425 1079.34 87.9717 1079.34 96.8538C1079.34 105.764 1077.68 113.421 1074.37 119.825C1071.09 126.229 1066.34 131.143 1060.14 134.568C1053.96 137.993 1046.54 139.705 1037.86 139.705ZM1024.38 126.299H1037.11C1043.06 126.299 1048.02 125.213 1052 123.041C1055.98 120.841 1058.97 117.57 1060.98 113.226C1062.98 108.854 1063.98 103.397 1063.98 96.8538C1063.98 90.3105 1062.98 84.881 1060.98 80.5653C1058.97 76.2216 1056.01 72.9779 1052.09 70.8339C1048.19 68.6621 1043.35 67.5762 1037.56 67.5762H1024.38V126.299Z" fill="#9F9FA9"/>
|
||||
<path d="M1170.41 96.9374C1170.41 106.154 1168.69 114.047 1165.24 120.618C1161.82 127.162 1157.14 132.174 1151.22 135.654C1145.32 139.134 1138.63 140.875 1131.15 140.875C1123.66 140.875 1116.96 139.134 1111.03 135.654C1105.13 132.146 1100.46 127.12 1097.01 120.577C1093.59 114.006 1091.88 106.126 1091.88 96.9374C1091.88 87.7211 1093.59 79.8413 1097.01 73.2981C1100.46 66.7269 1105.13 61.7012 1111.03 58.2207C1116.96 54.7402 1123.66 53 1131.15 53C1138.63 53 1145.32 54.7402 1151.22 58.2207C1157.14 61.7012 1161.82 66.7269 1165.24 73.2981C1168.69 79.8413 1170.41 87.7211 1170.41 96.9374ZM1154.85 96.9374C1154.85 90.4498 1153.83 84.9785 1151.8 80.5235C1149.8 76.0407 1147.02 72.6577 1143.46 70.3745C1139.9 68.0634 1135.79 66.9079 1131.15 66.9079C1126.5 66.9079 1122.4 68.0634 1118.83 70.3745C1115.27 72.6577 1112.48 76.0407 1110.45 80.5235C1108.44 84.9785 1107.44 90.4498 1107.44 96.9374C1107.44 103.425 1108.44 108.91 1110.45 113.393C1112.48 117.848 1115.27 121.231 1118.83 123.542C1122.4 125.825 1126.5 126.967 1131.15 126.967C1135.79 126.967 1139.9 125.825 1143.46 123.542C1147.02 121.231 1149.8 117.848 1151.8 113.393C1153.83 108.91 1154.85 103.425 1154.85 96.9374Z" fill="#9F9FA9"/>
|
||||
<path d="M1258.2 83.0294H1242.6C1242.15 80.4678 1241.33 78.1985 1240.14 76.2216C1238.94 74.2169 1237.45 72.5184 1235.67 71.1262C1233.89 69.7341 1231.86 68.6899 1229.58 67.9938C1227.32 67.2699 1224.89 66.9079 1222.27 66.9079C1217.63 66.9079 1213.51 68.0774 1209.92 70.4162C1206.33 72.7273 1203.52 76.1242 1201.49 80.607C1199.46 85.062 1198.45 90.5055 1198.45 96.9374C1198.45 103.481 1199.46 108.994 1201.49 113.477C1203.55 117.931 1206.36 121.301 1209.92 123.584C1213.51 125.839 1217.61 126.967 1222.23 126.967C1224.79 126.967 1227.19 126.633 1229.41 125.964C1231.66 125.268 1233.68 124.252 1235.46 122.916C1237.27 121.579 1238.79 119.936 1240.01 117.987C1241.26 116.038 1242.12 113.811 1242.6 111.305L1258.2 111.388C1257.62 115.453 1256.35 119.268 1254.41 122.832C1252.49 126.396 1249.97 129.542 1246.85 132.271C1243.74 134.972 1240.09 137.088 1235.92 138.619C1231.75 140.123 1227.12 140.875 1222.02 140.875C1214.51 140.875 1207.81 139.134 1201.91 135.654C1196.01 132.174 1191.37 127.148 1187.97 120.577C1184.58 114.006 1182.88 106.126 1182.88 96.9374C1182.88 87.7211 1184.59 79.8413 1188.01 73.2981C1191.44 66.7269 1196.1 61.7012 1201.99 58.2207C1207.89 54.7402 1214.57 53 1222.02 53C1226.78 53 1231.21 53.6682 1235.29 55.0047C1239.38 56.3412 1243.03 58.3042 1246.23 60.8937C1249.43 63.4553 1252.06 66.6016 1254.12 70.3327C1256.2 74.0359 1257.57 78.2682 1258.2 83.0294Z" fill="#9F9FA9"/>
|
||||
<path d="M1318.83 77.6834C1318.44 74.0359 1316.8 71.1959 1313.91 69.1633C1311.04 67.1307 1307.31 66.1144 1302.72 66.1144C1299.49 66.1144 1296.73 66.6016 1294.42 67.5762C1292.11 68.5507 1290.34 69.8733 1289.12 71.5439C1287.89 73.2145 1287.27 75.1218 1287.24 77.2658C1287.24 79.0478 1287.64 80.5931 1288.45 81.9018C1289.28 83.2104 1290.41 84.3242 1291.83 85.243C1293.25 86.134 1294.82 86.8858 1296.55 87.4983C1298.27 88.1109 1300.01 88.626 1301.76 89.0437L1309.77 91.0484C1313 91.8002 1316.1 92.8165 1319.08 94.0973C1322.08 95.3781 1324.77 96.993 1327.13 98.9421C1329.53 100.891 1331.42 103.244 1332.81 106C1334.2 108.757 1334.9 111.987 1334.9 115.69C1334.9 120.702 1333.62 125.115 1331.06 128.93C1328.5 132.716 1324.8 135.682 1319.96 137.826C1315.14 139.942 1309.31 141 1302.47 141C1295.82 141 1290.05 139.97 1285.15 137.909C1280.28 135.849 1276.47 132.842 1273.72 128.888C1270.99 124.934 1269.52 120.117 1269.29 114.437H1284.53C1284.75 117.416 1285.67 119.894 1287.28 121.871C1288.89 123.848 1290.99 125.324 1293.58 126.299C1296.2 127.273 1299.12 127.76 1302.35 127.76C1305.71 127.76 1308.66 127.259 1311.19 126.257C1313.75 125.227 1315.76 123.807 1317.2 121.997C1318.65 120.159 1319.39 118.015 1319.41 115.565C1319.39 113.337 1318.73 111.5 1317.45 110.052C1316.17 108.576 1314.38 107.351 1312.07 106.376C1309.79 105.374 1307.12 104.483 1304.06 103.703L1294.33 101.197C1287.29 99.3876 1281.73 96.645 1277.64 92.9696C1273.58 89.2664 1271.55 84.352 1271.55 78.2264C1271.55 73.1867 1272.91 68.7735 1275.64 64.9867C1278.39 61.2 1282.13 58.2625 1286.86 56.1742C1291.59 54.0581 1296.95 53 1302.93 53C1308.99 53 1314.31 54.0581 1318.87 56.1742C1323.46 58.2625 1327.06 61.1721 1329.68 64.9032C1332.29 68.6064 1333.64 72.8665 1333.73 77.6834H1318.83Z" fill="#9F9FA9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
1
docs/static/img/logo.svg
vendored
1
docs/static/img/logo.svg
vendored
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="22" viewBox="0 0 24 22" fill="currentColor" class="h-6 w-6"><path d="M13.0486 0.462158H9.75399C9.44371 0.462158 9.14614 0.586082 8.92674 0.806667L4.03751 5.72232C3.81811 5.9429 3.52054 6.06682 3.21026 6.06682H1.16992C0.511975 6.06682 -0.0165756 6.61212 0.000397655 7.2734L0.0515933 9.26798C0.0679586 9.90556 0.586745 10.4139 1.22111 10.4139H3.59097C3.90124 10.4139 4.19881 10.2899 4.41821 10.0694L9.34823 5.11269C9.56763 4.89211 9.8652 4.76818 10.1755 4.76818H13.0486C13.6947 4.76818 14.2185 4.24157 14.2185 3.59195V1.63839C14.2185 0.988773 13.6947 0.462158 13.0486 0.462158Z"></path><path d="M19.5355 11.5862H22.8301C23.4762 11.5862 24 12.1128 24 12.7624V14.716C24 15.3656 23.4762 15.8922 22.8301 15.8922H19.957C19.6467 15.8922 19.3491 16.0161 19.1297 16.2367L14.1997 21.1934C13.9803 21.414 13.6827 21.5379 13.3725 21.5379H11.0026C10.3682 21.5379 9.84945 21.0296 9.83309 20.392L9.78189 18.3974C9.76492 17.7361 10.2935 17.1908 10.9514 17.1908H12.9918C13.302 17.1908 13.5996 17.0669 13.819 16.8463L18.7082 11.9307C18.9276 11.7101 19.2252 11.5862 19.5355 11.5862Z"></path><path d="M19.5355 2.9796L22.8301 2.9796C23.4762 2.9796 24 3.50622 24 4.15583V6.1094C24 6.75901 23.4762 7.28563 22.8301 7.28563H19.957C19.6467 7.28563 19.3491 7.40955 19.1297 7.63014L14.1997 12.5868C13.9803 12.8074 13.6827 12.9313 13.3725 12.9313H10.493C10.1913 12.9313 9.90126 13.0485 9.68346 13.2583L4.14867 18.5917C3.93087 18.8016 3.64085 18.9187 3.33917 18.9187H1.32174C0.675616 18.9187 0.151832 18.3921 0.151832 17.7425V15.7343C0.151832 15.0846 0.675616 14.558 1.32174 14.558H3.32468C3.63496 14.558 3.93253 14.4341 4.15193 14.2135L9.40827 8.92878C9.62767 8.70819 9.92524 8.58427 10.2355 8.58427H12.9918C13.302 8.58427 13.5996 8.46034 13.819 8.23976L18.7082 3.32411C18.9276 3.10353 19.2252 2.9796 19.5355 2.9796Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
169
frontend/components/filter-icon-popover.tsx
Normal file
169
frontend/components/filter-icon-popover.tsx
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
"use client";
|
||||
|
||||
import React, { type SVGProps } from "react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import {
|
||||
Book,
|
||||
Scroll,
|
||||
Library,
|
||||
Map,
|
||||
FileImage,
|
||||
Layers3,
|
||||
Database,
|
||||
Folder,
|
||||
Archive,
|
||||
MessagesSquare,
|
||||
SquareStack,
|
||||
Ghost,
|
||||
Gem,
|
||||
Swords,
|
||||
Bolt,
|
||||
Shield,
|
||||
Hammer,
|
||||
Globe,
|
||||
HardDrive,
|
||||
Upload,
|
||||
Cable,
|
||||
ShoppingCart,
|
||||
ShoppingBag,
|
||||
Check,
|
||||
Filter,
|
||||
} from "lucide-react";
|
||||
import { filterAccentClasses } from "./knowledge-filter-panel";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const ICON_MAP = {
|
||||
filter: Filter,
|
||||
book: Book,
|
||||
scroll: Scroll,
|
||||
library: Library,
|
||||
map: Map,
|
||||
image: FileImage,
|
||||
layers3: Layers3,
|
||||
database: Database,
|
||||
folder: Folder,
|
||||
archive: Archive,
|
||||
messagesSquare: MessagesSquare,
|
||||
squareStack: SquareStack,
|
||||
ghost: Ghost,
|
||||
gem: Gem,
|
||||
swords: Swords,
|
||||
bolt: Bolt,
|
||||
shield: Shield,
|
||||
hammer: Hammer,
|
||||
globe: Globe,
|
||||
hardDrive: HardDrive,
|
||||
upload: Upload,
|
||||
cable: Cable,
|
||||
shoppingCart: ShoppingCart,
|
||||
shoppingBag: ShoppingBag,
|
||||
} as const;
|
||||
|
||||
export type IconKey = keyof typeof ICON_MAP;
|
||||
|
||||
export function iconKeyToComponent(
|
||||
key?: string
|
||||
): React.ComponentType<SVGProps<SVGSVGElement>> | undefined {
|
||||
if (!key) return undefined;
|
||||
return (
|
||||
ICON_MAP as Record<string, React.ComponentType<SVGProps<SVGSVGElement>>>
|
||||
)[key];
|
||||
}
|
||||
|
||||
const COLORS = [
|
||||
"zinc",
|
||||
"pink",
|
||||
"purple",
|
||||
"indigo",
|
||||
"emerald",
|
||||
"amber",
|
||||
"red",
|
||||
] as const;
|
||||
export type FilterColor = (typeof COLORS)[number];
|
||||
|
||||
const colorSwatchClasses = {
|
||||
zinc: "bg-muted-foreground",
|
||||
pink: "bg-accent-pink-foreground",
|
||||
purple: "bg-accent-purple-foreground",
|
||||
indigo: "bg-accent-indigo-foreground",
|
||||
emerald: "bg-accent-emerald-foreground",
|
||||
amber: "bg-accent-amber-foreground",
|
||||
red: "bg-accent-red-foreground",
|
||||
};
|
||||
|
||||
export interface FilterIconPopoverProps {
|
||||
color: FilterColor;
|
||||
iconKey: IconKey;
|
||||
onColorChange: (c: FilterColor) => void;
|
||||
onIconChange: (k: IconKey) => void;
|
||||
triggerClassName?: string;
|
||||
}
|
||||
|
||||
export function FilterIconPopover({
|
||||
color,
|
||||
iconKey,
|
||||
onColorChange,
|
||||
onIconChange,
|
||||
triggerClassName,
|
||||
}: FilterIconPopoverProps) {
|
||||
const Icon = iconKeyToComponent(iconKey);
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
"h-10 w-10 min-w-10 min-h-10 rounded-md flex items-center justify-center transition-colors",
|
||||
filterAccentClasses[color],
|
||||
triggerClassName
|
||||
)}
|
||||
>
|
||||
{Icon && <Icon className="h-5 w-5" />}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80" align="start">
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-7 items-center gap-2">
|
||||
{COLORS.map((c) => (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
onClick={() => onColorChange(c)}
|
||||
className={cn(
|
||||
"flex items-center justify-center h-6 w-6 rounded-sm transition-colors text-primary",
|
||||
colorSwatchClasses[c]
|
||||
)}
|
||||
aria-label={c}
|
||||
>
|
||||
{c === color && <Check className="h-3.5 w-3.5" />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-6 gap-2">
|
||||
{Object.keys(ICON_MAP).map((k: string) => {
|
||||
const OptIcon = ICON_MAP[k as IconKey];
|
||||
const active = iconKey === k;
|
||||
return (
|
||||
<button
|
||||
key={k}
|
||||
type="button"
|
||||
onClick={() => onIconChange(k as IconKey)}
|
||||
className={
|
||||
"h-8 w-8 inline-flex items-center hover:text-foreground justify-center rounded " +
|
||||
(active ? "bg-muted text-primary" : "text-muted-foreground")
|
||||
}
|
||||
aria-label={k}
|
||||
>
|
||||
<OptIcon className="h-4 w-4" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,24 +2,19 @@
|
|||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Filter, Loader2, Plus, Save, X } from "lucide-react";
|
||||
import { Loader2, Plus } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
useGetFiltersSearchQuery,
|
||||
type KnowledgeFilter,
|
||||
} from "@/src/app/api/queries/useGetFiltersSearchQuery";
|
||||
import { useCreateFilter } from "@/src/app/api/mutations/useCreateFilter";
|
||||
import { useKnowledgeFilter } from "@/src/contexts/knowledge-filter-context";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
FilterColor,
|
||||
IconKey,
|
||||
iconKeyToComponent,
|
||||
} from "./filter-icon-popover";
|
||||
import { filterAccentClasses } from "./knowledge-filter-panel";
|
||||
|
||||
interface ParsedQueryData {
|
||||
query: string;
|
||||
|
|
@ -30,6 +25,8 @@ interface ParsedQueryData {
|
|||
};
|
||||
limit: number;
|
||||
scoreThreshold: number;
|
||||
color: FilterColor;
|
||||
icon: IconKey;
|
||||
}
|
||||
|
||||
interface KnowledgeFilterListProps {
|
||||
|
|
@ -42,10 +39,7 @@ export function KnowledgeFilterList({
|
|||
onFilterSelect,
|
||||
}: KnowledgeFilterListProps) {
|
||||
const [searchQuery] = useState("");
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const [createName, setCreateName] = useState("");
|
||||
const [createDescription, setCreateDescription] = useState("");
|
||||
const [creating, setCreating] = useState(false);
|
||||
const { startCreateMode } = useKnowledgeFilter();
|
||||
|
||||
const { data, isFetching: loading } = useGetFiltersSearchQuery(
|
||||
searchQuery,
|
||||
|
|
@ -54,57 +48,16 @@ export function KnowledgeFilterList({
|
|||
|
||||
const filters = data || [];
|
||||
|
||||
const createFilterMutation = useCreateFilter();
|
||||
|
||||
const handleFilterSelect = (filter: KnowledgeFilter) => {
|
||||
if (filter.id === selectedFilter?.id) {
|
||||
onFilterSelect(null);
|
||||
return;
|
||||
}
|
||||
onFilterSelect(filter);
|
||||
};
|
||||
|
||||
const handleCreateNew = () => {
|
||||
setShowCreateModal(true);
|
||||
};
|
||||
|
||||
const handleCreateFilter = async () => {
|
||||
if (!createName.trim()) return;
|
||||
|
||||
setCreating(true);
|
||||
try {
|
||||
// Create a basic filter with wildcards (match everything by default)
|
||||
const defaultFilterData = {
|
||||
query: "",
|
||||
filters: {
|
||||
data_sources: ["*"],
|
||||
document_types: ["*"],
|
||||
owners: ["*"],
|
||||
},
|
||||
limit: 10,
|
||||
scoreThreshold: 0,
|
||||
};
|
||||
|
||||
const result = await createFilterMutation.mutateAsync({
|
||||
name: createName.trim(),
|
||||
description: createDescription.trim(),
|
||||
queryData: JSON.stringify(defaultFilterData),
|
||||
});
|
||||
|
||||
// Select the new filter from API response
|
||||
onFilterSelect(result.filter);
|
||||
|
||||
// Close modal and reset form
|
||||
setShowCreateModal(false);
|
||||
setCreateName("");
|
||||
setCreateDescription("");
|
||||
} catch (error) {
|
||||
console.error("Error creating knowledge filter:", error);
|
||||
} finally {
|
||||
setCreating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelCreate = () => {
|
||||
setShowCreateModal(false);
|
||||
setCreateName("");
|
||||
setCreateDescription("");
|
||||
startCreateMode();
|
||||
};
|
||||
|
||||
const parseQueryData = (queryData: string): ParsedQueryData => {
|
||||
|
|
@ -113,7 +66,7 @@ export function KnowledgeFilterList({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col items-center gap-1 px-3 !mb-12 mt-0 h-full overflow-y-auto">
|
||||
<div className="flex flex-col gap-1 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
|
||||
|
|
@ -136,7 +89,7 @@ export function KnowledgeFilterList({
|
|||
</span>
|
||||
</div>
|
||||
) : filters.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
<div className="py-2 px-4 text-sm text-muted-foreground">
|
||||
{searchQuery ? "No filters found" : "No saved filters"}
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -147,32 +100,45 @@ export function KnowledgeFilterList({
|
|||
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 &&
|
||||
"bg-accent text-accent-foreground"
|
||||
"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">
|
||||
<div className="flex items-center justify-center bg-blue-500/20 w-5 h-5 rounded">
|
||||
<Filter className="h-3 w-3 text-blue-400" />
|
||||
</div>
|
||||
{(() => {
|
||||
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 group-hover:text-accent-foreground/70 line-clamp-2">
|
||||
<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 group-hover:text-accent-foreground/70">
|
||||
<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">
|
||||
<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;
|
||||
|
|
@ -183,89 +149,11 @@ export function KnowledgeFilterList({
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{selectedFilter?.id === filter.id && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="px-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onFilterSelect(null);
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4 flex-shrink-0 opacity-0 group-hover:opacity-100 text-muted-foreground" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
{/* Create Filter Dialog */}
|
||||
<Dialog open={showCreateModal} onOpenChange={setShowCreateModal}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create a new knowledge filter</DialogTitle>
|
||||
<DialogDescription>
|
||||
Save a reusable filter to quickly scope searches across your
|
||||
knowledge base.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-2 space-y-2">
|
||||
<div>
|
||||
<Label htmlFor="filter-name" className="font-medium mb-2 gap-1">
|
||||
Name<span className="text-red-400">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="filter-name"
|
||||
type="text"
|
||||
placeholder="Enter filter name"
|
||||
value={createName}
|
||||
onChange={(e) => setCreateName(e.target.value)}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="filter-description" className="font-medium mb-2">
|
||||
Description (optional)
|
||||
</Label>
|
||||
<Textarea
|
||||
id="filter-description"
|
||||
placeholder="Brief description of this filter"
|
||||
value={createDescription}
|
||||
onChange={(e) => setCreateDescription(e.target.value)}
|
||||
className="mt-1"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancelCreate}
|
||||
disabled={creating}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCreateFilter}
|
||||
disabled={!createName.trim() || creating}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{creating ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-4 w-4" />
|
||||
Create Filter
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{/* Create flow moved to panel create mode */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { X, Edit3, Save, RefreshCw } from "lucide-react";
|
||||
import { X, Save, RefreshCw } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -12,7 +12,13 @@ import { Slider } from "@/components/ui/slider";
|
|||
import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context";
|
||||
import { useDeleteFilter } from "@/app/api/mutations/useDeleteFilter";
|
||||
import { useUpdateFilter } from "@/app/api/mutations/useUpdateFilter";
|
||||
import { useCreateFilter } from "@/app/api/mutations/useCreateFilter";
|
||||
import { useGetSearchAggregations } from "@/src/app/api/queries/useGetSearchAggregations";
|
||||
import {
|
||||
FilterColor,
|
||||
FilterIconPopover,
|
||||
IconKey,
|
||||
} from "@/components/filter-icon-popover";
|
||||
|
||||
interface FacetBucket {
|
||||
key: string;
|
||||
|
|
@ -26,6 +32,16 @@ interface AvailableFacets {
|
|||
connector_types: FacetBucket[];
|
||||
}
|
||||
|
||||
export const filterAccentClasses: Record<FilterColor, string> = {
|
||||
zinc: "bg-muted text-muted-foreground",
|
||||
pink: "bg-accent-pink text-accent-pink-foreground",
|
||||
purple: "bg-accent-purple text-accent-purple-foreground",
|
||||
indigo: "bg-accent-indigo text-accent-indigo-foreground",
|
||||
emerald: "bg-accent-emerald text-accent-emerald-foreground",
|
||||
amber: "bg-accent-amber text-accent-amber-foreground",
|
||||
red: "bg-accent-red text-accent-red-foreground",
|
||||
};
|
||||
|
||||
export function KnowledgeFilterPanel() {
|
||||
const {
|
||||
selectedFilter,
|
||||
|
|
@ -33,15 +49,18 @@ export function KnowledgeFilterPanel() {
|
|||
setSelectedFilter,
|
||||
isPanelOpen,
|
||||
closePanelOnly,
|
||||
createMode,
|
||||
endCreateMode,
|
||||
} = useKnowledgeFilter();
|
||||
const deleteFilterMutation = useDeleteFilter();
|
||||
const updateFilterMutation = useUpdateFilter();
|
||||
const createFilterMutation = useCreateFilter();
|
||||
|
||||
// Edit mode states
|
||||
const [isEditingMeta, setIsEditingMeta] = useState(false);
|
||||
const [editingName, setEditingName] = useState("");
|
||||
const [editingDescription, setEditingDescription] = useState("");
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [color, setColor] = useState<FilterColor>("zinc");
|
||||
const [iconKey, setIconKey] = useState<IconKey>("filter");
|
||||
|
||||
// Filter configuration states (mirror search page exactly)
|
||||
const [query, setQuery] = useState("");
|
||||
|
|
@ -62,7 +81,7 @@ export function KnowledgeFilterPanel() {
|
|||
connector_types: [],
|
||||
});
|
||||
|
||||
// Load current filter data into controls
|
||||
// Load current filter data into controls when a filter is selected
|
||||
useEffect(() => {
|
||||
if (selectedFilter && parsedFilterData) {
|
||||
setQuery(parsedFilterData.query || "");
|
||||
|
|
@ -84,11 +103,27 @@ export function KnowledgeFilterPanel() {
|
|||
setSelectedFilters(processedFilters);
|
||||
setResultLimit(parsedFilterData.limit || 10);
|
||||
setScoreThreshold(parsedFilterData.scoreThreshold || 0);
|
||||
setEditingName(selectedFilter.name);
|
||||
setEditingDescription(selectedFilter.description || "");
|
||||
setName(selectedFilter.name);
|
||||
setDescription(selectedFilter.description || "");
|
||||
setColor(parsedFilterData.color);
|
||||
setIconKey(parsedFilterData.icon);
|
||||
}
|
||||
}, [selectedFilter, parsedFilterData]);
|
||||
|
||||
// Initialize defaults when entering create mode
|
||||
useEffect(() => {
|
||||
if (createMode && parsedFilterData) {
|
||||
setQuery(parsedFilterData.query || "");
|
||||
setSelectedFilters(parsedFilterData.filters);
|
||||
setResultLimit(parsedFilterData.limit || 10);
|
||||
setScoreThreshold(parsedFilterData.scoreThreshold || 0);
|
||||
setName("");
|
||||
setDescription("");
|
||||
setColor(parsedFilterData.color);
|
||||
setIconKey(parsedFilterData.icon);
|
||||
}
|
||||
}, [createMode, parsedFilterData]);
|
||||
|
||||
// Load available facets using search aggregations hook
|
||||
const { data: aggregations } = useGetSearchAggregations("*", 1, 0, {
|
||||
enabled: isPanelOpen,
|
||||
|
|
@ -108,8 +143,8 @@ export function KnowledgeFilterPanel() {
|
|||
setAvailableFacets(facets);
|
||||
}, [aggregations]);
|
||||
|
||||
// Don't render if panel is closed or no filter selected
|
||||
if (!isPanelOpen || !selectedFilter || !parsedFilterData) return null;
|
||||
// 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
|
||||
|
|
@ -130,58 +165,42 @@ export function KnowledgeFilterPanel() {
|
|||
});
|
||||
};
|
||||
|
||||
const handleEditMeta = () => {
|
||||
setIsEditingMeta(true);
|
||||
};
|
||||
|
||||
const handleCancelEdit = () => {
|
||||
setIsEditingMeta(false);
|
||||
setEditingName(selectedFilter.name);
|
||||
setEditingDescription(selectedFilter.description || "");
|
||||
};
|
||||
|
||||
const handleSaveMeta = async () => {
|
||||
if (!editingName.trim()) return;
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const result = await updateFilterMutation.mutateAsync({
|
||||
id: selectedFilter.id,
|
||||
name: editingName.trim(),
|
||||
description: editingDescription.trim(),
|
||||
});
|
||||
|
||||
if (result.success && result.filter) {
|
||||
setSelectedFilter(result.filter);
|
||||
setIsEditingMeta(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating filter:", error);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveConfiguration = async () => {
|
||||
if (!name.trim()) return;
|
||||
const filterData = {
|
||||
query,
|
||||
filters: selectedFilters,
|
||||
limit: resultLimit,
|
||||
scoreThreshold,
|
||||
color,
|
||||
icon: iconKey,
|
||||
};
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const result = await updateFilterMutation.mutateAsync({
|
||||
id: selectedFilter.id,
|
||||
queryData: JSON.stringify(filterData),
|
||||
});
|
||||
|
||||
if (result.success && result.filter) {
|
||||
setSelectedFilter(result.filter);
|
||||
if (createMode) {
|
||||
const result = await createFilterMutation.mutateAsync({
|
||||
name: name.trim(),
|
||||
description: description.trim(),
|
||||
queryData: JSON.stringify(filterData),
|
||||
});
|
||||
if (result.success && result.filter) {
|
||||
setSelectedFilter(result.filter);
|
||||
endCreateMode();
|
||||
}
|
||||
} else if (selectedFilter) {
|
||||
const result = await updateFilterMutation.mutateAsync({
|
||||
id: selectedFilter.id,
|
||||
name: name.trim(),
|
||||
description: description.trim(),
|
||||
queryData: JSON.stringify(filterData),
|
||||
});
|
||||
if (result.success && result.filter) {
|
||||
setSelectedFilter(result.filter);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating filter configuration:", error);
|
||||
console.error("Error saving knowledge filter:", error);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
|
@ -208,6 +227,7 @@ export function KnowledgeFilterPanel() {
|
|||
};
|
||||
|
||||
const handleDeleteFilter = async () => {
|
||||
if (!selectedFilter) return;
|
||||
const result = await deleteFilterMutation.mutateAsync({
|
||||
id: selectedFilter.id,
|
||||
});
|
||||
|
|
@ -238,81 +258,40 @@ export function KnowledgeFilterPanel() {
|
|||
|
||||
<CardContent className="space-y-6">
|
||||
{/* Filter Name and Description */}
|
||||
<div className="space-y-4">
|
||||
{isEditingMeta ? (
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="filter-name">Name</Label>
|
||||
<Input
|
||||
id="filter-name"
|
||||
value={editingName}
|
||||
onChange={(e) => setEditingName(e.target.value)}
|
||||
placeholder="Filter name"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="filter-description">Description</Label>
|
||||
<Textarea
|
||||
id="filter-description"
|
||||
value={editingDescription}
|
||||
onChange={(e) => setEditingDescription(e.target.value)}
|
||||
placeholder="Optional description"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleSaveMeta}
|
||||
disabled={!editingName.trim() || isSaving}
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
>
|
||||
<Save className="h-3 w-3 mr-1" />
|
||||
{isSaving ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCancelEdit}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="filter-name">Filter name</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<FilterIconPopover
|
||||
color={color}
|
||||
iconKey={iconKey}
|
||||
onColorChange={setColor}
|
||||
onIconChange={setIconKey}
|
||||
/>
|
||||
<Input
|
||||
id="filter-name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Filter name"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-lg">
|
||||
{selectedFilter.name}
|
||||
</h3>
|
||||
{selectedFilter.description && (
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{selectedFilter.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleEditMeta}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<Edit3 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Created {formatDate(selectedFilter.created_at)}
|
||||
{selectedFilter.updated_at !== selectedFilter.created_at && (
|
||||
<span>
|
||||
{" "}
|
||||
• Updated {formatDate(selectedFilter.updated_at)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!createMode && selectedFilter?.created_at && (
|
||||
<div className="space-y-2 text-xs text-right text-muted-foreground">
|
||||
<span className="text-placeholder-foreground">Created</span>{" "}
|
||||
{formatDate(selectedFilter.created_at)}
|
||||
</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"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search Query */}
|
||||
|
|
@ -504,13 +483,15 @@ export function KnowledgeFilterPanel() {
|
|||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
onClick={handleDeleteFilter}
|
||||
>
|
||||
Delete Filter
|
||||
</Button>
|
||||
{!createMode && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
onClick={handleDeleteFilter}
|
||||
>
|
||||
Delete Filter
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -456,20 +456,24 @@ export function Navigation({
|
|||
</div>
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
<DropdownMenuTrigger disabled={loading || deleteSessionMutation.isPending} asChild>
|
||||
<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"
|
||||
title="More options"
|
||||
disabled={
|
||||
loading || deleteSessionMutation.isPending
|
||||
}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<EllipsisVertical className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
side="bottom"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export interface InputProps
|
|||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, inputClassName, icon, type, placeholder, ...props }, ref) => {
|
||||
const [hasValue, setHasValue] = React.useState(
|
||||
Boolean(props.value || props.defaultValue),
|
||||
Boolean(props.value || props.defaultValue)
|
||||
);
|
||||
const [showPassword, setShowPassword] = React.useState(false);
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||
<label
|
||||
className={cn(
|
||||
"relative block h-fit w-full text-sm group",
|
||||
icon ? className : "",
|
||||
icon ? className : ""
|
||||
)}
|
||||
>
|
||||
{icon && (
|
||||
|
|
@ -43,10 +43,10 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||
type={type === "password" && showPassword ? "text" : type}
|
||||
placeholder={placeholder}
|
||||
className={cn(
|
||||
"primary-input !placeholder-transparent",
|
||||
"primary-input",
|
||||
icon && "pl-9",
|
||||
type === "password" && "!pr-8",
|
||||
icon ? inputClassName : className,
|
||||
icon ? inputClassName : className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
|
|
@ -66,18 +66,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||
)}
|
||||
</button>
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"pointer-events-none absolute top-1/2 -translate-y-1/2 pl-px text-placeholder-foreground font-mono",
|
||||
icon ? "left-9" : "left-3",
|
||||
hasValue && "hidden",
|
||||
)}
|
||||
>
|
||||
{placeholder}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Input.displayName = "Input";
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const Switch = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
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-input",
|
||||
"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
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -19,7 +19,7 @@ const Switch = React.forwardRef<
|
|||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
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"
|
||||
"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>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ interface UpdateFlowSettingVariables {
|
|||
llm_model?: string;
|
||||
system_prompt?: string;
|
||||
embedding_model?: string;
|
||||
doclingPresets?: string;
|
||||
table_structure?: boolean;
|
||||
ocr?: boolean;
|
||||
picture_descriptions?: boolean;
|
||||
chunk_size?: number;
|
||||
chunk_overlap?: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ export const useGetSearchQuery = (
|
|||
|
||||
const queryResult = useQuery(
|
||||
{
|
||||
queryKey: ["search", effectiveQuery],
|
||||
queryKey: ["search", queryData],
|
||||
placeholderData: (prev) => prev,
|
||||
queryFn: getFiles,
|
||||
...options,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ export interface KnowledgeSettings {
|
|||
embedding_model?: string;
|
||||
chunk_size?: number;
|
||||
chunk_overlap?: number;
|
||||
doclingPresets?: string;
|
||||
table_structure?: boolean;
|
||||
ocr?: boolean;
|
||||
picture_descriptions?: boolean;
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
|
|
@ -35,6 +37,7 @@ export interface Settings {
|
|||
separator?: string;
|
||||
embeddingModel?: string;
|
||||
};
|
||||
localhost_url?: string;
|
||||
}
|
||||
|
||||
export const useGetSettingsQuery = (
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { useTask } from "@/contexts/task-context";
|
|||
import { useLoadingStore } from "@/stores/loadingStore";
|
||||
import { useGetNudgesQuery } from "../api/queries/useGetNudgesQuery";
|
||||
import Nudges from "./nudges";
|
||||
import { filterAccentClasses } from "@/components/knowledge-filter-panel";
|
||||
|
||||
interface Message {
|
||||
role: "user" | "assistant";
|
||||
|
|
@ -194,7 +195,7 @@ function ChatPage() {
|
|||
"Upload failed with status:",
|
||||
response.status,
|
||||
"Response:",
|
||||
errorText,
|
||||
errorText
|
||||
);
|
||||
throw new Error("Failed to process document");
|
||||
}
|
||||
|
|
@ -448,7 +449,7 @@ function ChatPage() {
|
|||
console.log(
|
||||
"Loading conversation with",
|
||||
conversationData.messages.length,
|
||||
"messages",
|
||||
"messages"
|
||||
);
|
||||
// Convert backend message format to frontend Message interface
|
||||
const convertedMessages: Message[] = conversationData.messages.map(
|
||||
|
|
@ -576,7 +577,7 @@ function ChatPage() {
|
|||
) === "string"
|
||||
? toolCall.function?.arguments || toolCall.arguments
|
||||
: JSON.stringify(
|
||||
toolCall.function?.arguments || toolCall.arguments,
|
||||
toolCall.function?.arguments || toolCall.arguments
|
||||
),
|
||||
result: toolCall.result,
|
||||
status: "completed",
|
||||
|
|
@ -595,7 +596,7 @@ function ChatPage() {
|
|||
}
|
||||
|
||||
return message;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
setMessages(convertedMessages);
|
||||
|
|
@ -684,7 +685,7 @@ function ChatPage() {
|
|||
console.log(
|
||||
"Chat page received file upload error event:",
|
||||
filename,
|
||||
error,
|
||||
error
|
||||
);
|
||||
|
||||
// Replace the last message with error message
|
||||
|
|
@ -698,37 +699,37 @@ function ChatPage() {
|
|||
|
||||
window.addEventListener(
|
||||
"fileUploadStart",
|
||||
handleFileUploadStart as EventListener,
|
||||
handleFileUploadStart as EventListener
|
||||
);
|
||||
window.addEventListener(
|
||||
"fileUploaded",
|
||||
handleFileUploaded as EventListener,
|
||||
handleFileUploaded as EventListener
|
||||
);
|
||||
window.addEventListener(
|
||||
"fileUploadComplete",
|
||||
handleFileUploadComplete as EventListener,
|
||||
handleFileUploadComplete as EventListener
|
||||
);
|
||||
window.addEventListener(
|
||||
"fileUploadError",
|
||||
handleFileUploadError as EventListener,
|
||||
handleFileUploadError as EventListener
|
||||
);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(
|
||||
"fileUploadStart",
|
||||
handleFileUploadStart as EventListener,
|
||||
handleFileUploadStart as EventListener
|
||||
);
|
||||
window.removeEventListener(
|
||||
"fileUploaded",
|
||||
handleFileUploaded as EventListener,
|
||||
handleFileUploaded as EventListener
|
||||
);
|
||||
window.removeEventListener(
|
||||
"fileUploadComplete",
|
||||
handleFileUploadComplete as EventListener,
|
||||
handleFileUploadComplete as EventListener
|
||||
);
|
||||
window.removeEventListener(
|
||||
"fileUploadError",
|
||||
handleFileUploadError as EventListener,
|
||||
handleFileUploadError as EventListener
|
||||
);
|
||||
};
|
||||
}, [endpoint, setPreviousResponseIds]);
|
||||
|
|
@ -755,7 +756,7 @@ function ChatPage() {
|
|||
}, [isFilterDropdownOpen]);
|
||||
|
||||
const { data: nudges = [], cancel: cancelNudges } = useGetNudgesQuery(
|
||||
previousResponseIds[endpoint],
|
||||
previousResponseIds[endpoint]
|
||||
);
|
||||
|
||||
const handleSSEStream = async (userMessage: Message) => {
|
||||
|
|
@ -860,7 +861,7 @@ function ChatPage() {
|
|||
console.log(
|
||||
"Received chunk:",
|
||||
chunk.type || chunk.object,
|
||||
chunk,
|
||||
chunk
|
||||
);
|
||||
|
||||
// Extract response ID if present
|
||||
|
|
@ -876,14 +877,14 @@ function ChatPage() {
|
|||
if (chunk.delta.function_call) {
|
||||
console.log(
|
||||
"Function call in delta:",
|
||||
chunk.delta.function_call,
|
||||
chunk.delta.function_call
|
||||
);
|
||||
|
||||
// Check if this is a new function call
|
||||
if (chunk.delta.function_call.name) {
|
||||
console.log(
|
||||
"New function call:",
|
||||
chunk.delta.function_call.name,
|
||||
chunk.delta.function_call.name
|
||||
);
|
||||
const functionCall: FunctionCall = {
|
||||
name: chunk.delta.function_call.name,
|
||||
|
|
@ -899,7 +900,7 @@ function ChatPage() {
|
|||
else if (chunk.delta.function_call.arguments) {
|
||||
console.log(
|
||||
"Function call arguments delta:",
|
||||
chunk.delta.function_call.arguments,
|
||||
chunk.delta.function_call.arguments
|
||||
);
|
||||
const lastFunctionCall =
|
||||
currentFunctionCalls[currentFunctionCalls.length - 1];
|
||||
|
|
@ -911,14 +912,14 @@ function ChatPage() {
|
|||
chunk.delta.function_call.arguments;
|
||||
console.log(
|
||||
"Accumulated arguments:",
|
||||
lastFunctionCall.argumentsString,
|
||||
lastFunctionCall.argumentsString
|
||||
);
|
||||
|
||||
// Try to parse arguments if they look complete
|
||||
if (lastFunctionCall.argumentsString.includes("}")) {
|
||||
try {
|
||||
const parsed = JSON.parse(
|
||||
lastFunctionCall.argumentsString,
|
||||
lastFunctionCall.argumentsString
|
||||
);
|
||||
lastFunctionCall.arguments = parsed;
|
||||
lastFunctionCall.status = "completed";
|
||||
|
|
@ -926,7 +927,7 @@ function ChatPage() {
|
|||
} catch (e) {
|
||||
console.log(
|
||||
"Arguments not yet complete or invalid JSON:",
|
||||
e,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -959,7 +960,7 @@ function ChatPage() {
|
|||
else if (toolCall.function.arguments) {
|
||||
console.log(
|
||||
"Tool call arguments delta:",
|
||||
toolCall.function.arguments,
|
||||
toolCall.function.arguments
|
||||
);
|
||||
const lastFunctionCall =
|
||||
currentFunctionCalls[
|
||||
|
|
@ -973,7 +974,7 @@ function ChatPage() {
|
|||
toolCall.function.arguments;
|
||||
console.log(
|
||||
"Accumulated tool arguments:",
|
||||
lastFunctionCall.argumentsString,
|
||||
lastFunctionCall.argumentsString
|
||||
);
|
||||
|
||||
// Try to parse arguments if they look complete
|
||||
|
|
@ -982,7 +983,7 @@ function ChatPage() {
|
|||
) {
|
||||
try {
|
||||
const parsed = JSON.parse(
|
||||
lastFunctionCall.argumentsString,
|
||||
lastFunctionCall.argumentsString
|
||||
);
|
||||
lastFunctionCall.arguments = parsed;
|
||||
lastFunctionCall.status = "completed";
|
||||
|
|
@ -990,7 +991,7 @@ function ChatPage() {
|
|||
} catch (e) {
|
||||
console.log(
|
||||
"Tool arguments not yet complete or invalid JSON:",
|
||||
e,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1022,7 +1023,7 @@ function ChatPage() {
|
|||
console.log(
|
||||
"Error parsing function call on finish:",
|
||||
fc,
|
||||
e,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1038,12 +1039,12 @@ function ChatPage() {
|
|||
console.log(
|
||||
"🟢 CREATING function call (added):",
|
||||
chunk.item.id,
|
||||
chunk.item.tool_name || chunk.item.name,
|
||||
chunk.item.tool_name || chunk.item.name
|
||||
);
|
||||
|
||||
// Try to find an existing pending call to update (created by earlier deltas)
|
||||
let existing = currentFunctionCalls.find(
|
||||
(fc) => fc.id === chunk.item.id,
|
||||
(fc) => fc.id === chunk.item.id
|
||||
);
|
||||
if (!existing) {
|
||||
existing = [...currentFunctionCalls]
|
||||
|
|
@ -1052,7 +1053,7 @@ function ChatPage() {
|
|||
(fc) =>
|
||||
fc.status === "pending" &&
|
||||
!fc.id &&
|
||||
fc.name === (chunk.item.tool_name || chunk.item.name),
|
||||
fc.name === (chunk.item.tool_name || chunk.item.name)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1065,7 +1066,7 @@ function ChatPage() {
|
|||
chunk.item.inputs || existing.arguments;
|
||||
console.log(
|
||||
"🟢 UPDATED existing pending function call with id:",
|
||||
existing.id,
|
||||
existing.id
|
||||
);
|
||||
} else {
|
||||
const functionCall: FunctionCall = {
|
||||
|
|
@ -1083,7 +1084,7 @@ function ChatPage() {
|
|||
currentFunctionCalls.map((fc) => ({
|
||||
id: fc.id,
|
||||
name: fc.name,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1094,7 +1095,7 @@ function ChatPage() {
|
|||
) {
|
||||
console.log(
|
||||
"Function args delta (Realtime API):",
|
||||
chunk.delta,
|
||||
chunk.delta
|
||||
);
|
||||
const lastFunctionCall =
|
||||
currentFunctionCalls[currentFunctionCalls.length - 1];
|
||||
|
|
@ -1105,7 +1106,7 @@ function ChatPage() {
|
|||
lastFunctionCall.argumentsString += chunk.delta || "";
|
||||
console.log(
|
||||
"Accumulated arguments (Realtime API):",
|
||||
lastFunctionCall.argumentsString,
|
||||
lastFunctionCall.argumentsString
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1116,26 +1117,26 @@ function ChatPage() {
|
|||
) {
|
||||
console.log(
|
||||
"Function args done (Realtime API):",
|
||||
chunk.arguments,
|
||||
chunk.arguments
|
||||
);
|
||||
const lastFunctionCall =
|
||||
currentFunctionCalls[currentFunctionCalls.length - 1];
|
||||
if (lastFunctionCall) {
|
||||
try {
|
||||
lastFunctionCall.arguments = JSON.parse(
|
||||
chunk.arguments || "{}",
|
||||
chunk.arguments || "{}"
|
||||
);
|
||||
lastFunctionCall.status = "completed";
|
||||
console.log(
|
||||
"Parsed function arguments (Realtime API):",
|
||||
lastFunctionCall.arguments,
|
||||
lastFunctionCall.arguments
|
||||
);
|
||||
} catch (e) {
|
||||
lastFunctionCall.arguments = { raw: chunk.arguments };
|
||||
lastFunctionCall.status = "error";
|
||||
console.log(
|
||||
"Error parsing function arguments (Realtime API):",
|
||||
e,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1149,14 +1150,14 @@ function ChatPage() {
|
|||
console.log(
|
||||
"🔵 UPDATING function call (done):",
|
||||
chunk.item.id,
|
||||
chunk.item.tool_name || chunk.item.name,
|
||||
chunk.item.tool_name || chunk.item.name
|
||||
);
|
||||
console.log(
|
||||
"🔵 Looking for existing function calls:",
|
||||
currentFunctionCalls.map((fc) => ({
|
||||
id: fc.id,
|
||||
name: fc.name,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
|
||||
// Find existing function call by ID or name
|
||||
|
|
@ -1164,14 +1165,14 @@ function ChatPage() {
|
|||
(fc) =>
|
||||
fc.id === chunk.item.id ||
|
||||
fc.name === chunk.item.tool_name ||
|
||||
fc.name === chunk.item.name,
|
||||
fc.name === chunk.item.name
|
||||
);
|
||||
|
||||
if (functionCall) {
|
||||
console.log(
|
||||
"🔵 FOUND existing function call, updating:",
|
||||
functionCall.id,
|
||||
functionCall.name,
|
||||
functionCall.name
|
||||
);
|
||||
// Update existing function call with completion data
|
||||
functionCall.status =
|
||||
|
|
@ -1194,7 +1195,7 @@ function ChatPage() {
|
|||
"🔴 WARNING: Could not find existing function call to update:",
|
||||
chunk.item.id,
|
||||
chunk.item.tool_name,
|
||||
chunk.item.name,
|
||||
chunk.item.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1215,7 +1216,7 @@ function ChatPage() {
|
|||
fc.name === chunk.item.name ||
|
||||
fc.name === chunk.item.type ||
|
||||
fc.name.includes(chunk.item.type.replace("_call", "")) ||
|
||||
chunk.item.type.includes(fc.name),
|
||||
chunk.item.type.includes(fc.name)
|
||||
);
|
||||
|
||||
if (functionCall) {
|
||||
|
|
@ -1259,12 +1260,12 @@ function ChatPage() {
|
|||
"🟡 CREATING tool call (added):",
|
||||
chunk.item.id,
|
||||
chunk.item.tool_name || chunk.item.name,
|
||||
chunk.item.type,
|
||||
chunk.item.type
|
||||
);
|
||||
|
||||
// Dedupe by id or pending with same name
|
||||
let existing = currentFunctionCalls.find(
|
||||
(fc) => fc.id === chunk.item.id,
|
||||
(fc) => fc.id === chunk.item.id
|
||||
);
|
||||
if (!existing) {
|
||||
existing = [...currentFunctionCalls]
|
||||
|
|
@ -1276,7 +1277,7 @@ function ChatPage() {
|
|||
fc.name ===
|
||||
(chunk.item.tool_name ||
|
||||
chunk.item.name ||
|
||||
chunk.item.type),
|
||||
chunk.item.type)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1292,7 +1293,7 @@ function ChatPage() {
|
|||
chunk.item.inputs || existing.arguments;
|
||||
console.log(
|
||||
"🟡 UPDATED existing pending tool call with id:",
|
||||
existing.id,
|
||||
existing.id
|
||||
);
|
||||
} else {
|
||||
const functionCall = {
|
||||
|
|
@ -1313,7 +1314,7 @@ function ChatPage() {
|
|||
id: fc.id,
|
||||
name: fc.name,
|
||||
type: fc.type,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1591,7 +1592,7 @@ function ChatPage() {
|
|||
|
||||
const handleForkConversation = (
|
||||
messageIndex: number,
|
||||
event?: React.MouseEvent,
|
||||
event?: React.MouseEvent
|
||||
) => {
|
||||
// Prevent any default behavior and stop event propagation
|
||||
if (event) {
|
||||
|
|
@ -1656,7 +1657,7 @@ function ChatPage() {
|
|||
|
||||
const renderFunctionCalls = (
|
||||
functionCalls: FunctionCall[],
|
||||
messageIndex?: number,
|
||||
messageIndex?: number
|
||||
) => {
|
||||
if (!functionCalls || functionCalls.length === 0) return null;
|
||||
|
||||
|
|
@ -2024,7 +2025,7 @@ function ChatPage() {
|
|||
<div className="flex-1 min-w-0">
|
||||
{renderFunctionCalls(
|
||||
message.functionCalls || [],
|
||||
index,
|
||||
index
|
||||
)}
|
||||
<MarkdownRenderer chatMessage={message.content} />
|
||||
</div>
|
||||
|
|
@ -2053,7 +2054,7 @@ function ChatPage() {
|
|||
<div className="flex-1">
|
||||
{renderFunctionCalls(
|
||||
streamingMessage.functionCalls,
|
||||
messages.length,
|
||||
messages.length
|
||||
)}
|
||||
<MarkdownRenderer
|
||||
chatMessage={streamingMessage.content}
|
||||
|
|
@ -2115,9 +2116,7 @@ function ChatPage() {
|
|||
<div className="flex items-center gap-2 px-4 pt-3 pb-1">
|
||||
<span
|
||||
className={`inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-colors ${
|
||||
isFilterHighlighted
|
||||
? "bg-blue-500/40 text-blue-300 ring-2 ring-blue-400/50"
|
||||
: "bg-blue-500/20 text-blue-400"
|
||||
filterAccentClasses[parsedFilterData?.color || "zinc"]
|
||||
}`}
|
||||
>
|
||||
@filter:{selectedFilter.name}
|
||||
|
|
@ -2127,7 +2126,7 @@ function ChatPage() {
|
|||
setSelectedFilter(null);
|
||||
setIsFilterHighlighted(false);
|
||||
}}
|
||||
className="ml-1 hover:bg-blue-500/30 rounded-full p-0.5"
|
||||
className="ml-1 rounded-full p-0.5"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
|
|
@ -2196,7 +2195,7 @@ function ChatPage() {
|
|||
const filteredFilters = availableFilters.filter((filter) =>
|
||||
filter.name
|
||||
.toLowerCase()
|
||||
.includes(filterSearchTerm.toLowerCase()),
|
||||
.includes(filterSearchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
if (e.key === "Escape") {
|
||||
|
|
@ -2214,7 +2213,7 @@ function ChatPage() {
|
|||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setSelectedFilterIndex((prev) =>
|
||||
prev < filteredFilters.length - 1 ? prev + 1 : 0,
|
||||
prev < filteredFilters.length - 1 ? prev + 1 : 0
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -2222,7 +2221,7 @@ function ChatPage() {
|
|||
if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setSelectedFilterIndex((prev) =>
|
||||
prev > 0 ? prev - 1 : filteredFilters.length - 1,
|
||||
prev > 0 ? prev - 1 : filteredFilters.length - 1
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -2240,7 +2239,7 @@ function ChatPage() {
|
|||
) {
|
||||
e.preventDefault();
|
||||
handleFilterSelect(
|
||||
filteredFilters[selectedFilterIndex],
|
||||
filteredFilters[selectedFilterIndex]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -2259,7 +2258,7 @@ function ChatPage() {
|
|||
) {
|
||||
e.preventDefault();
|
||||
handleFilterSelect(
|
||||
filteredFilters[selectedFilterIndex],
|
||||
filteredFilters[selectedFilterIndex]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -2339,7 +2338,7 @@ function ChatPage() {
|
|||
.filter((filter) =>
|
||||
filter.name
|
||||
.toLowerCase()
|
||||
.includes(filterSearchTerm.toLowerCase()),
|
||||
.includes(filterSearchTerm.toLowerCase())
|
||||
)
|
||||
.map((filter, index) => (
|
||||
<button
|
||||
|
|
@ -2365,7 +2364,7 @@ function ChatPage() {
|
|||
{availableFilters.filter((filter) =>
|
||||
filter.name
|
||||
.toLowerCase()
|
||||
.includes(filterSearchTerm.toLowerCase()),
|
||||
.includes(filterSearchTerm.toLowerCase())
|
||||
).length === 0 &&
|
||||
filterSearchTerm && (
|
||||
<div className="px-2 py-3 text-sm text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -27,41 +27,30 @@
|
|||
--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-emerald-foreground: 161.4 93.5% 30.4%;
|
||||
--accent-pink-foreground: 333.3 71.4% 50.6%;
|
||||
--accent-amber-foreground: 26 90.5% 37.1%;
|
||||
|
||||
/* Status Colors */
|
||||
--status-red: #ef4444;
|
||||
--status-yellow: #eab308;
|
||||
--status-green: #4ade80;
|
||||
--status-blue: #2563eb;
|
||||
--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 */
|
||||
|
||||
/* Component Colors */
|
||||
--component-icon: #d8598a;
|
||||
--flow-icon: #2f67d0;
|
||||
|
||||
/* Data Type Colors */
|
||||
--datatype-blue: 221.2 83.2% 53.3%;
|
||||
--datatype-blue-foreground: 214.3 94.6% 92.7%;
|
||||
--datatype-yellow: 40.6 96.1% 40.4%;
|
||||
--datatype-yellow-foreground: 54.9 96.7% 88%;
|
||||
--datatype-red: 0 72.2% 50.6%;
|
||||
--datatype-red-foreground: 0 93.3% 94.1%;
|
||||
--datatype-emerald: 161.4 93.5% 30.4%;
|
||||
--datatype-emerald-foreground: 149.3 80.4% 90%;
|
||||
--datatype-violet: 262.1 83.3% 57.8%;
|
||||
--datatype-violet-foreground: 251.4 91.3% 95.5%;
|
||||
|
||||
/* Warning */
|
||||
--warning: 48 96.6% 76.7%;
|
||||
--warning-foreground: 240 6% 10%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
|
|
@ -84,29 +73,25 @@
|
|||
--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-emerald-foreground: 158.1 64.4% 51.6%;
|
||||
--accent-pink-foreground: 328.6 85.5% 70.2%;
|
||||
--accent-amber-foreground: 45.9 96.7% 64.5%;
|
||||
|
||||
/* Dark mode data type colors */
|
||||
--datatype-blue: 211.7 96.4% 78.4%;
|
||||
--datatype-blue-foreground: 221.2 83.2% 53.3%;
|
||||
--datatype-yellow: 50.4 97.8% 63.5%;
|
||||
--datatype-yellow-foreground: 40.6 96.1% 40.4%;
|
||||
--datatype-red: 0 93.5% 81.8%;
|
||||
--datatype-red-foreground: 0 72.2% 50.6%;
|
||||
--datatype-emerald: 156.2 71.6% 66.9%;
|
||||
--datatype-emerald-foreground: 161.4 93.5% 30.4%;
|
||||
--datatype-violet: 252.5 94.7% 85.1%;
|
||||
--datatype-violet-foreground: 262.1 83.3% 57.8%;
|
||||
|
||||
--warning: 45.9 96.7% 64.5%;
|
||||
--warning-foreground: 240 6% 10%;
|
||||
--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 */
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { KnowledgeActionsDropdown } from "@/components/knowledge-actions-dropdow
|
|||
import { StatusBadge } from "@/components/ui/status-badge";
|
||||
import { DeleteConfirmationDialog } from "../../../components/confirmation-dialog";
|
||||
import { useDeleteDocument } from "../api/mutations/useDeleteDocument";
|
||||
import { filterAccentClasses } from "@/components/knowledge-filter-panel";
|
||||
|
||||
// Function to get the appropriate icon for a connector type
|
||||
function getSourceIcon(connectorType?: string) {
|
||||
|
|
@ -55,7 +56,7 @@ function SearchPage() {
|
|||
|
||||
const { data = [], isFetching } = useGetSearchQuery(
|
||||
parsedFilterData?.query || "*",
|
||||
parsedFilterData,
|
||||
parsedFilterData
|
||||
);
|
||||
|
||||
const handleTableSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
|
|
@ -80,7 +81,7 @@ function SearchPage() {
|
|||
return (
|
||||
taskFile.status !== "active" &&
|
||||
!backendFiles.some(
|
||||
(backendFile) => backendFile.filename === taskFile.filename,
|
||||
(backendFile) => backendFile.filename === taskFile.filename
|
||||
)
|
||||
);
|
||||
});
|
||||
|
|
@ -106,8 +107,8 @@ function SearchPage() {
|
|||
onClick={() => {
|
||||
router.push(
|
||||
`/knowledge/chunks?filename=${encodeURIComponent(
|
||||
data?.filename ?? "",
|
||||
)}`,
|
||||
data?.filename ?? ""
|
||||
)}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
|
@ -146,7 +147,7 @@ function SearchPage() {
|
|||
initialFlex: 0.5,
|
||||
cellRenderer: ({ value }: CustomCellRendererProps<File>) => {
|
||||
return (
|
||||
<span className="text-xs text-green-400 bg-green-400/20 px-2 py-1 rounded">
|
||||
<span className="text-xs text-accent-emerald-foreground bg-accent-emerald px-2 py-1 rounded">
|
||||
{value?.toFixed(2) ?? "-"}
|
||||
</span>
|
||||
);
|
||||
|
|
@ -201,7 +202,7 @@ function SearchPage() {
|
|||
try {
|
||||
// Delete each file individually since the API expects one filename at a time
|
||||
const deletePromises = selectedRows.map((row) =>
|
||||
deleteDocumentMutation.mutateAsync({ filename: row.filename }),
|
||||
deleteDocumentMutation.mutateAsync({ filename: row.filename })
|
||||
);
|
||||
|
||||
await Promise.all(deletePromises);
|
||||
|
|
@ -209,7 +210,7 @@ function SearchPage() {
|
|||
toast.success(
|
||||
`Successfully deleted ${selectedRows.length} document${
|
||||
selectedRows.length > 1 ? "s" : ""
|
||||
}`,
|
||||
}`
|
||||
);
|
||||
setSelectedRows([]);
|
||||
setShowBulkDeleteDialog(false);
|
||||
|
|
@ -222,7 +223,7 @@ function SearchPage() {
|
|||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to delete some documents",
|
||||
: "Failed to delete some documents"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -251,9 +252,13 @@ function SearchPage() {
|
|||
{/* Search Input Area */}
|
||||
<div className="flex-shrink-0 mb-6 xl:max-w-[75%]">
|
||||
<form className="flex gap-3">
|
||||
<div className="primary-input min-h-10 !flex items-center flex-nowrap gap-2 focus-within:border-foreground transition-colors !py-0">
|
||||
<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 bg-blue-500/20 text-blue-400 px-1.5 py-0.5 rounded max-w-[300px]">
|
||||
<div
|
||||
className={`flex items-center gap-1 h-full px-1.5 py-0.5 rounded max-w-[300px] ${
|
||||
filterAccentClasses[parsedFilterData?.color || "zinc"]
|
||||
}`}
|
||||
>
|
||||
<span className="truncate">{selectedFilter?.name}</span>
|
||||
<X
|
||||
aria-label="Remove filter"
|
||||
|
|
@ -263,7 +268,7 @@ function SearchPage() {
|
|||
</div>
|
||||
)}
|
||||
<input
|
||||
className="bg-transparent w-full h-full 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 placeholder:font-mono"
|
||||
name="search-query"
|
||||
id="search-query"
|
||||
type="text"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function OllamaOnboarding({
|
|||
sampleDataset: boolean;
|
||||
setSampleDataset: (dataset: boolean) => void;
|
||||
}) {
|
||||
const [endpoint, setEndpoint] = useState("http://localhost:11434");
|
||||
const [endpoint, setEndpoint] = useState(`http://localhost:11434`);
|
||||
const [showConnecting, setShowConnecting] = useState(false);
|
||||
const debouncedEndpoint = useDebouncedValue(endpoint, 500);
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ import {
|
|||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -112,7 +112,9 @@ function KnowledgeSourcesPage() {
|
|||
const [systemPrompt, setSystemPrompt] = useState<string>("");
|
||||
const [chunkSize, setChunkSize] = useState<number>(1024);
|
||||
const [chunkOverlap, setChunkOverlap] = useState<number>(50);
|
||||
const [processingMode, setProcessingMode] = useState<string>("standard");
|
||||
const [tableStructure, setTableStructure] = useState<boolean>(false);
|
||||
const [ocr, setOcr] = useState<boolean>(false);
|
||||
const [pictureDescriptions, setPictureDescriptions] = useState<boolean>(false);
|
||||
|
||||
// Fetch settings using React Query
|
||||
const { data: settings = {} } = useGetSettingsQuery({
|
||||
|
|
@ -195,12 +197,24 @@ function KnowledgeSourcesPage() {
|
|||
}
|
||||
}, [settings.knowledge?.chunk_overlap]);
|
||||
|
||||
// Sync processing mode with settings data
|
||||
// Sync docling settings with settings data
|
||||
useEffect(() => {
|
||||
if (settings.knowledge?.doclingPresets) {
|
||||
setProcessingMode(settings.knowledge.doclingPresets);
|
||||
if (settings.knowledge?.table_structure !== undefined) {
|
||||
setTableStructure(settings.knowledge.table_structure);
|
||||
}
|
||||
}, [settings.knowledge?.doclingPresets]);
|
||||
}, [settings.knowledge?.table_structure]);
|
||||
|
||||
useEffect(() => {
|
||||
if (settings.knowledge?.ocr !== undefined) {
|
||||
setOcr(settings.knowledge.ocr);
|
||||
}
|
||||
}, [settings.knowledge?.ocr]);
|
||||
|
||||
useEffect(() => {
|
||||
if (settings.knowledge?.picture_descriptions !== undefined) {
|
||||
setPictureDescriptions(settings.knowledge.picture_descriptions);
|
||||
}
|
||||
}, [settings.knowledge?.picture_descriptions]);
|
||||
|
||||
// Update model selection immediately
|
||||
const handleModelChange = (newModel: string) => {
|
||||
|
|
@ -231,11 +245,20 @@ function KnowledgeSourcesPage() {
|
|||
debouncedUpdate({ chunk_overlap: numValue });
|
||||
};
|
||||
|
||||
// Update processing mode
|
||||
const handleProcessingModeChange = (mode: string) => {
|
||||
setProcessingMode(mode);
|
||||
// Update the configuration setting (backend will also update the flow automatically)
|
||||
debouncedUpdate({ doclingPresets: mode });
|
||||
// Update docling settings
|
||||
const handleTableStructureChange = (checked: boolean) => {
|
||||
setTableStructure(checked);
|
||||
updateFlowSettingMutation.mutate({ table_structure: checked });
|
||||
};
|
||||
|
||||
const handleOcrChange = (checked: boolean) => {
|
||||
setOcr(checked);
|
||||
updateFlowSettingMutation.mutate({ ocr: checked });
|
||||
};
|
||||
|
||||
const handlePictureDescriptionsChange = (checked: boolean) => {
|
||||
setPictureDescriptions(checked);
|
||||
updateFlowSettingMutation.mutate({ picture_descriptions: checked });
|
||||
};
|
||||
|
||||
// Helper function to get connector icon
|
||||
|
|
@ -574,7 +597,9 @@ function KnowledgeSourcesPage() {
|
|||
// Only reset form values if the API call was successful
|
||||
setChunkSize(DEFAULT_KNOWLEDGE_SETTINGS.chunk_size);
|
||||
setChunkOverlap(DEFAULT_KNOWLEDGE_SETTINGS.chunk_overlap);
|
||||
setProcessingMode(DEFAULT_KNOWLEDGE_SETTINGS.processing_mode);
|
||||
setTableStructure(false);
|
||||
setOcr(false);
|
||||
setPictureDescriptions(false);
|
||||
closeDialog(); // Close after successful completion
|
||||
})
|
||||
.catch(error => {
|
||||
|
|
@ -1060,76 +1085,61 @@ function KnowledgeSourcesPage() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<Label className="text-base font-medium">Ingestion presets</Label>
|
||||
<RadioGroup
|
||||
value={processingMode}
|
||||
onValueChange={handleProcessingModeChange}
|
||||
className="space-y-3"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<RadioGroupItem value="standard" id="standard" />
|
||||
<div className="flex-1">
|
||||
<Label
|
||||
htmlFor="standard"
|
||||
className="text-base font-medium cursor-pointer"
|
||||
>
|
||||
No OCR
|
||||
</Label>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Fast ingest for documents with selectable text. Images are
|
||||
ignored.
|
||||
</div>
|
||||
<div className="">
|
||||
<div className="flex items-center justify-between py-3 border-b border-border">
|
||||
<div className="flex-1">
|
||||
<Label
|
||||
htmlFor="table-structure"
|
||||
className="text-base font-medium cursor-pointer pb-3"
|
||||
>
|
||||
Table Structure
|
||||
</Label>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Capture table structure during ingest.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<RadioGroupItem value="ocr" id="ocr" />
|
||||
<div className="flex-1">
|
||||
<Label
|
||||
htmlFor="ocr"
|
||||
className="text-base font-medium cursor-pointer"
|
||||
>
|
||||
OCR
|
||||
</Label>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Extracts text from images and scanned pages.
|
||||
</div>
|
||||
<Switch
|
||||
id="table-structure"
|
||||
checked={tableStructure}
|
||||
onCheckedChange={handleTableStructureChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-3 border-b border-border">
|
||||
<div className="flex-1">
|
||||
<Label
|
||||
htmlFor="ocr"
|
||||
className="text-base font-medium cursor-pointer pb-3"
|
||||
>
|
||||
OCR
|
||||
</Label>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Extracts text from images/PDFs. Ingest is slower when enabled.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<RadioGroupItem
|
||||
value="picture_description"
|
||||
id="picture_description"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<Label
|
||||
htmlFor="picture_description"
|
||||
className="text-base font-medium cursor-pointer"
|
||||
>
|
||||
OCR + Captions
|
||||
</Label>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Extracts text from images and scanned pages. Generates
|
||||
short image captions.
|
||||
</div>
|
||||
<Switch
|
||||
id="ocr"
|
||||
checked={ocr}
|
||||
onCheckedChange={handleOcrChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-3">
|
||||
<div className="flex-1">
|
||||
<Label
|
||||
htmlFor="picture-descriptions"
|
||||
className="text-base font-medium cursor-pointer pb-3"
|
||||
>
|
||||
Picture Descriptions
|
||||
</Label>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Adds captions for images. Ingest is slower when enabled.
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<RadioGroupItem value="VLM" id="VLM" />
|
||||
<div className="flex-1">
|
||||
<Label
|
||||
htmlFor="VLM"
|
||||
className="text-base font-medium cursor-pointer"
|
||||
>
|
||||
VLM
|
||||
</Label>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Extracts text from layout-aware parsing of text, tables,
|
||||
and sections.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<Switch
|
||||
id="picture-descriptions"
|
||||
checked={pictureDescriptions}
|
||||
onCheckedChange={handlePictureDescriptionsChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -16,27 +16,27 @@ interface StatusBadgeProps {
|
|||
const statusConfig = {
|
||||
processing: {
|
||||
label: "Processing",
|
||||
className: "text-muted-foreground dark:text-muted-foreground ",
|
||||
className: "text-muted-foreground ",
|
||||
},
|
||||
active: {
|
||||
label: "Active",
|
||||
className: "text-emerald-600 dark:text-emerald-400 ",
|
||||
className: "text-accent-emerald-foreground ",
|
||||
},
|
||||
unavailable: {
|
||||
label: "Unavailable",
|
||||
className: "text-red-600 dark:text-red-400 ",
|
||||
className: "text-accent-red-foreground ",
|
||||
},
|
||||
failed: {
|
||||
label: "Failed",
|
||||
className: "text-red-600 dark:text-red-400 ",
|
||||
className: "text-accent-red-foreground ",
|
||||
},
|
||||
hidden: {
|
||||
label: "Hidden",
|
||||
className: "text-zinc-400 dark:text-zinc-500 ",
|
||||
className: "text-muted-foreground ",
|
||||
},
|
||||
sync: {
|
||||
label: "Sync",
|
||||
className: "text-amber-700 dark:text-amber-300 underline",
|
||||
className: "text-accent-amber-foreground underline",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Switch = React.forwardRef<
|
||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
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-input",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
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"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
))
|
||||
Switch.displayName = SwitchPrimitives.Root.displayName
|
||||
|
||||
export { Switch }
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { FilterColor, IconKey } from "@/components/filter-icon-popover";
|
||||
import React, {
|
||||
createContext,
|
||||
type ReactNode,
|
||||
|
|
@ -27,6 +28,8 @@ export interface ParsedQueryData {
|
|||
};
|
||||
limit: number;
|
||||
scoreThreshold: number;
|
||||
color: FilterColor;
|
||||
icon: IconKey;
|
||||
}
|
||||
|
||||
interface KnowledgeFilterContextType {
|
||||
|
|
@ -38,6 +41,9 @@ interface KnowledgeFilterContextType {
|
|||
openPanel: () => void;
|
||||
closePanel: () => void;
|
||||
closePanelOnly: () => void;
|
||||
createMode: boolean;
|
||||
startCreateMode: () => void;
|
||||
endCreateMode: () => void;
|
||||
}
|
||||
|
||||
const KnowledgeFilterContext = createContext<
|
||||
|
|
@ -48,7 +54,7 @@ export function useKnowledgeFilter() {
|
|||
const context = useContext(KnowledgeFilterContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
"useKnowledgeFilter must be used within a KnowledgeFilterProvider",
|
||||
"useKnowledgeFilter must be used within a KnowledgeFilterProvider"
|
||||
);
|
||||
}
|
||||
return context;
|
||||
|
|
@ -66,11 +72,13 @@ export function KnowledgeFilterProvider({
|
|||
const [parsedFilterData, setParsedFilterData] =
|
||||
useState<ParsedQueryData | null>(null);
|
||||
const [isPanelOpen, setIsPanelOpen] = useState(false);
|
||||
const [createMode, setCreateMode] = useState(false);
|
||||
|
||||
const setSelectedFilter = (filter: KnowledgeFilter | null) => {
|
||||
setSelectedFilterState(filter);
|
||||
|
||||
if (filter) {
|
||||
setCreateMode(false);
|
||||
try {
|
||||
const parsed = JSON.parse(filter.query_data) as ParsedQueryData;
|
||||
setParsedFilterData(parsed);
|
||||
|
|
@ -96,6 +104,7 @@ export function KnowledgeFilterProvider({
|
|||
};
|
||||
|
||||
const closePanel = () => {
|
||||
setCreateMode(false);
|
||||
setSelectedFilter(null); // This will also close the panel
|
||||
};
|
||||
|
||||
|
|
@ -103,6 +112,30 @@ export function KnowledgeFilterProvider({
|
|||
setIsPanelOpen(false); // Close panel but keep filter selected
|
||||
};
|
||||
|
||||
const startCreateMode = () => {
|
||||
// Initialize defaults
|
||||
setCreateMode(true);
|
||||
setSelectedFilterState(null);
|
||||
setParsedFilterData({
|
||||
query: "",
|
||||
filters: {
|
||||
data_sources: ["*"],
|
||||
document_types: ["*"],
|
||||
owners: ["*"],
|
||||
connector_types: ["*"],
|
||||
},
|
||||
limit: 10,
|
||||
scoreThreshold: 0,
|
||||
color: "zinc",
|
||||
icon: "filter",
|
||||
});
|
||||
setIsPanelOpen(true);
|
||||
};
|
||||
|
||||
const endCreateMode = () => {
|
||||
setCreateMode(false);
|
||||
};
|
||||
|
||||
const value: KnowledgeFilterContextType = {
|
||||
selectedFilter,
|
||||
parsedFilterData,
|
||||
|
|
@ -112,6 +145,9 @@ export function KnowledgeFilterProvider({
|
|||
openPanel,
|
||||
closePanel,
|
||||
closePanelOnly,
|
||||
createMode,
|
||||
startCreateMode,
|
||||
endCreateMode,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ export const DEFAULT_AGENT_SETTINGS = {
|
|||
export const DEFAULT_KNOWLEDGE_SETTINGS = {
|
||||
chunk_size: 1000,
|
||||
chunk_overlap: 200,
|
||||
processing_mode: "standard"
|
||||
table_structure: false,
|
||||
ocr: false,
|
||||
picture_descriptions: false
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -109,14 +109,29 @@ const config = {
|
|||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
"accent-emerald-foreground": {
|
||||
DEFAULT: "hsl(var(--accent-emerald-foreground))",
|
||||
"accent-emerald": {
|
||||
DEFAULT: "hsl(var(--accent-emerald))",
|
||||
foreground: "hsl(var(--accent-emerald-foreground))",
|
||||
},
|
||||
"accent-pink-foreground": {
|
||||
DEFAULT: "hsl(var(--accent-pink-foreground))",
|
||||
"accent-pink": {
|
||||
DEFAULT: "hsl(var(--accent-pink))",
|
||||
foreground: "hsl(var(--accent-pink-foreground))",
|
||||
},
|
||||
"accent-amber-foreground": {
|
||||
DEFAULT: "hsl(var(--accent-amber-foreground))",
|
||||
"accent-amber": {
|
||||
DEFAULT: "hsl(var(--accent-amber))",
|
||||
foreground: "hsl(var(--accent-amber-foreground))",
|
||||
},
|
||||
"accent-purple": {
|
||||
DEFAULT: "hsl(var(--accent-purple))",
|
||||
foreground: "hsl(var(--accent-purple-foreground))",
|
||||
},
|
||||
"accent-indigo": {
|
||||
DEFAULT: "hsl(var(--accent-indigo))",
|
||||
foreground: "hsl(var(--accent-indigo-foreground))",
|
||||
},
|
||||
"accent-red": {
|
||||
DEFAULT: "hsl(var(--accent-red))",
|
||||
foreground: "hsl(var(--accent-red-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
|
|
@ -126,33 +141,9 @@ const config = {
|
|||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
"status-blue": "var(--status-blue)",
|
||||
"status-green": "var(--status-green)",
|
||||
"status-red": "var(--status-red)",
|
||||
"status-yellow": "var(--status-yellow)",
|
||||
"component-icon": "var(--component-icon)",
|
||||
"flow-icon": "var(--flow-icon)",
|
||||
"placeholder-foreground": "hsl(var(--placeholder-foreground))",
|
||||
"datatype-blue": {
|
||||
DEFAULT: "hsl(var(--datatype-blue))",
|
||||
foreground: "hsl(var(--datatype-blue-foreground))",
|
||||
},
|
||||
"datatype-yellow": {
|
||||
DEFAULT: "hsl(var(--datatype-yellow))",
|
||||
foreground: "hsl(var(--datatype-yellow-foreground))",
|
||||
},
|
||||
"datatype-red": {
|
||||
DEFAULT: "hsl(var(--datatype-red))",
|
||||
foreground: "hsl(var(--datatype-red-foreground))",
|
||||
},
|
||||
"datatype-emerald": {
|
||||
DEFAULT: "hsl(var(--datatype-emerald))",
|
||||
foreground: "hsl(var(--datatype-emerald-foreground))",
|
||||
},
|
||||
"datatype-violet": {
|
||||
DEFAULT: "hsl(var(--datatype-violet))",
|
||||
foreground: "hsl(var(--datatype-violet-foreground))",
|
||||
},
|
||||
warning: {
|
||||
DEFAULT: "hsl(var(--warning))",
|
||||
foreground: "hsl(var(--warning-foreground))",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import json
|
||||
import platform
|
||||
from starlette.responses import JSONResponse
|
||||
from utils.container_utils import transform_localhost_url
|
||||
from utils.logging_config import get_logger
|
||||
from config.settings import (
|
||||
LANGFLOW_URL,
|
||||
|
|
@ -8,6 +9,7 @@ from config.settings import (
|
|||
LANGFLOW_INGEST_FLOW_ID,
|
||||
LANGFLOW_PUBLIC_URL,
|
||||
DOCLING_COMPONENT_ID,
|
||||
LOCALHOST_URL,
|
||||
clients,
|
||||
get_openrag_config,
|
||||
config_manager,
|
||||
|
|
@ -17,35 +19,30 @@ logger = get_logger(__name__)
|
|||
|
||||
|
||||
# Docling preset configurations
|
||||
def get_docling_preset_configs():
|
||||
"""Get docling preset configurations with platform-specific settings"""
|
||||
def get_docling_preset_configs(table_structure=False, ocr=False, picture_descriptions=False):
|
||||
"""Get docling preset configurations based on toggle settings
|
||||
|
||||
Args:
|
||||
table_structure: Enable table structure parsing (default: False)
|
||||
ocr: Enable OCR for text extraction from images (default: False)
|
||||
picture_descriptions: Enable picture descriptions/captions (default: False)
|
||||
"""
|
||||
is_macos = platform.system() == "Darwin"
|
||||
|
||||
return {
|
||||
"standard": {"do_ocr": False},
|
||||
"ocr": {"do_ocr": True, "ocr_engine": "ocrmac" if is_macos else "easyocr"},
|
||||
"picture_description": {
|
||||
"do_ocr": True,
|
||||
"ocr_engine": "ocrmac" if is_macos else "easyocr",
|
||||
"do_picture_classification": True,
|
||||
"do_picture_description": True,
|
||||
"picture_description_local": {
|
||||
"repo_id": "HuggingFaceTB/SmolVLM-256M-Instruct",
|
||||
"prompt": "Describe this image in a few sentences.",
|
||||
},
|
||||
},
|
||||
"VLM": {
|
||||
"pipeline": "vlm",
|
||||
"vlm_pipeline_model_local": {
|
||||
"repo_id": "ds4sd/SmolDocling-256M-preview-mlx-bf16"
|
||||
if is_macos
|
||||
else "ds4sd/SmolDocling-256M-preview",
|
||||
"response_format": "doctags",
|
||||
"inference_framework": "mlx",
|
||||
},
|
||||
},
|
||||
config = {
|
||||
"do_ocr": ocr,
|
||||
"ocr_engine": "ocrmac" if is_macos else "easyocr",
|
||||
"do_table_structure": table_structure,
|
||||
"do_picture_classification": picture_descriptions,
|
||||
"do_picture_description": picture_descriptions,
|
||||
"picture_description_local": {
|
||||
"repo_id": "HuggingFaceTB/SmolVLM-256M-Instruct",
|
||||
"prompt": "Describe this image in a few sentences.",
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
|
||||
async def get_settings(request, session_manager):
|
||||
"""Get application settings"""
|
||||
|
|
@ -71,12 +68,15 @@ async def get_settings(request, session_manager):
|
|||
"embedding_model": knowledge_config.embedding_model,
|
||||
"chunk_size": knowledge_config.chunk_size,
|
||||
"chunk_overlap": knowledge_config.chunk_overlap,
|
||||
"doclingPresets": knowledge_config.doclingPresets,
|
||||
"table_structure": knowledge_config.table_structure,
|
||||
"ocr": knowledge_config.ocr,
|
||||
"picture_descriptions": knowledge_config.picture_descriptions,
|
||||
},
|
||||
"agent": {
|
||||
"llm_model": agent_config.llm_model,
|
||||
"system_prompt": agent_config.system_prompt,
|
||||
},
|
||||
"localhost_url": LOCALHOST_URL,
|
||||
}
|
||||
|
||||
# Only expose edit URLs when a public URL is configured
|
||||
|
|
@ -178,7 +178,9 @@ async def update_settings(request, session_manager):
|
|||
"system_prompt",
|
||||
"chunk_size",
|
||||
"chunk_overlap",
|
||||
"doclingPresets",
|
||||
"table_structure",
|
||||
"ocr",
|
||||
"picture_descriptions",
|
||||
"embedding_model",
|
||||
}
|
||||
|
||||
|
|
@ -255,32 +257,68 @@ async def update_settings(request, session_manager):
|
|||
# Don't fail the entire settings update if flow update fails
|
||||
# The config will still be saved
|
||||
|
||||
if "doclingPresets" in body:
|
||||
preset_configs = get_docling_preset_configs()
|
||||
valid_presets = list(preset_configs.keys())
|
||||
if body["doclingPresets"] not in valid_presets:
|
||||
if "table_structure" in body:
|
||||
if not isinstance(body["table_structure"], bool):
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": f"doclingPresets must be one of: {', '.join(valid_presets)}"
|
||||
},
|
||||
status_code=400,
|
||||
{"error": "table_structure must be a boolean"}, status_code=400
|
||||
)
|
||||
current_config.knowledge.doclingPresets = body["doclingPresets"]
|
||||
current_config.knowledge.table_structure = body["table_structure"]
|
||||
config_updated = True
|
||||
|
||||
# Also update the flow with the new docling preset
|
||||
# Also update the flow with the new docling settings
|
||||
try:
|
||||
flows_service = _get_flows_service()
|
||||
await flows_service.update_flow_docling_preset(
|
||||
body["doclingPresets"], preset_configs[body["doclingPresets"]]
|
||||
)
|
||||
logger.info(
|
||||
f"Successfully updated docling preset in flow to '{body['doclingPresets']}'"
|
||||
preset_config = get_docling_preset_configs(
|
||||
table_structure=body["table_structure"],
|
||||
ocr=current_config.knowledge.ocr,
|
||||
picture_descriptions=current_config.knowledge.picture_descriptions
|
||||
)
|
||||
await flows_service.update_flow_docling_preset("custom", preset_config)
|
||||
logger.info(f"Successfully updated table_structure setting in flow")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update docling preset in flow: {str(e)}")
|
||||
# Don't fail the entire settings update if flow update fails
|
||||
# The config will still be saved
|
||||
logger.error(f"Failed to update docling settings in flow: {str(e)}")
|
||||
|
||||
if "ocr" in body:
|
||||
if not isinstance(body["ocr"], bool):
|
||||
return JSONResponse(
|
||||
{"error": "ocr must be a boolean"}, status_code=400
|
||||
)
|
||||
current_config.knowledge.ocr = body["ocr"]
|
||||
config_updated = True
|
||||
|
||||
# Also update the flow with the new docling settings
|
||||
try:
|
||||
flows_service = _get_flows_service()
|
||||
preset_config = get_docling_preset_configs(
|
||||
table_structure=current_config.knowledge.table_structure,
|
||||
ocr=body["ocr"],
|
||||
picture_descriptions=current_config.knowledge.picture_descriptions
|
||||
)
|
||||
await flows_service.update_flow_docling_preset("custom", preset_config)
|
||||
logger.info(f"Successfully updated ocr setting in flow")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update docling settings in flow: {str(e)}")
|
||||
|
||||
if "picture_descriptions" in body:
|
||||
if not isinstance(body["picture_descriptions"], bool):
|
||||
return JSONResponse(
|
||||
{"error": "picture_descriptions must be a boolean"}, status_code=400
|
||||
)
|
||||
current_config.knowledge.picture_descriptions = body["picture_descriptions"]
|
||||
config_updated = True
|
||||
|
||||
# Also update the flow with the new docling settings
|
||||
try:
|
||||
flows_service = _get_flows_service()
|
||||
preset_config = get_docling_preset_configs(
|
||||
table_structure=current_config.knowledge.table_structure,
|
||||
ocr=current_config.knowledge.ocr,
|
||||
picture_descriptions=body["picture_descriptions"]
|
||||
)
|
||||
await flows_service.update_flow_docling_preset("custom", preset_config)
|
||||
logger.info(f"Successfully updated picture_descriptions setting in flow")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update docling settings in flow: {str(e)}")
|
||||
|
||||
if "chunk_size" in body:
|
||||
if not isinstance(body["chunk_size"], int) or body["chunk_size"] <= 0:
|
||||
|
|
@ -535,7 +573,8 @@ async def onboarding(request, flows_service):
|
|||
|
||||
# Set base URL for Ollama provider
|
||||
if provider == "ollama" and "endpoint" in body:
|
||||
endpoint = body["endpoint"]
|
||||
endpoint = transform_localhost_url(body["endpoint"])
|
||||
|
||||
await clients._create_langflow_global_variable(
|
||||
"OLLAMA_BASE_URL", endpoint, modify=True
|
||||
)
|
||||
|
|
@ -624,48 +663,56 @@ def _get_flows_service():
|
|||
|
||||
|
||||
async def update_docling_preset(request, session_manager):
|
||||
"""Update docling preset in the ingest flow"""
|
||||
"""Update docling settings in the ingest flow - deprecated endpoint, use /settings instead"""
|
||||
try:
|
||||
# Parse request body
|
||||
body = await request.json()
|
||||
|
||||
# Validate preset parameter
|
||||
if "preset" not in body:
|
||||
return JSONResponse(
|
||||
{"error": "preset parameter is required"}, status_code=400
|
||||
)
|
||||
# Support old preset-based API for backwards compatibility
|
||||
if "preset" in body:
|
||||
# Map old presets to new toggle settings
|
||||
preset_map = {
|
||||
"standard": {"table_structure": False, "ocr": False, "picture_descriptions": False},
|
||||
"ocr": {"table_structure": False, "ocr": True, "picture_descriptions": False},
|
||||
"picture_description": {"table_structure": False, "ocr": True, "picture_descriptions": True},
|
||||
"VLM": {"table_structure": False, "ocr": False, "picture_descriptions": False},
|
||||
}
|
||||
|
||||
preset = body["preset"]
|
||||
preset_configs = get_docling_preset_configs()
|
||||
preset = body["preset"]
|
||||
if preset not in preset_map:
|
||||
return JSONResponse(
|
||||
{"error": f"Invalid preset '{preset}'. Valid presets: {', '.join(preset_map.keys())}"},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
if preset not in preset_configs:
|
||||
valid_presets = list(preset_configs.keys())
|
||||
return JSONResponse(
|
||||
{
|
||||
"error": f"Invalid preset '{preset}'. Valid presets: {', '.join(valid_presets)}"
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
settings = preset_map[preset]
|
||||
else:
|
||||
# Support new toggle-based API
|
||||
settings = {
|
||||
"table_structure": body.get("table_structure", False),
|
||||
"ocr": body.get("ocr", False),
|
||||
"picture_descriptions": body.get("picture_descriptions", False),
|
||||
}
|
||||
|
||||
# Get the preset configuration
|
||||
preset_config = preset_configs[preset]
|
||||
preset_config = get_docling_preset_configs(**settings)
|
||||
|
||||
# Use the helper function to update the flow
|
||||
flows_service = _get_flows_service()
|
||||
await flows_service.update_flow_docling_preset(preset, preset_config)
|
||||
await flows_service.update_flow_docling_preset("custom", preset_config)
|
||||
|
||||
logger.info(f"Successfully updated docling preset to '{preset}' in ingest flow")
|
||||
logger.info(f"Successfully updated docling settings in ingest flow")
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"message": f"Successfully updated docling preset to '{preset}'",
|
||||
"preset": preset,
|
||||
"message": f"Successfully updated docling settings",
|
||||
"settings": settings,
|
||||
"preset_config": preset_config,
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to update docling preset", error=str(e))
|
||||
logger.error("Failed to update docling settings", error=str(e))
|
||||
return JSONResponse(
|
||||
{"error": f"Failed to update docling preset: {str(e)}"}, status_code=500
|
||||
{"error": f"Failed to update docling settings: {str(e)}"}, status_code=500
|
||||
)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ class KnowledgeConfig:
|
|||
embedding_model: str = "text-embedding-3-small"
|
||||
chunk_size: int = 1000
|
||||
chunk_overlap: int = 200
|
||||
doclingPresets: str = "standard"
|
||||
table_structure: bool = False
|
||||
ocr: bool = False
|
||||
picture_descriptions: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from openai import AsyncOpenAI
|
|||
from opensearchpy import AsyncOpenSearch
|
||||
from opensearchpy._async.http_aiohttp import AIOHttpConnection
|
||||
|
||||
from utils.container_utils import get_container_host
|
||||
from utils.document_processing import create_document_converter
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
|
|
@ -575,6 +576,8 @@ OLLAMA_LLM_TEXT_COMPONENT_ID = os.getenv(
|
|||
# Docling component ID for ingest flow
|
||||
DOCLING_COMPONENT_ID = os.getenv("DOCLING_COMPONENT_ID", "DoclingRemote-78KoX")
|
||||
|
||||
LOCALHOST_URL = get_container_host() or "localhost"
|
||||
|
||||
# Global clients instance
|
||||
clients = AppClients()
|
||||
|
||||
|
|
|
|||
|
|
@ -836,10 +836,5 @@ class FlowsService:
|
|||
template["url"]["value"] = endpoint
|
||||
template["url"]["options"] = [endpoint]
|
||||
updated = True
|
||||
elif provider == "ollama" and "base_url" in template:
|
||||
# Ollama uses "base_url" field
|
||||
template["base_url"]["value"] = endpoint
|
||||
# Note: base_url is typically a MessageTextInput, not dropdown, so no options field
|
||||
updated = True
|
||||
|
||||
return updated
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import httpx
|
||||
from typing import Dict, List
|
||||
from utils.container_utils import transform_localhost_url
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
|
@ -95,7 +96,7 @@ class ModelsService:
|
|||
"""Fetch available models from Ollama API with tool calling capabilities for language models"""
|
||||
try:
|
||||
# Use provided endpoint or default
|
||||
ollama_url = endpoint
|
||||
ollama_url = transform_localhost_url(endpoint)
|
||||
|
||||
# API endpoints
|
||||
tags_url = f"{ollama_url}/api/tags"
|
||||
|
|
|
|||
138
src/utils/container_utils.py
Normal file
138
src/utils/container_utils.py
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
"""Utilities for detecting and working with container environments."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def detect_container_environment() -> str | None:
|
||||
"""Detect if running in a container and return the appropriate container type.
|
||||
|
||||
Returns:
|
||||
'docker' if running in Docker, 'podman' if running in Podman, None otherwise.
|
||||
"""
|
||||
# Check for .dockerenv file (Docker)
|
||||
if Path("/.dockerenv").exists():
|
||||
return "docker"
|
||||
|
||||
# Check cgroup for container indicators
|
||||
try:
|
||||
with Path("/proc/self/cgroup").open() as f:
|
||||
content = f.read()
|
||||
if "docker" in content:
|
||||
return "docker"
|
||||
if "podman" in content:
|
||||
return "podman"
|
||||
except (FileNotFoundError, PermissionError):
|
||||
pass
|
||||
|
||||
# Check environment variables (lowercase 'container' is the standard for Podman)
|
||||
if os.getenv("container") == "podman": # noqa: SIM112
|
||||
return "podman"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_container_host() -> str | None:
|
||||
"""Get the hostname to access host services from within a container.
|
||||
|
||||
Tries multiple methods to find the correct hostname:
|
||||
1. host.containers.internal (Podman) or host.docker.internal (Docker)
|
||||
2. Gateway IP from routing table (fallback for Linux)
|
||||
|
||||
Returns:
|
||||
The hostname or IP to use, or None if not in a container.
|
||||
"""
|
||||
import socket
|
||||
|
||||
# Check if we're in a container first
|
||||
container_type = detect_container_environment()
|
||||
if not container_type:
|
||||
return None
|
||||
|
||||
# Try container-specific hostnames first based on detected type
|
||||
if container_type == "podman":
|
||||
# Podman: try host.containers.internal first
|
||||
try:
|
||||
socket.getaddrinfo("host.containers.internal", None)
|
||||
except socket.gaierror:
|
||||
pass
|
||||
else:
|
||||
return "host.containers.internal"
|
||||
|
||||
# Fallback to host.docker.internal (for Podman Desktop on macOS)
|
||||
try:
|
||||
socket.getaddrinfo("host.docker.internal", None)
|
||||
except socket.gaierror:
|
||||
pass
|
||||
else:
|
||||
return "host.docker.internal"
|
||||
else:
|
||||
# Docker: try host.docker.internal first
|
||||
try:
|
||||
socket.getaddrinfo("host.docker.internal", None)
|
||||
except socket.gaierror:
|
||||
pass
|
||||
else:
|
||||
return "host.docker.internal"
|
||||
|
||||
# Fallback to host.containers.internal (unlikely but possible)
|
||||
try:
|
||||
socket.getaddrinfo("host.containers.internal", None)
|
||||
except socket.gaierror:
|
||||
pass
|
||||
else:
|
||||
return "host.containers.internal"
|
||||
|
||||
# Fallback: try to get gateway IP from routing table (Linux containers)
|
||||
try:
|
||||
with Path("/proc/net/route").open() as f:
|
||||
for line in f:
|
||||
fields = line.strip().split()
|
||||
min_field_count = 3 # Minimum fields needed: interface, destination, gateway
|
||||
if len(fields) >= min_field_count and fields[1] == "00000000": # Default route
|
||||
# Gateway is in hex format (little-endian)
|
||||
gateway_hex = fields[2]
|
||||
# Convert hex to IP address
|
||||
# The hex is in little-endian format, so we read it backwards in pairs
|
||||
octets = [gateway_hex[i : i + 2] for i in range(0, 8, 2)]
|
||||
return ".".join(str(int(octet, 16)) for octet in reversed(octets))
|
||||
except (FileNotFoundError, PermissionError, IndexError, ValueError):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def transform_localhost_url(url: str) -> str:
|
||||
"""Transform localhost URLs to container-accessible hosts when running in a container.
|
||||
|
||||
Automatically detects if running inside a container and finds the appropriate host
|
||||
address to replace localhost/127.0.0.1. Tries in order:
|
||||
- host.docker.internal (if resolvable)
|
||||
- host.containers.internal (if resolvable)
|
||||
- Gateway IP from routing table (fallback)
|
||||
|
||||
Args:
|
||||
url: The original URL
|
||||
|
||||
Returns:
|
||||
Transformed URL with container-accessible host if applicable, otherwise the original URL.
|
||||
|
||||
Example:
|
||||
>>> transform_localhost_url("http://localhost:5001")
|
||||
# Returns "http://host.docker.internal:5001" if running in Docker and hostname resolves
|
||||
# Returns "http://172.17.0.1:5001" if running in Docker on Linux (gateway IP fallback)
|
||||
# Returns "http://localhost:5001" if not in a container
|
||||
"""
|
||||
container_host = get_container_host()
|
||||
|
||||
if not container_host:
|
||||
return url
|
||||
|
||||
# Replace localhost and 127.0.0.1 with the container host
|
||||
localhost_patterns = ["localhost", "127.0.0.1"]
|
||||
|
||||
for pattern in localhost_patterns:
|
||||
if pattern in url:
|
||||
return url.replace(pattern, container_host)
|
||||
|
||||
return url
|
||||
|
|
@ -10,6 +10,8 @@ def get_embedding_dimensions(model_name: str) -> int:
|
|||
# Check all model dictionaries
|
||||
all_models = {**OPENAI_EMBEDDING_DIMENSIONS, **OLLAMA_EMBEDDING_DIMENSIONS, **WATSONX_EMBEDDING_DIMENSIONS}
|
||||
|
||||
model_name = model_name.lower().strip().split(":")[0]
|
||||
|
||||
if model_name in all_models:
|
||||
dimensions = all_models[model_name]
|
||||
logger.info(f"Found dimensions for model '{model_name}': {dimensions}")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue