forked from github/onyx
Feature/openapi (#4710)
* starting openapi support * fix app / app_fn * send gitignore * dedupe function names * add readme * modify gitignore * update launch template * fix unused path param * fix mypy * local tests pass * first pass at making integration tests work * fixes * fix script path * set python path * try full path * fix output dir * fix integration test * more build fixes * add generated directory * use the config * add a comment * add * modify tsconfig.json * fix index linting bugs * tons of lint fixes * new gitignore * remove generated dir * add tasks template * check for undefined explicitly * fix hooks.ts * refactor destructureValue * improve readme --------- Co-authored-by: Richard Kuo (Onyx) <rkuo@onyx.app>
This commit is contained in:
33
.github/workflows/pr-integration-tests.yml
vendored
33
.github/workflows/pr-integration-tests.yml
vendored
@@ -26,6 +26,39 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: "pip"
|
||||
cache-dependency-path: |
|
||||
backend/requirements/default.txt
|
||||
backend/requirements/dev.txt
|
||||
backend/requirements/ee.txt
|
||||
- run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --retries 5 --timeout 30 -r backend/requirements/default.txt
|
||||
pip install --retries 5 --timeout 30 -r backend/requirements/dev.txt
|
||||
pip install --retries 5 --timeout 30 -r backend/requirements/ee.txt
|
||||
|
||||
- name: Generate OpenAPI schema
|
||||
working-directory: ./backend
|
||||
env:
|
||||
PYTHONPATH: "."
|
||||
run: |
|
||||
python scripts/onyx_openapi_schema.py --filename generated/openapi.json
|
||||
|
||||
- name: Generate OpenAPI Python client
|
||||
working-directory: ./backend
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v "${{ github.workspace }}/backend/generated:/local" \
|
||||
openapitools/openapi-generator-cli generate \
|
||||
-i /local/openapi.json \
|
||||
-g python \
|
||||
-o /local/onyx_openapi_client \
|
||||
--package-name onyx_openapi_client
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
|
||||
31
.github/workflows/pr-mit-integration-tests.yml
vendored
31
.github/workflows/pr-mit-integration-tests.yml
vendored
@@ -24,7 +24,38 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: "pip"
|
||||
cache-dependency-path: |
|
||||
backend/requirements/default.txt
|
||||
backend/requirements/dev.txt
|
||||
- run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --retries 5 --timeout 30 -r backend/requirements/default.txt
|
||||
pip install --retries 5 --timeout 30 -r backend/requirements/dev.txt
|
||||
|
||||
- name: Generate OpenAPI schema
|
||||
working-directory: ./backend
|
||||
env:
|
||||
PYTHONPATH: "."
|
||||
run: |
|
||||
python scripts/onyx_openapi_schema.py --filename generated/openapi.json
|
||||
|
||||
- name: Generate OpenAPI Python client
|
||||
working-directory: ./backend
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v "${{ github.workspace }}/backend/generated:/local" \
|
||||
openapitools/openapi-generator-cli generate \
|
||||
-i /local/openapi.json \
|
||||
-g python \
|
||||
-o /local/onyx_openapi_client \
|
||||
--package-name onyx_openapi_client
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
|
||||
18
.github/workflows/pr-python-checks.yml
vendored
18
.github/workflows/pr-python-checks.yml
vendored
@@ -31,6 +31,24 @@ jobs:
|
||||
pip install --retries 5 --timeout 30 -r backend/requirements/dev.txt
|
||||
pip install --retries 5 --timeout 30 -r backend/requirements/model_server.txt
|
||||
|
||||
- name: Generate OpenAPI schema
|
||||
working-directory: ./backend
|
||||
env:
|
||||
PYTHONPATH: "."
|
||||
run: |
|
||||
python scripts/onyx_openapi_schema.py --filename generated/openapi.json
|
||||
|
||||
- name: Generate OpenAPI Python client
|
||||
working-directory: ./backend
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v "${{ github.workspace }}/backend/generated:/local" \
|
||||
openapitools/openapi-generator-cli generate \
|
||||
-i /local/openapi.json \
|
||||
-g python \
|
||||
-o /local/onyx_openapi_client \
|
||||
--package-name onyx_openapi_client
|
||||
|
||||
- name: Run MyPy
|
||||
run: |
|
||||
cd backend
|
||||
|
||||
17
.vscode/launch.template.jsonc
vendored
17
.vscode/launch.template.jsonc
vendored
@@ -412,6 +412,23 @@
|
||||
"group": "3"
|
||||
}
|
||||
},
|
||||
{
|
||||
// script to generate the openapi schema
|
||||
"name": "Onyx OpenAPI Schema Generator",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "scripts/onyx_openapi_schema.py",
|
||||
"cwd": "${workspaceFolder}/backend",
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"env": {
|
||||
"PYTHONUNBUFFERED": "1",
|
||||
"PYTHONPATH": "."
|
||||
},
|
||||
"args": [
|
||||
"--filename",
|
||||
"generated/openapi.json",
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Debug React Web App in Chrome",
|
||||
"type": "chrome",
|
||||
|
||||
101
.vscode/tasks.template.jsonc
vendored
Normal file
101
.vscode/tasks.template.jsonc
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "austin",
|
||||
"label": "Profile celery beat",
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/backend"
|
||||
},
|
||||
"command": [
|
||||
"sudo",
|
||||
"-E"
|
||||
],
|
||||
"args": [
|
||||
"celery",
|
||||
"-A",
|
||||
"onyx.background.celery.versioned_apps.beat",
|
||||
"beat",
|
||||
"--loglevel=INFO"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Generate Onyx OpenAPI Python client",
|
||||
"cwd": "${workspaceFolder}/backend",
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/backend"
|
||||
},
|
||||
"command": [
|
||||
"openapi-generator"
|
||||
],
|
||||
"args": [
|
||||
"generate",
|
||||
"-i",
|
||||
"generated/openapi.json",
|
||||
"-g",
|
||||
"python",
|
||||
"-o",
|
||||
"generated/onyx_openapi_client",
|
||||
"--package-name",
|
||||
"onyx_openapi_client",
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Generate Typescript Fetch client (openapi-generator)",
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
"command": [
|
||||
"openapi-generator"
|
||||
],
|
||||
"args": [
|
||||
"generate",
|
||||
"-i",
|
||||
"backend/generated/openapi.json",
|
||||
"-g",
|
||||
"typescript-fetch",
|
||||
"-o",
|
||||
"${workspaceFolder}/web/src/lib/generated/onyx_api",
|
||||
"--additional-properties=disallowAdditionalPropertiesIfNotPresent=false,legacyDiscriminatorBehavior=false,supportsES6=true",
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Generate TypeScript Client (openapi-ts)",
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/web"
|
||||
},
|
||||
"command": [
|
||||
"npx"
|
||||
],
|
||||
"args": [
|
||||
"openapi-typescript",
|
||||
"../backend/generated/openapi.json",
|
||||
"--output",
|
||||
"./src/lib/generated/onyx-schema.ts",
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Generate TypeScript Client (orval)",
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/web"
|
||||
},
|
||||
"command": [
|
||||
"npx"
|
||||
],
|
||||
"args": [
|
||||
"orval",
|
||||
"--config",
|
||||
"orval.config.js",
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
2
backend/.gitignore
vendored
2
backend/.gitignore
vendored
@@ -11,4 +11,4 @@ dynamic_config_storage/
|
||||
celerybeat-schedule*
|
||||
onyx/connectors/salesforce/data/
|
||||
.test.env
|
||||
|
||||
/generated
|
||||
|
||||
@@ -51,6 +51,7 @@ from onyx.main import get_application as get_application_base
|
||||
from onyx.main import include_auth_router_with_prefix
|
||||
from onyx.main import include_router_with_global_prefix_prepended
|
||||
from onyx.main import lifespan as lifespan_base
|
||||
from onyx.main import use_route_function_names_as_operation_ids
|
||||
from onyx.utils.logger import setup_logger
|
||||
from onyx.utils.variable_functionality import global_version
|
||||
from shared_configs.configs import MULTI_TENANT
|
||||
@@ -192,4 +193,6 @@ def get_application() -> FastAPI:
|
||||
# for route in application.router.routes:
|
||||
# print(f"Path: {route.path}, Methods: {route.methods}")
|
||||
|
||||
use_route_function_names_as_operation_ids(application)
|
||||
|
||||
return application
|
||||
|
||||
@@ -114,14 +114,14 @@ async def refresh_access_token(
|
||||
|
||||
|
||||
@admin_router.put("")
|
||||
def put_settings(
|
||||
def admin_ee_put_settings(
|
||||
settings: EnterpriseSettings, _: User | None = Depends(current_admin_user)
|
||||
) -> None:
|
||||
store_settings(settings)
|
||||
|
||||
|
||||
@basic_router.get("")
|
||||
def fetch_settings() -> EnterpriseSettings:
|
||||
def ee_fetch_settings() -> EnterpriseSettings:
|
||||
if MULTI_TENANT:
|
||||
tenant_id = get_current_tenant_id()
|
||||
if not tenant_id or tenant_id == POSTGRES_DEFAULT_SCHEMA:
|
||||
|
||||
@@ -147,7 +147,7 @@ def snapshot_from_chat_session(
|
||||
|
||||
|
||||
@router.get("/admin/chat-sessions")
|
||||
def get_user_chat_sessions(
|
||||
def admin_get_chat_sessions(
|
||||
user_id: UUID,
|
||||
_: User | None = Depends(current_admin_user),
|
||||
db_session: Session = Depends(get_session),
|
||||
|
||||
2
backend/generated/README.md
Normal file
2
backend/generated/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- Generated Files
|
||||
* Generated files live here. This directory should be git ignored.
|
||||
@@ -16,6 +16,7 @@ from fastapi import status
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.routing import APIRoute
|
||||
from httpx_oauth.clients.google import GoogleOAuth2
|
||||
from prometheus_fastapi_instrumentator import Instrumentator
|
||||
from sentry_sdk.integrations.fastapi import FastApiIntegration
|
||||
@@ -157,6 +158,20 @@ def value_error_handler(_: Request, exc: Exception) -> JSONResponse:
|
||||
)
|
||||
|
||||
|
||||
def use_route_function_names_as_operation_ids(app: FastAPI) -> None:
|
||||
"""
|
||||
OpenAPI generation defaults to naming the operation with the
|
||||
function + route + HTTP method, which usually looks very redundant.
|
||||
|
||||
This function changes the operation IDs to be just the function name.
|
||||
|
||||
Should be called only after all routes have been added.
|
||||
"""
|
||||
for route in app.routes:
|
||||
if isinstance(route, APIRoute):
|
||||
route.operation_id = route.name
|
||||
|
||||
|
||||
def include_router_with_global_prefix_prepended(
|
||||
application: FastAPI, router: APIRouter, **kwargs: Any
|
||||
) -> None:
|
||||
@@ -308,7 +323,6 @@ def get_application(lifespan_override: Lifespan | None = None) -> FastAPI:
|
||||
include_router_with_global_prefix_prepended(application, admin_query_router)
|
||||
include_router_with_global_prefix_prepended(application, admin_router)
|
||||
include_router_with_global_prefix_prepended(application, connector_router)
|
||||
include_router_with_global_prefix_prepended(application, user_router)
|
||||
include_router_with_global_prefix_prepended(application, credential_router)
|
||||
include_router_with_global_prefix_prepended(application, input_prompt_router)
|
||||
include_router_with_global_prefix_prepended(application, admin_input_prompt_router)
|
||||
@@ -444,6 +458,8 @@ def get_application(lifespan_override: Lifespan | None = None) -> FastAPI:
|
||||
# Initialize and instrument the app
|
||||
Instrumentator().instrument(application).expose(application)
|
||||
|
||||
use_route_function_names_as_operation_ids(application)
|
||||
|
||||
return application
|
||||
|
||||
|
||||
|
||||
@@ -329,6 +329,7 @@ def list_runs(
|
||||
|
||||
@router.get("/threads/{thread_id}/runs/{run_id}/steps")
|
||||
def list_run_steps(
|
||||
thread_id: UUID,
|
||||
run_id: str,
|
||||
limit: int = 20,
|
||||
order: Literal["asc", "desc"] = "desc",
|
||||
|
||||
@@ -32,7 +32,7 @@ basic_router = APIRouter(prefix="/settings")
|
||||
|
||||
|
||||
@admin_router.put("")
|
||||
def put_settings(
|
||||
def admin_put_settings(
|
||||
settings: Settings, _: User | None = Depends(current_admin_user)
|
||||
) -> None:
|
||||
store_settings(settings)
|
||||
|
||||
@@ -86,7 +86,7 @@ def create_folder(
|
||||
@router.get(
|
||||
"/user/folder",
|
||||
)
|
||||
def get_folders(
|
||||
def user_get_folders(
|
||||
user: User = Depends(current_user),
|
||||
db_session: Session = Depends(get_session),
|
||||
) -> list[UserFolderSnapshot]:
|
||||
|
||||
@@ -5,6 +5,9 @@ explicit_package_bases = true
|
||||
disallow_untyped_defs = true
|
||||
enable_error_code = ["possibly-undefined"]
|
||||
strict_equality = true
|
||||
exclude = [
|
||||
"^generated/",
|
||||
]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "alembic.versions.*"
|
||||
@@ -14,6 +17,10 @@ disable_error_code = ["var-annotated"]
|
||||
module = "alembic_tenants.versions.*"
|
||||
disable_error_code = ["var-annotated"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "generated.*"
|
||||
follow_imports = "silent"
|
||||
|
||||
[tool.ruff]
|
||||
ignore = []
|
||||
line-length = 130
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
[pytest]
|
||||
pythonpath = .
|
||||
pythonpath =
|
||||
.
|
||||
generated/onyx_openapi_client
|
||||
markers =
|
||||
slow: marks tests as slow
|
||||
filterwarnings =
|
||||
|
||||
44
backend/scripts/onyx_openapi_schema.py
Normal file
44
backend/scripts/onyx_openapi_schema.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# export openapi schema without having to start the actual web server
|
||||
|
||||
# helpful tips: https://github.com/fastapi/fastapi/issues/1173
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
|
||||
from onyx.main import app as app_fn
|
||||
|
||||
|
||||
def go(filename: str) -> None:
|
||||
with open(filename, "w") as f:
|
||||
app: FastAPI = app_fn()
|
||||
json.dump(
|
||||
get_openapi(
|
||||
title=app.title,
|
||||
version=app.version,
|
||||
openapi_version=app.openapi_version,
|
||||
description=app.description,
|
||||
routes=app.routes,
|
||||
),
|
||||
f,
|
||||
)
|
||||
|
||||
print(f"Wrote OpenAPI schema to {filename}.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Export OpenAPI schema for Onyx API (does not require starting API server)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--filename", "-f", help="Filename to write to", default="openapi.json"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
go(args.filename)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -3,6 +3,7 @@ from collections.abc import Generator
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from onyx.main import fetch_versioned_implementation
|
||||
@@ -17,7 +18,7 @@ def client() -> Generator[TestClient, Any, None]:
|
||||
os.environ["ENABLE_PAID_ENTERPRISE_EDITION_FEATURES"] = "True"
|
||||
|
||||
# Initialize TestClient with the FastAPI app
|
||||
app = fetch_versioned_implementation(
|
||||
app: FastAPI = fetch_versioned_implementation(
|
||||
module="onyx.main", attribute="get_application"
|
||||
)()
|
||||
client = TestClient(app)
|
||||
|
||||
@@ -3,6 +3,7 @@ from collections.abc import Generator
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from onyx.main import fetch_versioned_implementation
|
||||
@@ -17,7 +18,7 @@ def client() -> Generator[TestClient, Any, None]:
|
||||
os.environ["ENABLE_PAID_ENTERPRISE_EDITION_FEATURES"] = "True"
|
||||
|
||||
# Initialize TestClient with the FastAPI app
|
||||
app = fetch_versioned_implementation(
|
||||
app: FastAPI = fetch_versioned_implementation(
|
||||
module="onyx.main", attribute="get_application"
|
||||
)()
|
||||
client = TestClient(app)
|
||||
|
||||
@@ -71,6 +71,7 @@ COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# Set up application files
|
||||
COPY ./onyx /app/onyx
|
||||
COPY ./generated /app/generated
|
||||
COPY ./shared_configs /app/shared_configs
|
||||
COPY ./alembic_tenants /app/alembic_tenants
|
||||
COPY ./alembic /app/alembic
|
||||
|
||||
4
backend/tests/integration/common_utils/config.py
Normal file
4
backend/tests/integration/common_utils/config.py
Normal file
@@ -0,0 +1,4 @@
|
||||
import generated.onyx_openapi_client.onyx_openapi_client as onyx_api
|
||||
from tests.integration.common_utils.constants import API_SERVER_URL
|
||||
|
||||
api_config = onyx_api.Configuration(host=API_SERVER_URL)
|
||||
@@ -5,6 +5,7 @@ from uuid import uuid4
|
||||
|
||||
import requests
|
||||
|
||||
import generated.onyx_openapi_client.onyx_openapi_client as api
|
||||
from onyx.connectors.models import InputType
|
||||
from onyx.db.enums import AccessType
|
||||
from onyx.db.enums import ConnectorCredentialPairStatus
|
||||
@@ -13,6 +14,7 @@ from onyx.server.documents.models import ConnectorCredentialPairIdentifier
|
||||
from onyx.server.documents.models import ConnectorIndexingStatus
|
||||
from onyx.server.documents.models import DocumentSource
|
||||
from onyx.server.documents.models import DocumentSyncStatus
|
||||
from tests.integration.common_utils.config import api_config
|
||||
from tests.integration.common_utils.constants import API_SERVER_URL
|
||||
from tests.integration.common_utils.constants import GENERAL_HEADERS
|
||||
from tests.integration.common_utils.constants import MAX_DELAY
|
||||
@@ -32,24 +34,27 @@ def _cc_pair_creator(
|
||||
) -> DATestCCPair:
|
||||
name = f"{name}-cc-pair" if name else f"test-cc-pair-{uuid4()}"
|
||||
|
||||
request = {
|
||||
"name": name,
|
||||
"access_type": access_type,
|
||||
"groups": groups or [],
|
||||
}
|
||||
|
||||
response = requests.put(
|
||||
url=f"{API_SERVER_URL}/manage/connector/{connector_id}/credential/{credential_id}",
|
||||
json=request,
|
||||
headers=(
|
||||
with api.ApiClient(api_config) as api_client:
|
||||
api_instance = api.DefaultApi(api_client)
|
||||
connector_credential_pair_metadata = api.ConnectorCredentialPairMetadata(
|
||||
name=name, access_type=access_type, groups=groups or []
|
||||
)
|
||||
headers = (
|
||||
user_performing_action.headers
|
||||
if user_performing_action
|
||||
else GENERAL_HEADERS
|
||||
),
|
||||
)
|
||||
response.raise_for_status()
|
||||
)
|
||||
api_response: api.StatusResponseInt = (
|
||||
api_instance.associate_credential_to_connector(
|
||||
connector_id,
|
||||
credential_id,
|
||||
connector_credential_pair_metadata,
|
||||
_headers=headers,
|
||||
)
|
||||
)
|
||||
|
||||
return DATestCCPair(
|
||||
id=response.json()["data"],
|
||||
id=int(api_response.data),
|
||||
name=name,
|
||||
connector_id=connector_id,
|
||||
credential_id=credential_id,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# TODO(rkuo): All of the downgrade_postgres and upgrade_postgres operations here
|
||||
# are vulnerable to deadlocks. We could deal with them similar to reset_postgres
|
||||
# where we retry out of process
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
File diff suppressed because one or more lines are too long
3
web/.gitignore
vendored
3
web/.gitignore
vendored
@@ -40,3 +40,6 @@ next-env.d.ts
|
||||
/user_auth.json
|
||||
/build-archive.log
|
||||
/test-results
|
||||
|
||||
# generated clients ... in particular, the API to the Onyx backend itself!
|
||||
/src/lib/generated
|
||||
|
||||
@@ -43,6 +43,19 @@ cd web
|
||||
npx playwright test
|
||||
```
|
||||
|
||||
To run a single test:
|
||||
```
|
||||
npx playwright test landing-page.spec.ts
|
||||
```
|
||||
|
||||
If running locally, interactive options can help you see exactly what is happening in
|
||||
the test.
|
||||
|
||||
```
|
||||
npx playwright test --ui
|
||||
npx playwright test --headed
|
||||
```
|
||||
|
||||
3. Inspect results
|
||||
|
||||
By default, playwright.config.ts is configured to output the results to:
|
||||
|
||||
@@ -89,7 +89,7 @@ function ActionForm({
|
||||
setMethodSpecs(response.data);
|
||||
setDefinitionError(null);
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setMethodSpecs(null);
|
||||
setDefinitionError("Invalid JSON format");
|
||||
}
|
||||
@@ -143,7 +143,7 @@ function ActionForm({
|
||||
parseJsonWithTrailingCommas(definition)
|
||||
);
|
||||
setFieldValue("definition", formatted);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
alert("Invalid JSON format");
|
||||
}
|
||||
}
|
||||
@@ -414,7 +414,7 @@ export function ActionEditor({ tool }: { tool?: ToolSnapshot }) {
|
||||
let definition: any;
|
||||
try {
|
||||
definition = parseJsonWithTrailingCommas(values.definition);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setDefinitionError("Invalid JSON in action definition");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ export default function Page() {
|
||||
);
|
||||
if (
|
||||
filteredCategories.length > 0 &&
|
||||
filteredCategories[0] !== undefined &&
|
||||
filteredCategories[0][1].length > 0
|
||||
) {
|
||||
const firstSource = filteredCategories[0][1][0];
|
||||
|
||||
@@ -25,7 +25,7 @@ import { getDisplayNameForModel, useLabels } from "@/lib/hooks";
|
||||
import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable";
|
||||
import { addAssistantToList } from "@/lib/assistants/updateAssistantPreferences";
|
||||
import {
|
||||
destructureValue,
|
||||
parseLlmDescriptor,
|
||||
modelSupportsImageInput,
|
||||
structureValue,
|
||||
} from "@/lib/llm/utils";
|
||||
@@ -548,6 +548,7 @@ export function AssistantEditor({
|
||||
|
||||
const submissionData: PersonaUpsertParameters = {
|
||||
...values,
|
||||
icon_color: values.icon_color ?? null,
|
||||
existing_prompt_id: existingPrompt?.id ?? null,
|
||||
starter_messages: starterMessages,
|
||||
groups: groups,
|
||||
@@ -1163,7 +1164,7 @@ export function AssistantEditor({
|
||||
setFieldValue("llm_model_provider_override", null);
|
||||
} else {
|
||||
const { modelName, provider, name } =
|
||||
destructureValue(selected);
|
||||
parseLlmDescriptor(selected);
|
||||
if (modelName && name) {
|
||||
setFieldValue(
|
||||
"llm_model_version_override",
|
||||
|
||||
@@ -36,12 +36,12 @@ export default function StarterMessagesList({
|
||||
|
||||
if (value && index === values.length - 1 && values.length < 4) {
|
||||
arrayHelpers.push({ message: "" });
|
||||
} else if (
|
||||
!value &&
|
||||
index === values.length - 2 &&
|
||||
!values[values.length - 1].message
|
||||
) {
|
||||
arrayHelpers.pop();
|
||||
} else if (!value && index === values.length - 2) {
|
||||
const lastItem = values[values.length - 1];
|
||||
if (lastItem !== undefined && !lastItem.message) {
|
||||
// Check if lastItem's message is also empty
|
||||
arrayHelpers.pop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -124,8 +124,12 @@ export function ModelConfigurationField({
|
||||
for (const key in newErrors) {
|
||||
const numKey = Number(key);
|
||||
if (numKey > index) {
|
||||
newErrors[numKey - 1] = newErrors[key];
|
||||
delete newErrors[numKey];
|
||||
const errorValue = newErrors[key];
|
||||
if (errorValue !== undefined) {
|
||||
// Ensure the value is not undefined
|
||||
newErrors[numKey - 1] = errorValue;
|
||||
delete newErrors[numKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ import {
|
||||
OpenAISVG,
|
||||
} from "@/components/icons/icons";
|
||||
|
||||
export const getProviderIcon = (providerName: string, modelName?: string) => {
|
||||
export const getProviderIcon = (
|
||||
providerName: string,
|
||||
modelName?: string
|
||||
): (({ size, className }: IconProps) => JSX.Element) => {
|
||||
const iconMap: Record<
|
||||
string,
|
||||
({ size, className }: IconProps) => JSX.Element
|
||||
@@ -32,8 +35,12 @@ export const getProviderIcon = (providerName: string, modelName?: string) => {
|
||||
};
|
||||
|
||||
// First check if provider name directly matches an icon
|
||||
if (providerName.toLowerCase() in iconMap) {
|
||||
return iconMap[providerName.toLowerCase()];
|
||||
const lowerProviderName = providerName.toLowerCase();
|
||||
if (lowerProviderName in iconMap) {
|
||||
const icon = iconMap[lowerProviderName];
|
||||
if (icon) {
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
|
||||
// Then check if model name contains any of the keys
|
||||
|
||||
@@ -49,63 +49,68 @@ const TabsField: FC<TabsFieldProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Tabs
|
||||
defaultValue={tabField.tabs[0].value}
|
||||
className="w-full"
|
||||
onValueChange={(newTab) => {
|
||||
// Clear values from other tabs but preserve defaults
|
||||
tabField.tabs.forEach((tab) => {
|
||||
if (tab.value !== newTab) {
|
||||
tab.fields.forEach((field) => {
|
||||
// Only clear if not default value
|
||||
if (values[field.name] !== field.default) {
|
||||
values[field.name] = field.default;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<TabsList>
|
||||
{tabField.tabs.map((tab) => (
|
||||
<TabsTrigger key={tab.value} value={tab.value}>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabField.tabs.map((tab) => (
|
||||
<TabsContent key={tab.value} value={tab.value} className="">
|
||||
{tab.fields.map((subField, index, array) => {
|
||||
// Check visibility condition first
|
||||
if (
|
||||
subField.visibleCondition &&
|
||||
!subField.visibleCondition(values, currentCredential)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={subField.name}
|
||||
className={
|
||||
index < array.length - 1 && subField.type !== "string_tab"
|
||||
? "mb-4"
|
||||
: ""
|
||||
{/* Ensure there's at least one tab before rendering */}
|
||||
{tabField.tabs.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground">No tabs to display.</div>
|
||||
) : (
|
||||
<Tabs
|
||||
defaultValue={tabField.tabs[0]?.value} // Optional chaining for safety, though the length check above handles it
|
||||
className="w-full"
|
||||
onValueChange={(newTab) => {
|
||||
// Clear values from other tabs but preserve defaults
|
||||
tabField.tabs.forEach((tab) => {
|
||||
if (tab.value !== newTab) {
|
||||
tab.fields.forEach((field) => {
|
||||
// Only clear if not default value
|
||||
if (values[field.name] !== field.default) {
|
||||
values[field.name] = field.default;
|
||||
}
|
||||
>
|
||||
<RenderField
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<TabsList>
|
||||
{tabField.tabs.map((tab) => (
|
||||
<TabsTrigger key={tab.value} value={tab.value}>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabField.tabs.map((tab) => (
|
||||
<TabsContent key={tab.value} value={tab.value} className="">
|
||||
{tab.fields.map((subField, index, array) => {
|
||||
// Check visibility condition first
|
||||
if (
|
||||
subField.visibleCondition &&
|
||||
!subField.visibleCondition(values, currentCredential)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={subField.name}
|
||||
field={subField}
|
||||
values={values}
|
||||
connector={connector}
|
||||
currentCredential={currentCredential}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
className={
|
||||
index < array.length - 1 && subField.type !== "string_tab"
|
||||
? "mb-4"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<RenderField
|
||||
key={subField.name}
|
||||
field={subField}
|
||||
values={values}
|
||||
connector={connector}
|
||||
currentCredential={currentCredential}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -168,7 +168,10 @@ export const DriveJsonUpload = ({
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
const file = files[0];
|
||||
if (file.type === "application/json" || file.name.endsWith(".json")) {
|
||||
if (
|
||||
file !== undefined &&
|
||||
(file.type === "application/json" || file.name.endsWith(".json"))
|
||||
) {
|
||||
handleFileUpload(file);
|
||||
} else {
|
||||
setPopup({
|
||||
@@ -224,6 +227,9 @@ export const DriveJsonUpload = ({
|
||||
return;
|
||||
}
|
||||
const file = event.target.files[0];
|
||||
if (file === undefined) {
|
||||
return;
|
||||
}
|
||||
handleFileUpload(file);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -166,7 +166,10 @@ const GmailCredentialUpload = ({
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
const file = files[0];
|
||||
if (file.type === "application/json" || file.name.endsWith(".json")) {
|
||||
if (
|
||||
file !== undefined &&
|
||||
(file.type === "application/json" || file.name.endsWith(".json"))
|
||||
) {
|
||||
handleFileUpload(file);
|
||||
} else {
|
||||
setPopup({
|
||||
@@ -222,6 +225,9 @@ const GmailCredentialUpload = ({
|
||||
return;
|
||||
}
|
||||
const file = event.target.files[0];
|
||||
if (file === undefined) {
|
||||
return;
|
||||
}
|
||||
handleFileUpload(file);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -36,6 +36,25 @@ export const submitGoogleSite = async (
|
||||
}
|
||||
|
||||
const filePaths = responseJson.file_paths as string[];
|
||||
if (!filePaths || filePaths.length === 0) {
|
||||
setPopup({
|
||||
message:
|
||||
"File upload was successful, but no file path was returned. Cannot create connector.",
|
||||
type: "error",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const filePath = filePaths[0];
|
||||
if (filePath === undefined) {
|
||||
setPopup({
|
||||
message:
|
||||
"File upload was successful, but file path is undefined. Cannot create connector.",
|
||||
type: "error",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const [connectorErrorMsg, connector] =
|
||||
await createConnector<GoogleSitesConfig>({
|
||||
name: name ? name : `GoogleSitesConnector-${base_url}`,
|
||||
@@ -43,7 +62,7 @@ export const submitGoogleSite = async (
|
||||
input_type: "load_state",
|
||||
connector_specific_config: {
|
||||
base_url: base_url,
|
||||
zip_path: filePaths[0],
|
||||
zip_path: filePath,
|
||||
},
|
||||
access_type: access_type,
|
||||
refresh_freq: refreshFreq,
|
||||
|
||||
@@ -114,6 +114,11 @@ export function ChangeCredentialsModal({
|
||||
.toLowerCase()
|
||||
.split(" ")[0];
|
||||
|
||||
if (!normalizedProviderType) {
|
||||
setTestError("Provider type is invalid or missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const testResponse = await testEmbedding({
|
||||
provider_type: normalizedProviderType,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
fetchVisionProviders,
|
||||
setDefaultVisionProvider,
|
||||
} from "@/lib/llm/visionLLM";
|
||||
import { destructureValue, structureValue } from "@/lib/llm/utils";
|
||||
import { parseLlmDescriptor, structureValue } from "@/lib/llm/utils";
|
||||
|
||||
// Define a type for the popup setter function
|
||||
type SetPopup = (popup: {
|
||||
@@ -73,7 +73,7 @@ export function useVisionProviders(setPopup: SetPopup) {
|
||||
}
|
||||
|
||||
try {
|
||||
const { name, modelName } = destructureValue(llmValue);
|
||||
const { name, modelName } = parseLlmDescriptor(llmValue);
|
||||
|
||||
// Find the provider ID
|
||||
const providerObj = visionProviders.find((p) => p.name === name);
|
||||
|
||||
@@ -36,7 +36,9 @@ export const TokenRateLimitTable = ({
|
||||
isAdmin,
|
||||
}: TokenRateLimitTableArgs) => {
|
||||
const shouldRenderGroupName = () =>
|
||||
tokenRateLimits.length > 0 && tokenRateLimits[0].group_name !== undefined;
|
||||
tokenRateLimits.length > 0 &&
|
||||
tokenRateLimits[0] !== undefined &&
|
||||
tokenRateLimits[0].group_name !== undefined;
|
||||
|
||||
const handleEnabledChange = (id: number) => {
|
||||
const tokenRateLimit = tokenRateLimits.find(
|
||||
|
||||
@@ -544,6 +544,7 @@ export function ChatPage({
|
||||
// if this is a seeded chat, then kick off the AI message generation
|
||||
if (
|
||||
newMessageHistory.length === 1 &&
|
||||
newMessageHistory[0] !== undefined &&
|
||||
!submitOnLoadPerformed.current &&
|
||||
searchParams?.get(SEARCH_PARAM_NAMES.SEEDED) === "true"
|
||||
) {
|
||||
@@ -649,7 +650,7 @@ export function ChatPage({
|
||||
completeMessageMapOverride || currentMessageMap(completeMessageDetail);
|
||||
const newCompleteMessageMap = structuredClone(frozenCompleteMessageMap);
|
||||
|
||||
if (newCompleteMessageMap.size === 0) {
|
||||
if (messages[0] !== undefined && newCompleteMessageMap.size === 0) {
|
||||
const systemMessageId = messages[0].parentMessageId || SYSTEM_MESSAGE_ID;
|
||||
const firstMessageId = messages[0].messageId;
|
||||
const dummySystemMessage: Message = {
|
||||
@@ -690,7 +691,7 @@ export function ChatPage({
|
||||
frozenCompleteMessageMap
|
||||
);
|
||||
const latestMessage = currentMessageChain[currentMessageChain.length - 1];
|
||||
if (latestMessage) {
|
||||
if (messages[0] !== undefined && latestMessage) {
|
||||
newCompleteMessageMap.get(
|
||||
latestMessage.messageId
|
||||
)!.latestChildMessageId = messages[0].messageId;
|
||||
@@ -1379,7 +1380,11 @@ export function ChatPage({
|
||||
} else if (alternativeAssistant) {
|
||||
currentAssistantId = alternativeAssistant.id;
|
||||
} else {
|
||||
currentAssistantId = liveAssistant.id;
|
||||
if (liveAssistant) {
|
||||
currentAssistantId = liveAssistant.id;
|
||||
} else {
|
||||
currentAssistantId = 0; // Fallback if no assistant is live
|
||||
}
|
||||
}
|
||||
|
||||
resetInputBar();
|
||||
@@ -1438,8 +1443,8 @@ export function ChatPage({
|
||||
filterManager.selectedDocumentSets,
|
||||
filterManager.timeRange,
|
||||
filterManager.selectedTags,
|
||||
selectedFiles.map((file) => file.id),
|
||||
selectedFolders.map((folder) => folder.id)
|
||||
selectedFiles.map((file) => file.id)
|
||||
// selectedFolders.map((folder) => folder.id)
|
||||
),
|
||||
selectedDocumentIds: selectedDocuments
|
||||
.filter(
|
||||
@@ -1957,7 +1962,7 @@ export function ChatPage({
|
||||
) => {
|
||||
const [_, llmModel] = getFinalLLM(
|
||||
llmProviders,
|
||||
liveAssistant,
|
||||
liveAssistant ?? null,
|
||||
llmManager.currentLlm
|
||||
);
|
||||
const llmAcceptsImages = modelSupportsImageInput(llmProviders, llmModel);
|
||||
@@ -1984,7 +1989,7 @@ export function ChatPage({
|
||||
formData.append("files", file);
|
||||
const response: FileResponse[] = await uploadFile(formData, null);
|
||||
|
||||
if (response.length > 0) {
|
||||
if (response.length > 0 && response[0] !== undefined) {
|
||||
const uploadedFile = response[0];
|
||||
|
||||
if (intent == UploadIntent.ADD_TO_DOCUMENTS) {
|
||||
@@ -2392,14 +2397,14 @@ export function ChatPage({
|
||||
? true
|
||||
: false
|
||||
}
|
||||
humanMessage={humanMessage}
|
||||
humanMessage={humanMessage ?? null}
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
modal={true}
|
||||
ref={innerSidebarElementRef}
|
||||
closeSidebar={() => {
|
||||
setDocumentSidebarVisible(false);
|
||||
}}
|
||||
selectedMessage={aiMessage}
|
||||
selectedMessage={aiMessage ?? null}
|
||||
selectedDocuments={selectedDocuments}
|
||||
toggleDocumentSelection={toggleDocumentSelection}
|
||||
clearSelectedDocuments={clearSelectedDocuments}
|
||||
@@ -2548,7 +2553,7 @@ export function ChatPage({
|
||||
`}
|
||||
>
|
||||
<DocumentResults
|
||||
humanMessage={humanMessage}
|
||||
humanMessage={humanMessage ?? null}
|
||||
agenticMessage={
|
||||
aiMessage?.sub_questions?.length! > 0 ||
|
||||
messageHistory.find(
|
||||
@@ -2563,7 +2568,7 @@ export function ChatPage({
|
||||
closeSidebar={() =>
|
||||
setTimeout(() => setDocumentSidebarVisible(false), 300)
|
||||
}
|
||||
selectedMessage={aiMessage}
|
||||
selectedMessage={aiMessage ?? null}
|
||||
selectedDocuments={selectedDocuments}
|
||||
toggleDocumentSelection={toggleDocumentSelection}
|
||||
clearSelectedDocuments={clearSelectedDocuments}
|
||||
@@ -2675,14 +2680,16 @@ export function ChatPage({
|
||||
<div className="h-full w-[95%] mx-auto flex flex-col justify-center items-center">
|
||||
<ChatIntro selectedPersona={liveAssistant} />
|
||||
|
||||
<StarterMessages
|
||||
currentPersona={currentPersona}
|
||||
onSubmit={(messageOverride) =>
|
||||
onSubmit({
|
||||
messageOverride,
|
||||
})
|
||||
}
|
||||
/>
|
||||
{currentPersona && (
|
||||
<StarterMessages
|
||||
currentPersona={currentPersona}
|
||||
onSubmit={(messageOverride) =>
|
||||
onSubmit({
|
||||
messageOverride,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
StreamingPhaseText,
|
||||
} from "./message/StreamingMessages";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import next from "next";
|
||||
|
||||
export function useOrderedPhases(externalPhase: StreamingPhase) {
|
||||
const [phaseQueue, setPhaseQueue] = useState<StreamingPhase[]>([]);
|
||||
@@ -62,7 +63,9 @@ export function useOrderedPhases(externalPhase: StreamingPhase) {
|
||||
setPhaseQueue((prevQueue) => {
|
||||
if (prevQueue.length > 0) {
|
||||
const [nextPhase, ...rest] = prevQueue;
|
||||
setDisplayedPhases((prev) => [...prev, nextPhase]);
|
||||
if (nextPhase !== undefined) {
|
||||
setDisplayedPhases((prev) => [...prev, nextPhase]);
|
||||
}
|
||||
lastDisplayTimeRef.current = Date.now();
|
||||
return rest;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "@/lib/hooks";
|
||||
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { destructureValue } from "@/lib/llm/utils";
|
||||
import { parseLlmDescriptor } from "@/lib/llm/utils";
|
||||
import { useState } from "react";
|
||||
import { Hoverable } from "@/components/Hoverable";
|
||||
import { IconType } from "react-icons";
|
||||
@@ -53,7 +53,9 @@ export default function RegenerateOption({
|
||||
</div>
|
||||
}
|
||||
onSelect={(value) => {
|
||||
const { name, provider, modelName } = destructureValue(value as string);
|
||||
const { name, provider, modelName } = parseLlmDescriptor(
|
||||
value as string
|
||||
);
|
||||
regenerate({
|
||||
name: name,
|
||||
provider: provider,
|
||||
|
||||
@@ -29,7 +29,7 @@ export function useIntersectionObserver({
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting) {
|
||||
if (entry !== undefined && entry.isIntersecting) {
|
||||
onIntersect();
|
||||
}
|
||||
}, options);
|
||||
|
||||
@@ -347,12 +347,14 @@ export const FolderList = ({
|
||||
showDeleteModal={showDeleteModal}
|
||||
/>
|
||||
))}
|
||||
{folders.length == 1 && folders[0].chat_sessions.length == 0 && (
|
||||
<p className="text-sm font-normal text-subtle mt-2">
|
||||
{" "}
|
||||
Drag a chat into a folder to save for later{" "}
|
||||
</p>
|
||||
)}
|
||||
{folders.length == 1 &&
|
||||
folders[0] &&
|
||||
folders[0].chat_sessions.length == 0 && (
|
||||
<p className="text-sm font-normal text-subtle mt-2">
|
||||
{" "}
|
||||
Drag a chat into a folder to save for later{" "}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -262,8 +262,9 @@ export function ChatInputBar({
|
||||
if (items) {
|
||||
const pastedFiles = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].kind === "file") {
|
||||
const file = items[i].getAsFile();
|
||||
const item = items[i];
|
||||
if (item && item.kind === "file") {
|
||||
const file = item.getAsFile();
|
||||
if (file) pastedFiles.push(file);
|
||||
}
|
||||
}
|
||||
@@ -360,26 +361,35 @@ export function ChatInputBar({
|
||||
handlePromptInput(text);
|
||||
};
|
||||
|
||||
let startFilterAt = "";
|
||||
if (message !== undefined) {
|
||||
const message_segments = message
|
||||
.slice(message.lastIndexOf("@") + 1)
|
||||
.split(/\s/);
|
||||
if (message_segments[0]) {
|
||||
startFilterAt = message_segments[0].toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
const assistantTagOptions = assistantOptions.filter((assistant) =>
|
||||
assistant.name.toLowerCase().startsWith(
|
||||
message
|
||||
.slice(message.lastIndexOf("@") + 1)
|
||||
.split(/\s/)[0]
|
||||
.toLowerCase()
|
||||
)
|
||||
assistant.name.toLowerCase().startsWith(startFilterAt)
|
||||
);
|
||||
|
||||
let startFilterSlash = "";
|
||||
if (message !== undefined) {
|
||||
const message_segments = message
|
||||
.slice(message.lastIndexOf("/") + 1)
|
||||
.split(/\s/);
|
||||
if (message_segments[0]) {
|
||||
startFilterSlash = message_segments[0].toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
const [tabbingIconIndex, setTabbingIconIndex] = useState(0);
|
||||
|
||||
const filteredPrompts = inputPrompts.filter(
|
||||
(prompt) =>
|
||||
prompt.active &&
|
||||
prompt.prompt.toLowerCase().startsWith(
|
||||
message
|
||||
.slice(message.lastIndexOf("/") + 1)
|
||||
.split(/\s/)[0]
|
||||
.toLowerCase()
|
||||
)
|
||||
prompt.active && prompt.prompt.toLowerCase().startsWith(startFilterSlash)
|
||||
);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
@@ -402,11 +412,15 @@ export function ChatInputBar({
|
||||
if (showPrompts) {
|
||||
const selectedPrompt =
|
||||
filteredPrompts[tabbingIconIndex >= 0 ? tabbingIconIndex : 0];
|
||||
updateInputPrompt(selectedPrompt);
|
||||
if (selectedPrompt) {
|
||||
updateInputPrompt(selectedPrompt);
|
||||
}
|
||||
} else {
|
||||
const option =
|
||||
assistantTagOptions[tabbingIconIndex >= 0 ? tabbingIconIndex : 0];
|
||||
updatedTaggedAssistant(option);
|
||||
if (option) {
|
||||
updatedTaggedAssistant(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { getDisplayNameForModel } from "@/lib/hooks";
|
||||
import {
|
||||
modelSupportsImageInput,
|
||||
destructureValue,
|
||||
parseLlmDescriptor,
|
||||
structureValue,
|
||||
} from "@/lib/llm/utils";
|
||||
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
|
||||
@@ -74,18 +74,21 @@ export default function LLMPopover({
|
||||
modelConfiguration.is_visible
|
||||
) {
|
||||
uniqueModelNames.add(modelConfiguration.name);
|
||||
llmOptionsByProvider[llmProvider.provider].push({
|
||||
name: modelConfiguration.name,
|
||||
value: structureValue(
|
||||
llmProvider.name,
|
||||
llmProvider.provider,
|
||||
modelConfiguration.name
|
||||
),
|
||||
icon: getProviderIcon(
|
||||
llmProvider.provider,
|
||||
modelConfiguration.name
|
||||
),
|
||||
});
|
||||
const options = llmOptionsByProvider[llmProvider.provider];
|
||||
if (options) {
|
||||
options.push({
|
||||
name: modelConfiguration.name,
|
||||
value: structureValue(
|
||||
llmProvider.name,
|
||||
llmProvider.provider,
|
||||
modelConfiguration.name
|
||||
),
|
||||
icon: getProviderIcon(
|
||||
llmProvider.provider,
|
||||
modelConfiguration.name
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -121,12 +124,18 @@ export default function LLMPopover({
|
||||
|
||||
// Use useCallback to prevent function recreation
|
||||
const handleTemperatureChange = useCallback((value: number[]) => {
|
||||
setLocalTemperature(value[0]);
|
||||
const value_0 = value[0];
|
||||
if (value_0 !== undefined) {
|
||||
setLocalTemperature(value_0);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleTemperatureChangeComplete = useCallback(
|
||||
(value: number[]) => {
|
||||
llmManager.updateTemperature(value[0]);
|
||||
const value_0 = value[0];
|
||||
if (value_0 !== undefined) {
|
||||
llmManager.updateTemperature(value_0);
|
||||
}
|
||||
},
|
||||
[llmManager]
|
||||
);
|
||||
@@ -187,7 +196,7 @@ export default function LLMPopover({
|
||||
: "text-text-darker"
|
||||
}`}
|
||||
onClick={() => {
|
||||
llmManager.updateCurrentLlm(destructureValue(value));
|
||||
llmManager.updateCurrentLlm(parseLlmDescriptor(value));
|
||||
onSelect?.(value);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
|
||||
@@ -56,8 +56,9 @@ export function SimplifiedChatInputBar({
|
||||
if (items) {
|
||||
const pastedFiles = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].kind === "file") {
|
||||
const file = items[i].getAsFile();
|
||||
const item = items[i];
|
||||
if (item && item.kind === "file") {
|
||||
const file = item.getAsFile();
|
||||
if (file) pastedFiles.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,14 +377,18 @@ export function getHumanAndAIMessageFromMessageNumber(
|
||||
if (messageInd !== -1) {
|
||||
const matchingMessage = messageHistory[messageInd];
|
||||
const pairedMessage =
|
||||
matchingMessage.type === "user"
|
||||
matchingMessage && matchingMessage.type === "user"
|
||||
? messageHistory[messageInd + 1]
|
||||
: messageHistory[messageInd - 1];
|
||||
|
||||
const humanMessage =
|
||||
matchingMessage.type === "user" ? matchingMessage : pairedMessage;
|
||||
matchingMessage && matchingMessage.type === "user"
|
||||
? matchingMessage
|
||||
: pairedMessage;
|
||||
const aiMessage =
|
||||
matchingMessage.type === "user" ? pairedMessage : matchingMessage;
|
||||
matchingMessage && matchingMessage.type === "user"
|
||||
? pairedMessage
|
||||
: matchingMessage;
|
||||
|
||||
return {
|
||||
humanMessage,
|
||||
@@ -433,13 +437,25 @@ export function groupSessionsByDateRange(chatSessions: ChatSession[]) {
|
||||
const diffDays = diffTime / (1000 * 3600 * 24); // Convert time difference to days
|
||||
|
||||
if (diffDays < 1) {
|
||||
groups["Today"].push(chatSession);
|
||||
const groups_today = groups["Today"];
|
||||
if (groups_today) {
|
||||
groups_today.push(chatSession);
|
||||
}
|
||||
} else if (diffDays <= 7) {
|
||||
groups["Previous 7 Days"].push(chatSession);
|
||||
const groups_7 = groups["Previous 7 Days"];
|
||||
if (groups_7) {
|
||||
groups_7.push(chatSession);
|
||||
}
|
||||
} else if (diffDays <= 30) {
|
||||
groups["Previous 30 days"].push(chatSession);
|
||||
const groups_30 = groups["Previous 30 Days"];
|
||||
if (groups_30) {
|
||||
groups_30.push(chatSession);
|
||||
}
|
||||
} else {
|
||||
groups["Over 30 days"].push(chatSession);
|
||||
const groups_over_30 = groups["Over 30 days"];
|
||||
if (groups_over_30) {
|
||||
groups_over_30.push(chatSession);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -560,7 +576,11 @@ export function buildLatestMessageChain(
|
||||
|
||||
//
|
||||
// remove system message
|
||||
if (finalMessageList.length > 0 && finalMessageList[0].type === "system") {
|
||||
if (
|
||||
finalMessageList.length > 0 &&
|
||||
finalMessageList[0] &&
|
||||
finalMessageList[0].type === "system"
|
||||
) {
|
||||
finalMessageList = finalMessageList.slice(1);
|
||||
}
|
||||
return finalMessageList.concat(additionalMessagesOnMainline);
|
||||
|
||||
@@ -163,7 +163,7 @@ export const AgenticMessage = ({
|
||||
}, processed);
|
||||
|
||||
const lastMatch = matches[matches.length - 1];
|
||||
if (!lastMatch.endsWith("```")) {
|
||||
if (lastMatch && !lastMatch.endsWith("```")) {
|
||||
processed = preprocessLaTeX(processed);
|
||||
}
|
||||
}
|
||||
@@ -403,6 +403,11 @@ export const AgenticMessage = ({
|
||||
otherMessagesCanSwitchTo &&
|
||||
otherMessagesCanSwitchTo.length > 1;
|
||||
|
||||
let otherMessage: number | undefined = undefined;
|
||||
if (currentMessageInd && otherMessagesCanSwitchTo) {
|
||||
otherMessage = otherMessagesCanSwitchTo[currentMessageInd - 1];
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!allowStreaming) {
|
||||
return;
|
||||
@@ -587,28 +592,21 @@ export const AgenticMessage = ({
|
||||
>
|
||||
<TooltipGroup>
|
||||
<div className="flex justify-start w-full gap-x-0.5">
|
||||
{includeMessageSwitcher && (
|
||||
<div className="-mx-1 mr-auto">
|
||||
<MessageSwitcher
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
onMessageSelection(
|
||||
otherMessagesCanSwitchTo[
|
||||
currentMessageInd - 1
|
||||
]
|
||||
);
|
||||
}}
|
||||
handleNext={() => {
|
||||
onMessageSelection(
|
||||
otherMessagesCanSwitchTo[
|
||||
currentMessageInd + 1
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{includeMessageSwitcher &&
|
||||
otherMessage !== undefined && (
|
||||
<div className="-mx-1 mr-auto">
|
||||
<MessageSwitcher
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
onMessageSelection(otherMessage!);
|
||||
}}
|
||||
handleNext={() => {
|
||||
onMessageSelection(otherMessage!);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CustomTooltip showTick line content="Copy">
|
||||
<CopyButton
|
||||
@@ -675,28 +673,21 @@ export const AgenticMessage = ({
|
||||
>
|
||||
<TooltipGroup>
|
||||
<div className="flex justify-start w-full gap-x-0.5">
|
||||
{includeMessageSwitcher && (
|
||||
<div className="-mx-1 mr-auto">
|
||||
<MessageSwitcher
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
onMessageSelection(
|
||||
otherMessagesCanSwitchTo[
|
||||
currentMessageInd - 1
|
||||
]
|
||||
);
|
||||
}}
|
||||
handleNext={() => {
|
||||
onMessageSelection(
|
||||
otherMessagesCanSwitchTo[
|
||||
currentMessageInd + 1
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{includeMessageSwitcher &&
|
||||
otherMessage !== undefined && (
|
||||
<div className="-mx-1 mr-auto">
|
||||
<MessageSwitcher
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
onMessageSelection(otherMessage!);
|
||||
}}
|
||||
handleNext={() => {
|
||||
onMessageSelection(otherMessage!);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CustomTooltip showTick line content="Copy">
|
||||
<CopyButton
|
||||
|
||||
@@ -34,77 +34,83 @@ export const MemoizedAnchor = memo(
|
||||
if (value?.startsWith("[") && value?.endsWith("]")) {
|
||||
const match = value.match(/\[(D|Q)?(\d+)\]/);
|
||||
if (match) {
|
||||
const isUserFileCitation = userFiles?.length && userFiles.length > 0;
|
||||
if (isUserFileCitation) {
|
||||
const index = Math.min(
|
||||
parseInt(match[2], 10) - 1,
|
||||
userFiles?.length - 1
|
||||
);
|
||||
const associatedUserFile = userFiles?.[index];
|
||||
if (!associatedUserFile) {
|
||||
return <a href={children as string}>{children}</a>;
|
||||
}
|
||||
} else if (!isUserFileCitation) {
|
||||
const index = parseInt(match[2], 10) - 1;
|
||||
const associatedDoc = docs?.[index];
|
||||
if (!associatedDoc) {
|
||||
return <a href={children as string}>{children}</a>;
|
||||
}
|
||||
} else {
|
||||
const index = parseInt(match[2], 10) - 1;
|
||||
const associatedSubQuestion = subQuestions?.[index];
|
||||
if (!associatedSubQuestion) {
|
||||
return <a href={href || (children as string)}>{children}</a>;
|
||||
const match_item = match[2];
|
||||
if (match_item !== undefined) {
|
||||
const isUserFileCitation = userFiles?.length && userFiles.length > 0;
|
||||
if (isUserFileCitation) {
|
||||
const index = Math.min(
|
||||
parseInt(match_item, 10) - 1,
|
||||
userFiles?.length - 1
|
||||
);
|
||||
const associatedUserFile = userFiles?.[index];
|
||||
if (!associatedUserFile) {
|
||||
return <a href={children as string}>{children}</a>;
|
||||
}
|
||||
} else if (!isUserFileCitation) {
|
||||
const index = parseInt(match_item, 10) - 1;
|
||||
const associatedDoc = docs?.[index];
|
||||
if (!associatedDoc) {
|
||||
return <a href={children as string}>{children}</a>;
|
||||
}
|
||||
} else {
|
||||
const index = parseInt(match_item, 10) - 1;
|
||||
const associatedSubQuestion = subQuestions?.[index];
|
||||
if (!associatedSubQuestion) {
|
||||
return <a href={href || (children as string)}>{children}</a>;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
const isSubQuestion = match[1] === "Q";
|
||||
const isDocument = !isSubQuestion;
|
||||
const match_item = match[2];
|
||||
if (match_item !== undefined) {
|
||||
const isSubQuestion = match[1] === "Q";
|
||||
const isDocument = !isSubQuestion;
|
||||
|
||||
// Fix: parseInt now uses match[2], which is the numeric part
|
||||
const index = parseInt(match[2], 10) - 1;
|
||||
// Fix: parseInt now uses match[2], which is the numeric part
|
||||
const index = parseInt(match_item, 10) - 1;
|
||||
|
||||
const associatedDoc = isDocument ? docs?.[index] : null;
|
||||
const associatedSubQuestion = isSubQuestion
|
||||
? subQuestions?.[index]
|
||||
: undefined;
|
||||
const associatedDoc = isDocument ? docs?.[index] : null;
|
||||
const associatedSubQuestion = isSubQuestion
|
||||
? subQuestions?.[index]
|
||||
: undefined;
|
||||
|
||||
if (!associatedDoc && !associatedSubQuestion) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
if (!associatedDoc && !associatedSubQuestion) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
let icon: React.ReactNode = null;
|
||||
if (associatedDoc?.source_type === "web") {
|
||||
icon = <WebResultIcon url={associatedDoc.link} />;
|
||||
} else {
|
||||
icon = (
|
||||
<SourceIcon
|
||||
sourceType={associatedDoc?.source_type as ValidSources}
|
||||
iconSize={18}
|
||||
/>
|
||||
let icon: React.ReactNode = null;
|
||||
if (associatedDoc?.source_type === "web") {
|
||||
icon = <WebResultIcon url={associatedDoc.link} />;
|
||||
} else {
|
||||
icon = (
|
||||
<SourceIcon
|
||||
sourceType={associatedDoc?.source_type as ValidSources}
|
||||
iconSize={18}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const associatedDocInfo = associatedDoc
|
||||
? {
|
||||
...associatedDoc,
|
||||
icon: icon as any,
|
||||
link: associatedDoc.link,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<MemoizedLink
|
||||
updatePresentingDocument={updatePresentingDocument}
|
||||
href={href}
|
||||
document={associatedDocInfo}
|
||||
question={associatedSubQuestion}
|
||||
openQuestion={openQuestion}
|
||||
>
|
||||
{children}
|
||||
</MemoizedLink>
|
||||
);
|
||||
}
|
||||
const associatedDocInfo = associatedDoc
|
||||
? {
|
||||
...associatedDoc,
|
||||
icon: icon as any,
|
||||
link: associatedDoc.link,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<MemoizedLink
|
||||
updatePresentingDocument={updatePresentingDocument}
|
||||
href={href}
|
||||
document={associatedDocInfo}
|
||||
question={associatedSubQuestion}
|
||||
openQuestion={openQuestion}
|
||||
>
|
||||
{children}
|
||||
</MemoizedLink>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -336,7 +336,7 @@ export const AIMessage = ({
|
||||
}, content);
|
||||
|
||||
const lastMatch = matches[matches.length - 1];
|
||||
if (!lastMatch.endsWith("```")) {
|
||||
if (lastMatch && !lastMatch.endsWith("```")) {
|
||||
return preprocessLaTeX(content);
|
||||
}
|
||||
}
|
||||
@@ -490,6 +490,11 @@ export const AIMessage = ({
|
||||
otherMessagesCanSwitchTo &&
|
||||
otherMessagesCanSwitchTo.length > 1;
|
||||
|
||||
let otherMessage: number | undefined = undefined;
|
||||
if (currentMessageInd && otherMessagesCanSwitchTo) {
|
||||
otherMessage = otherMessagesCanSwitchTo[currentMessageInd - 1];
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id={isComplete ? "onyx-ai-message" : undefined}
|
||||
@@ -732,28 +737,21 @@ export const AIMessage = ({
|
||||
>
|
||||
<TooltipGroup>
|
||||
<div className="flex justify-start w-full gap-x-0.5">
|
||||
{includeMessageSwitcher && (
|
||||
<div className="-mx-1 mr-auto">
|
||||
<MessageSwitcher
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
onMessageSelection(
|
||||
otherMessagesCanSwitchTo[
|
||||
currentMessageInd - 1
|
||||
]
|
||||
);
|
||||
}}
|
||||
handleNext={() => {
|
||||
onMessageSelection(
|
||||
otherMessagesCanSwitchTo[
|
||||
currentMessageInd + 1
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{includeMessageSwitcher &&
|
||||
otherMessage !== undefined && (
|
||||
<div className="-mx-1 mr-auto">
|
||||
<MessageSwitcher
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
onMessageSelection(otherMessage!);
|
||||
}}
|
||||
handleNext={() => {
|
||||
onMessageSelection(otherMessage!);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CustomTooltip showTick line content="Copy">
|
||||
<CopyButton
|
||||
@@ -814,28 +812,21 @@ export const AIMessage = ({
|
||||
>
|
||||
<TooltipGroup>
|
||||
<div className="flex justify-start w-full gap-x-0.5">
|
||||
{includeMessageSwitcher && (
|
||||
<div className="-mx-1 mr-auto">
|
||||
<MessageSwitcher
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
onMessageSelection(
|
||||
otherMessagesCanSwitchTo[
|
||||
currentMessageInd - 1
|
||||
]
|
||||
);
|
||||
}}
|
||||
handleNext={() => {
|
||||
onMessageSelection(
|
||||
otherMessagesCanSwitchTo[
|
||||
currentMessageInd + 1
|
||||
]
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{includeMessageSwitcher &&
|
||||
otherMessage !== undefined && (
|
||||
<div className="-mx-1 mr-auto">
|
||||
<MessageSwitcher
|
||||
currentPage={currentMessageInd + 1}
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
onMessageSelection(otherMessage!);
|
||||
}}
|
||||
handleNext={() => {
|
||||
onMessageSelection(otherMessage!);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CustomTooltip showTick line content="Copy">
|
||||
<CopyButton
|
||||
@@ -1021,6 +1012,11 @@ export const HumanMessage = ({
|
||||
? otherMessagesCanSwitchTo?.indexOf(messageId)
|
||||
: undefined;
|
||||
|
||||
let otherMessage: number | undefined = undefined;
|
||||
if (currentMessageInd && otherMessagesCanSwitchTo) {
|
||||
otherMessage = otherMessagesCanSwitchTo[currentMessageInd - 1];
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id="onyx-human-message"
|
||||
@@ -1216,6 +1212,7 @@ export const HumanMessage = ({
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-x-0.5 mt-1">
|
||||
{currentMessageInd !== undefined &&
|
||||
otherMessage !== undefined &&
|
||||
onMessageSelection &&
|
||||
otherMessagesCanSwitchTo &&
|
||||
otherMessagesCanSwitchTo.length > 1 && (
|
||||
@@ -1226,15 +1223,11 @@ export const HumanMessage = ({
|
||||
totalPages={otherMessagesCanSwitchTo.length}
|
||||
handlePrevious={() => {
|
||||
stopGenerating();
|
||||
onMessageSelection(
|
||||
otherMessagesCanSwitchTo[currentMessageInd - 1]
|
||||
);
|
||||
onMessageSelection(otherMessage!);
|
||||
}}
|
||||
handleNext={() => {
|
||||
stopGenerating();
|
||||
onMessageSelection(
|
||||
otherMessagesCanSwitchTo[currentMessageInd + 1]
|
||||
);
|
||||
onMessageSelection(otherMessage!);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -131,26 +131,50 @@ export const useStreamingMessages = (
|
||||
|
||||
for (let i = 0; i < actualSubQs.length; i++) {
|
||||
const sq = actualSubQs[i];
|
||||
if (sq === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const p = progressRef.current[i];
|
||||
if (p === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dynSQ = dynamicSubQuestionsRef.current[i];
|
||||
if (dynSQ === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
const p2 = progressRef.current[i - 1];
|
||||
if (p2 === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!p2.questionDone) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Always stream the first subquestion (index 0)
|
||||
// For others, only stream if the previous question is complete
|
||||
if (i === 0 || (i > 0 && progressRef.current[i - 1].questionDone)) {
|
||||
if (sq.question) {
|
||||
const nextIndex = p.questionCharIndex + 1;
|
||||
if (nextIndex <= sq.question.length) {
|
||||
dynSQ.question = sq.question.slice(0, nextIndex);
|
||||
p.questionCharIndex = nextIndex;
|
||||
if (nextIndex >= sq.question.length && sq.is_stopped) {
|
||||
p.questionDone = true;
|
||||
}
|
||||
didStreamQuestion = true;
|
||||
allQuestionsComplete = false;
|
||||
|
||||
// Break after streaming one question to ensure sequential behavior
|
||||
break;
|
||||
if (sq.question) {
|
||||
const nextIndex = p.questionCharIndex + 1;
|
||||
if (nextIndex <= sq.question.length) {
|
||||
dynSQ.question = sq.question.slice(0, nextIndex);
|
||||
p.questionCharIndex = nextIndex;
|
||||
if (nextIndex >= sq.question.length && sq.is_stopped) {
|
||||
p.questionDone = true;
|
||||
}
|
||||
didStreamQuestion = true;
|
||||
allQuestionsComplete = false;
|
||||
|
||||
// Break after streaming one question to ensure sequential behavior
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,10 +200,21 @@ export const useStreamingMessages = (
|
||||
// 2) Handle SUB_QUERIES → CONTEXT_DOCS → ANSWER → COMPLETE
|
||||
for (let i = 0; i < actualSubQs.length; i++) {
|
||||
const sq = actualSubQs[i];
|
||||
if (sq === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dynSQ = dynamicSubQuestionsRef.current[i];
|
||||
if (dynSQ === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dynSQ.answer_streaming = sq.answer_streaming;
|
||||
|
||||
const p = progressRef.current[i];
|
||||
if (p === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wait for subquestion #0 or the previous subquestion's progress
|
||||
if (p.currentPhase === StreamingPhase.WAITING) {
|
||||
@@ -192,8 +227,9 @@ export const useStreamingMessages = (
|
||||
} else {
|
||||
const prevP = progressRef.current[i - 1];
|
||||
if (
|
||||
prevP.currentPhase === StreamingPhase.ANSWER ||
|
||||
prevP.currentPhase === StreamingPhase.COMPLETE
|
||||
prevP !== undefined &&
|
||||
(prevP.currentPhase === StreamingPhase.ANSWER ||
|
||||
prevP.currentPhase === StreamingPhase.COMPLETE)
|
||||
) {
|
||||
// Can only proceed if we've spent enough time in WAITING
|
||||
if (canTransition(p) && !p.waitingTimeoutSet) {
|
||||
@@ -216,18 +252,35 @@ export const useStreamingMessages = (
|
||||
|
||||
// "Stream" the subqueries (in this code, it just sets them all at once)
|
||||
while (dynSQ.sub_queries!.length < subQueries.length) {
|
||||
const subquery_detail = subQueries[0];
|
||||
if (subquery_detail === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const subquery_id = subquery_detail.query_id;
|
||||
dynSQ.sub_queries!.push({
|
||||
query: "",
|
||||
query_id: subQueries[0].query_id,
|
||||
query_id: subquery_id,
|
||||
});
|
||||
}
|
||||
|
||||
for (let j = 0; j < subQueries.length; j++) {
|
||||
const dyn_subquery_detail = dynSQ.sub_queries![j];
|
||||
if (dyn_subquery_detail === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const subquery_detail = subQueries[j];
|
||||
if (subquery_detail === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
dynSQ.sub_queries![j].query.length < subQueries[j].query.length
|
||||
dyn_subquery_detail.query.length < subquery_detail.query.length
|
||||
) {
|
||||
dynSQ.sub_queries![j].query = subQueries[j].query;
|
||||
dyn_subquery_detail.query = subquery_detail.query;
|
||||
} else {
|
||||
// console.log("NOT STEAMING");
|
||||
// console.log("NOT STREAMING");
|
||||
}
|
||||
}
|
||||
// console.log(subQueries);
|
||||
|
||||
@@ -137,7 +137,7 @@ const SubQuestionDisplay: React.FC<{
|
||||
}, content);
|
||||
|
||||
const lastMatch = matches[matches.length - 1];
|
||||
if (!lastMatch.endsWith("```")) {
|
||||
if (lastMatch && !lastMatch.endsWith("```")) {
|
||||
return preprocessLaTeX(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ export function extractCodeText(
|
||||
// Match code block with optional language declaration
|
||||
const codeBlockMatch = codeText.match(/^```[^\n]*\n([\s\S]*?)\n?```$/);
|
||||
if (codeBlockMatch) {
|
||||
codeText = codeBlockMatch[1];
|
||||
const codeTextMatch = codeBlockMatch[1];
|
||||
if (codeTextMatch !== undefined) {
|
||||
codeText = codeTextMatch;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize indentation
|
||||
@@ -133,7 +136,7 @@ export const preprocessLaTeX = (content: string) => {
|
||||
// Restore code blocks
|
||||
const restoredCodeBlocks = restoredDollars.replace(
|
||||
/___CODE_BLOCK_(\d+)___/g,
|
||||
(_, index) => codeBlocks[parseInt(index)]
|
||||
(_, index) => codeBlocks[parseInt(index)] ?? ""
|
||||
);
|
||||
|
||||
return restoredCodeBlocks;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Modal } from "@/components/Modal";
|
||||
import { getDisplayNameForModel, LlmDescriptor } from "@/lib/hooks";
|
||||
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
|
||||
|
||||
import { destructureValue, structureValue } from "@/lib/llm/utils";
|
||||
import { parseLlmDescriptor, structureValue } from "@/lib/llm/utils";
|
||||
import { setUserDefaultModel } from "@/lib/users/UserSettings";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
@@ -96,7 +96,7 @@ export function UserSettingsModal({
|
||||
}, [onClose]);
|
||||
|
||||
const defaultModelDestructured = defaultModel
|
||||
? destructureValue(defaultModel)
|
||||
? parseLlmDescriptor(defaultModel)
|
||||
: null;
|
||||
const modelOptionsByProvider = new Map<
|
||||
string,
|
||||
@@ -125,14 +125,17 @@ export function UserSettingsModal({
|
||||
llmProvider.model_configurations.forEach((modelConfiguration) => {
|
||||
if (!uniqueModelNames.has(modelConfiguration.name)) {
|
||||
uniqueModelNames.add(modelConfiguration.name);
|
||||
llmOptionsByProvider[llmProvider.provider].push({
|
||||
name: modelConfiguration.name,
|
||||
value: structureValue(
|
||||
llmProvider.name,
|
||||
llmProvider.provider,
|
||||
modelConfiguration.name
|
||||
),
|
||||
});
|
||||
const llmOptions = llmOptionsByProvider[llmProvider.provider];
|
||||
if (llmOptions) {
|
||||
llmOptions.push({
|
||||
name: modelConfiguration.name,
|
||||
value: structureValue(
|
||||
llmProvider.name,
|
||||
llmProvider.provider,
|
||||
modelConfiguration.name
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -143,7 +146,7 @@ export function UserSettingsModal({
|
||||
|
||||
if (response.ok) {
|
||||
if (defaultModel && setCurrentLlm) {
|
||||
setCurrentLlm(destructureValue(defaultModel));
|
||||
setCurrentLlm(parseLlmDescriptor(defaultModel));
|
||||
}
|
||||
setPopup({
|
||||
message: "Default model updated successfully",
|
||||
@@ -361,9 +364,9 @@ export function UserSettingsModal({
|
||||
currentLlm={
|
||||
defaultModel
|
||||
? structureValue(
|
||||
destructureValue(defaultModel).provider,
|
||||
parseLlmDescriptor(defaultModel).provider,
|
||||
"",
|
||||
destructureValue(defaultModel).modelName
|
||||
parseLlmDescriptor(defaultModel).modelName
|
||||
)
|
||||
: null
|
||||
}
|
||||
@@ -373,7 +376,7 @@ export function UserSettingsModal({
|
||||
handleChangedefaultModel(null);
|
||||
} else {
|
||||
const { modelName, provider, name } =
|
||||
destructureValue(selected);
|
||||
parseLlmDescriptor(selected);
|
||||
if (modelName && name) {
|
||||
handleChangedefaultModel(
|
||||
structureValue(provider, "", modelName)
|
||||
|
||||
@@ -181,7 +181,6 @@ export default function UserFolderContent({ folderId }: { folderId: number }) {
|
||||
},
|
||||
});
|
||||
const [selectedModel, setSelectedModel] = useState(modelDescriptors[0]);
|
||||
|
||||
const [uploadingFiles, setUploadingFiles] = useState<string[]>([]);
|
||||
const [uploadProgress, setUploadProgress] = useState<UploadProgress[]>([]);
|
||||
const [isCleanupModalOpen, setIsCleanupModalOpen] = useState(false);
|
||||
@@ -237,6 +236,21 @@ export default function UserFolderContent({ folderId }: { folderId: number }) {
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedModel === undefined) {
|
||||
return (
|
||||
<div className="min-h-full w-full min-w-0 flex-1 mx-auto max-w-5xl px-4 pb-20 md:pl-8 mt-6 md:pr-8 2xl:pr-14">
|
||||
<div className="text-left space-y-4">
|
||||
<h2 className="flex items-center gap-1.5 text-lg font-medium leading-tight tracking-tight max-md:hidden">
|
||||
No Models defined
|
||||
</h2>
|
||||
<p className="text-neutral-600">
|
||||
This page requires models to be available.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const totalTokens = folderDetails.files.reduce(
|
||||
(acc, file) => acc + (file.token_count || 0),
|
||||
0
|
||||
|
||||
@@ -24,7 +24,9 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
<Select
|
||||
value={selectedModel.modelName}
|
||||
onValueChange={(value) =>
|
||||
onSelectModel(models.find((m) => m.modelName === value) || models[0])
|
||||
onSelectModel(
|
||||
models.find((m) => m.modelName === value) || models[0] || selectedModel
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
|
||||
@@ -72,8 +72,13 @@ export const FileListItem: React.FC<FileListItemProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
const checkStatus = async () => {
|
||||
const status = await getFilesIndexingStatus([file.id]);
|
||||
setIndexingStatus(status[file.id]);
|
||||
const status_by_file_id = await getFilesIndexingStatus([file.id]);
|
||||
if (status_by_file_id) {
|
||||
const status = status_by_file_id[file.id];
|
||||
if (status !== undefined) {
|
||||
setIndexingStatus(status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkStatus();
|
||||
|
||||
@@ -343,6 +343,14 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
const [activeId, setActiveId] = useState<string | null>(null);
|
||||
const [isHoveringRight, setIsHoveringRight] = useState(false);
|
||||
|
||||
let activeSplit = "";
|
||||
if (activeId !== undefined && activeId !== null) {
|
||||
const active_part_1 = activeId.split("-")[1];
|
||||
if (active_part_1) {
|
||||
activeSplit = active_part_1;
|
||||
}
|
||||
}
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
@@ -364,13 +372,16 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
const { setPopup } = usePopup();
|
||||
|
||||
// Create model descriptors and selectedModel state
|
||||
// why is this hardcoded here?
|
||||
const modelDescriptors: LLMModelDescriptor[] = [
|
||||
{ modelName: "Claude 3 Opus", maxTokens: 200000 },
|
||||
{ modelName: "Claude 3 Sonnet", maxTokens: 180000 },
|
||||
{ modelName: "GPT-4", maxTokens: 128000 },
|
||||
];
|
||||
|
||||
const [selectedModel, setSelectedModel] = useState(modelDescriptors[0]);
|
||||
const firstModelDescriptor = modelDescriptors[0]!;
|
||||
|
||||
const [selectedModel, setSelectedModel] = useState(firstModelDescriptor);
|
||||
|
||||
// Add a new state for tracking uploads
|
||||
const [uploadStartTime, setUploadStartTime] = useState<number | null>(null);
|
||||
@@ -811,6 +822,9 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
const addUploadedFileToContext = async (files: FileList) => {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
if (file === undefined) {
|
||||
continue;
|
||||
}
|
||||
// Add file to uploading files state
|
||||
setUploadingFiles((prev) => [...prev, { name: file.name, progress: 0 }]);
|
||||
const formData = new FormData();
|
||||
@@ -819,7 +833,10 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
|
||||
if (response.length > 0) {
|
||||
const uploadedFile = response[0];
|
||||
addSelectedFile(uploadedFile);
|
||||
if (uploadedFile !== undefined) {
|
||||
addSelectedFile(uploadedFile);
|
||||
}
|
||||
|
||||
markFileComplete(file.name);
|
||||
}
|
||||
}
|
||||
@@ -1214,29 +1231,23 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
</SortableContext>
|
||||
|
||||
<DragOverlay>
|
||||
{activeId ? (
|
||||
{activeId && activeSplit ? (
|
||||
<DraggableItem
|
||||
id={activeId}
|
||||
type={activeId.startsWith("folder") ? "folder" : "file"}
|
||||
item={
|
||||
activeId.startsWith("folder")
|
||||
? folders.find(
|
||||
(f) =>
|
||||
f.id === parseInt(activeId.split("-")[1], 10)
|
||||
(f) => f.id === parseInt(activeSplit, 10)
|
||||
)!
|
||||
: currentFolderFiles.find(
|
||||
(f) =>
|
||||
f.id === parseInt(activeId.split("-")[1], 10)
|
||||
(f) => f.id === parseInt(activeSplit, 10)
|
||||
)!
|
||||
}
|
||||
isSelected={
|
||||
activeId.startsWith("folder")
|
||||
? selectedFolderIds.has(
|
||||
parseInt(activeId.split("-")[1], 10)
|
||||
)
|
||||
: selectedFileIds.has(
|
||||
parseInt(activeId.split("-")[1], 10)
|
||||
)
|
||||
? selectedFolderIds.has(parseInt(activeSplit, 10))
|
||||
: selectedFileIds.has(parseInt(activeSplit, 10))
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
@@ -1334,8 +1345,10 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
// Extract domain from URL to help with detection
|
||||
const urlObj = new URL(url);
|
||||
|
||||
const createdFile: FileResponse = response[0];
|
||||
addSelectedFile(createdFile);
|
||||
const createdFile = response[0];
|
||||
if (createdFile !== undefined) {
|
||||
addSelectedFile(createdFile);
|
||||
}
|
||||
// Make sure to remove the uploading file indicator when done
|
||||
markFileComplete(url);
|
||||
}
|
||||
|
||||
@@ -94,6 +94,21 @@ export function SharedChatDisplay({
|
||||
processRawChatHistory(chatSession.messages)
|
||||
);
|
||||
|
||||
const firstMessage = messages[0];
|
||||
|
||||
if (firstMessage === undefined) {
|
||||
return (
|
||||
<div className="min-h-full w-full">
|
||||
<div className="mx-auto w-fit pt-8">
|
||||
<Callout type="danger" title="Shared Chat Not Found">
|
||||
No messages found in shared chat.
|
||||
</Callout>
|
||||
</div>
|
||||
<BackToOnyxButton documentSidebarVisible={documentSidebarVisible} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{presentingDocument && (
|
||||
@@ -106,7 +121,7 @@ export function SharedChatDisplay({
|
||||
<div className="md:hidden">
|
||||
<Modal noPadding noScroll>
|
||||
<DocumentResults
|
||||
humanMessage={messages[0]}
|
||||
humanMessage={firstMessage}
|
||||
agenticMessage={false}
|
||||
isSharedChat={true}
|
||||
selectedMessage={
|
||||
@@ -163,7 +178,7 @@ export function SharedChatDisplay({
|
||||
`}
|
||||
>
|
||||
<DocumentResults
|
||||
humanMessage={messages[0]}
|
||||
humanMessage={firstMessage}
|
||||
agenticMessage={false}
|
||||
modal={false}
|
||||
isSharedChat={true}
|
||||
|
||||
@@ -65,7 +65,9 @@ export function getDatesList(startDate: Date): string[] {
|
||||
|
||||
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
||||
const dateStr = d.toISOString().split("T")[0]; // convert date object to 'YYYY-MM-DD' format
|
||||
datesList.push(dateStr);
|
||||
if (dateStr !== undefined) {
|
||||
datesList.push(dateStr);
|
||||
}
|
||||
}
|
||||
|
||||
return datesList;
|
||||
|
||||
@@ -25,7 +25,11 @@ export function FeedbackChart({
|
||||
<ThreeDotsLoader />
|
||||
</div>
|
||||
);
|
||||
} else if (!queryAnalyticsData || queryAnalyticsError) {
|
||||
} else if (
|
||||
!queryAnalyticsData ||
|
||||
queryAnalyticsData[0] === undefined ||
|
||||
queryAnalyticsError
|
||||
) {
|
||||
chart = (
|
||||
<div className="h-80 text-red-600 text-bold flex flex-col">
|
||||
<p className="m-auto">Failed to fetch feedback data...</p>
|
||||
|
||||
@@ -24,7 +24,11 @@ export function OnyxBotChart({
|
||||
<ThreeDotsLoader />
|
||||
</div>
|
||||
);
|
||||
} else if (!onyxBotAnalyticsData || onyxBotAnalyticsError) {
|
||||
} else if (
|
||||
!onyxBotAnalyticsData ||
|
||||
onyxBotAnalyticsData[0] == undefined ||
|
||||
onyxBotAnalyticsError
|
||||
) {
|
||||
chart = (
|
||||
<div className="h-80 text-red-600 text-bold flex flex-col">
|
||||
<p className="m-auto">Failed to fetch feedback data...</p>
|
||||
|
||||
@@ -73,9 +73,12 @@ export function PersonaMessagesChart({
|
||||
highlightedIndex >= 0 &&
|
||||
highlightedIndex < filteredPersonaList.length
|
||||
) {
|
||||
setSelectedPersonaId(filteredPersonaList[highlightedIndex].id);
|
||||
setSearchQuery("");
|
||||
setHighlightedIndex(-1);
|
||||
const filteredPersona = filteredPersonaList[highlightedIndex];
|
||||
if (filteredPersona !== undefined) {
|
||||
setSelectedPersonaId(filteredPersona.id);
|
||||
setSearchQuery("");
|
||||
setHighlightedIndex(-1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "Escape":
|
||||
|
||||
@@ -33,6 +33,7 @@ export function QueryPerformanceChart({
|
||||
);
|
||||
} else if (
|
||||
!queryAnalyticsData ||
|
||||
queryAnalyticsData[0] === undefined ||
|
||||
!userAnalyticsData ||
|
||||
queryAnalyticsError ||
|
||||
userAnalyticsError
|
||||
|
||||
@@ -14,6 +14,16 @@ async function Page(props: { params: Promise<{ id: string }> }) {
|
||||
];
|
||||
const [standardAnswersResponse, standardAnswerCategoriesResponse] =
|
||||
await Promise.all(tasks);
|
||||
|
||||
if (standardAnswersResponse === undefined) {
|
||||
return (
|
||||
<ErrorCallout
|
||||
errorTitle="Something went wrong :("
|
||||
errorMsg={`Failed to fetch standard answers.`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!standardAnswersResponse.ok) {
|
||||
return (
|
||||
<ErrorCallout
|
||||
@@ -37,6 +47,15 @@ async function Page(props: { params: Promise<{ id: string }> }) {
|
||||
);
|
||||
}
|
||||
|
||||
if (standardAnswerCategoriesResponse === undefined) {
|
||||
return (
|
||||
<ErrorCallout
|
||||
errorTitle="Something went wrong :("
|
||||
errorMsg={`Failed to fetch standard answer categories.`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!standardAnswerCategoriesResponse.ok) {
|
||||
return (
|
||||
<ErrorCallout
|
||||
|
||||
@@ -33,10 +33,20 @@ export function ImageUpload({
|
||||
type: "error",
|
||||
message: "Only one file can be uploaded at a time",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setTmpImageUrl(URL.createObjectURL(acceptedFiles[0]));
|
||||
setSelectedFile(acceptedFiles[0]);
|
||||
const acceptedFile = acceptedFiles[0];
|
||||
if (acceptedFile === undefined) {
|
||||
setPopup({
|
||||
type: "error",
|
||||
message: "acceptedFile cannot be undefined",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setTmpImageUrl(URL.createObjectURL(acceptedFile));
|
||||
setSelectedFile(acceptedFile);
|
||||
setDragActive(false);
|
||||
}}
|
||||
onDragLeave={() => setDragActive(false)}
|
||||
|
||||
@@ -39,7 +39,11 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
|
||||
if (!isUserAdmin) {
|
||||
formikProps.setFieldValue("is_public", false);
|
||||
}
|
||||
if (userGroups.length === 1 && !isUserAdmin) {
|
||||
if (
|
||||
userGroups.length === 1 &&
|
||||
userGroups[0] !== undefined &&
|
||||
!isUserAdmin
|
||||
) {
|
||||
formikProps.setFieldValue("groups", [userGroups[0].id]);
|
||||
setShouldHideContent(true);
|
||||
} else if (formikProps.values.is_public) {
|
||||
@@ -58,13 +62,21 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
|
||||
return null;
|
||||
}
|
||||
|
||||
let firstUserGroupName = "Unknown";
|
||||
if (userGroups) {
|
||||
const userGroup = userGroups[0];
|
||||
if (userGroup) {
|
||||
firstUserGroupName = userGroup.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldHideContent && enforceGroupSelection) {
|
||||
return (
|
||||
<>
|
||||
{userGroups && (
|
||||
<div className="mb-1 font-medium text-base">
|
||||
This {objectName} will be assigned to group{" "}
|
||||
<b>{userGroups[0].name}</b>.
|
||||
<b>{firstUserGroupName}</b>.
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -157,7 +157,9 @@ export function UserDropdown({
|
||||
text-base
|
||||
"
|
||||
>
|
||||
{user && user.email ? user.email[0].toUpperCase() : "A"}
|
||||
{user && user.email
|
||||
? user.email[0] !== undefined && user.email[0].toUpperCase()
|
||||
: "A"}
|
||||
</div>
|
||||
{notifications && notifications.length > 0 && (
|
||||
<div className="absolute -right-0.5 -top-0.5 w-3 h-3 bg-red-500 rounded-full"></div>
|
||||
|
||||
@@ -56,6 +56,7 @@ export function AccessTypeGroupSelector({
|
||||
if (
|
||||
access_type.value === "private" &&
|
||||
userGroups.length === 1 &&
|
||||
userGroups[0] !== undefined &&
|
||||
!isUserAdmin
|
||||
) {
|
||||
groups_helpers.setValue([userGroups[0].id]);
|
||||
@@ -87,7 +88,7 @@ export function AccessTypeGroupSelector({
|
||||
if (shouldHideContent) {
|
||||
return (
|
||||
<>
|
||||
{userGroups && (
|
||||
{userGroups && userGroups[0] !== undefined && (
|
||||
<div className="mb-1 font-medium text-base">
|
||||
This Connector will be assigned to group <b>{userGroups[0].name}</b>
|
||||
.
|
||||
|
||||
@@ -27,8 +27,20 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
<div>
|
||||
<Dropzone
|
||||
onDrop={(acceptedFiles) => {
|
||||
const filesToSet = multiple ? acceptedFiles : [acceptedFiles[0]];
|
||||
setSelectedFiles(filesToSet);
|
||||
let filesToSet: File[] = [];
|
||||
if (multiple) {
|
||||
filesToSet = acceptedFiles;
|
||||
} else {
|
||||
const acceptedFile = acceptedFiles[0];
|
||||
if (acceptedFile !== undefined) {
|
||||
filesToSet = [acceptedFile];
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToSet !== undefined) {
|
||||
setSelectedFiles(filesToSet);
|
||||
}
|
||||
|
||||
setDragActive(false);
|
||||
if (name) {
|
||||
setFieldValue(name, multiple ? filesToSet : filesToSet[0]);
|
||||
|
||||
@@ -100,12 +100,14 @@ export function getUniqueIcons(docs: OnyxDocument[]): JSX.Element[] {
|
||||
while (uniqueIcons.length < 3) {
|
||||
// The last icon in the array
|
||||
const lastIcon = uniqueIcons[uniqueIcons.length - 1];
|
||||
// Clone it with a new key
|
||||
uniqueIcons.push(
|
||||
React.cloneElement(lastIcon, {
|
||||
key: `${lastIcon.key}-dup-${uniqueIcons.length}`,
|
||||
})
|
||||
);
|
||||
if (lastIcon) {
|
||||
// Clone it with a new key
|
||||
uniqueIcons.push(
|
||||
React.cloneElement(lastIcon, {
|
||||
key: `${lastIcon.key}-dup-${uniqueIcons.length}`,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Slice to just the first 3 if there are more than 3
|
||||
@@ -172,12 +174,14 @@ export function getUniqueFileIcons(files: FileResponse[]): JSX.Element[] {
|
||||
while (uniqueIcons.length < 3) {
|
||||
// The last icon in the array
|
||||
const lastIcon = uniqueIcons[uniqueIcons.length - 1];
|
||||
// Clone it with a new key
|
||||
uniqueIcons.push(
|
||||
React.cloneElement(lastIcon, {
|
||||
key: `${lastIcon.key}-dup-${uniqueIcons.length}`,
|
||||
})
|
||||
);
|
||||
if (lastIcon) {
|
||||
// Clone it with a new key
|
||||
uniqueIcons.push(
|
||||
React.cloneElement(lastIcon, {
|
||||
key: `${lastIcon.key}-dup-${uniqueIcons.length}`,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Slice to just the first 3 if there are more than 3
|
||||
|
||||
@@ -56,7 +56,10 @@ export const ChatProvider: React.FC<{
|
||||
setFolders(
|
||||
folders.map((folder) => {
|
||||
if (folder.folder_id) {
|
||||
folder.display_priority = displayPriorityMap[folder.folder_id];
|
||||
const display_priority = displayPriorityMap[folder.folder_id];
|
||||
if (display_priority !== undefined) {
|
||||
folder.display_priority = display_priority;
|
||||
}
|
||||
}
|
||||
return folder;
|
||||
})
|
||||
|
||||
@@ -50,6 +50,9 @@ function useLocalStorageState<T>(
|
||||
return [state, setValue];
|
||||
}
|
||||
|
||||
const firstLightExtensionImage = lightExtensionImages[0]!;
|
||||
const firstDarkExtensionImage = darkExtensionImages[0]!;
|
||||
|
||||
export function NRFPreferencesProvider({
|
||||
children,
|
||||
}: {
|
||||
@@ -62,12 +65,12 @@ export function NRFPreferencesProvider({
|
||||
const [defaultLightBackgroundUrl, setDefaultLightBackgroundUrl] =
|
||||
useLocalStorageState<string>(
|
||||
LocalStorageKeys.LIGHT_BG_URL,
|
||||
lightExtensionImages[0]
|
||||
firstLightExtensionImage
|
||||
);
|
||||
const [defaultDarkBackgroundUrl, setDefaultDarkBackgroundUrl] =
|
||||
useLocalStorageState<string>(
|
||||
LocalStorageKeys.DARK_BG_URL,
|
||||
darkExtensionImages[0]
|
||||
firstDarkExtensionImage
|
||||
);
|
||||
const [shortcuts, setShortcuts] = useLocalStorageState<Shortcut[]>(
|
||||
LocalStorageKeys.SHORTCUTS,
|
||||
|
||||
@@ -108,10 +108,17 @@ export function ModelSelector({
|
||||
const groupedModelOptions = modelOptions.reduce(
|
||||
(acc, model) => {
|
||||
const [type] = model.model_name.split("/");
|
||||
if (!acc[type]) {
|
||||
acc[type] = [];
|
||||
if (type !== undefined) {
|
||||
if (!acc[type]) {
|
||||
acc[type] = [];
|
||||
}
|
||||
|
||||
const acc_by_type = acc[type];
|
||||
if (acc_by_type !== undefined) {
|
||||
acc_by_type.push(model);
|
||||
}
|
||||
}
|
||||
acc[type].push(model);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, HostedEmbeddingModel[]>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const ApiKeyForm = ({
|
||||
setPopup: (popup: PopupSpec) => void;
|
||||
hideSuccess?: boolean;
|
||||
}) => {
|
||||
const defaultProvider = providerOptions[0]?.name;
|
||||
const defaultProvider = providerOptions[0]!.name;
|
||||
const providerNameToIndexMap = new Map<string, number>();
|
||||
providerOptions.forEach((provider, index) => {
|
||||
providerNameToIndexMap.set(provider.name, index);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { getDisplayNameForModel } from "@/lib/hooks";
|
||||
import {
|
||||
destructureValue,
|
||||
parseLlmDescriptor,
|
||||
modelSupportsImageInput,
|
||||
structureValue,
|
||||
} from "@/lib/llm/utils";
|
||||
@@ -63,7 +63,7 @@ export const LLMSelector: React.FC<LLMSelectorProps> = ({
|
||||
: null;
|
||||
|
||||
const destructuredCurrentValue = currentLlm
|
||||
? destructureValue(currentLlm)
|
||||
? parseLlmDescriptor(currentLlm)
|
||||
: null;
|
||||
|
||||
const currentLlmName = destructuredCurrentValue?.modelName;
|
||||
|
||||
@@ -22,7 +22,9 @@ export const usePopupFromQuery = (messages: PopupMessages) => {
|
||||
if (messageValue && messageValue in messages) {
|
||||
const popupMessage = messages[messageValue];
|
||||
router.replace(window.location.pathname);
|
||||
setPopup(popupMessage);
|
||||
if (popupMessage !== undefined) {
|
||||
setPopup(popupMessage);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -74,12 +74,18 @@ export const buildDocumentSummaryDisplay = (
|
||||
sections.push(["...", false, false]);
|
||||
}
|
||||
});
|
||||
|
||||
if (sections.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let previousIsContinuation = sections[0][2];
|
||||
let previousIsBold = sections[0][1];
|
||||
const firstSection = sections[0];
|
||||
if (firstSection === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let previousIsContinuation = firstSection[2];
|
||||
let previousIsBold = firstSection[1];
|
||||
let currentText = "";
|
||||
const finalJSX = [] as (JSX.Element | string)[];
|
||||
sections.forEach(([word, shouldBeBold, isContinuation], index) => {
|
||||
|
||||
@@ -33,9 +33,21 @@ export function Citation({
|
||||
children?: JSX.Element | string | null | ReactNode;
|
||||
index?: number;
|
||||
}) {
|
||||
const innerText = children
|
||||
? children?.toString().split("[")[1].split("]")[0]
|
||||
: index;
|
||||
let innerText = "";
|
||||
if (index !== undefined) {
|
||||
innerText = index.toString();
|
||||
}
|
||||
|
||||
if (children) {
|
||||
const childrenString = children.toString();
|
||||
const childrenSegment1 = childrenString.split("[")[1];
|
||||
if (childrenSegment1 !== undefined) {
|
||||
const childrenSegment1_0 = childrenSegment1.split("]")[0];
|
||||
if (childrenSegment1_0 !== undefined) {
|
||||
innerText = childrenSegment1_0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!document_info && !question_info) {
|
||||
return <>{children}</>;
|
||||
|
||||
@@ -42,8 +42,14 @@ export async function fetchSettingsSS(): Promise<CombinedSettings | null> {
|
||||
const results = await Promise.all(tasks);
|
||||
|
||||
let settings: Settings;
|
||||
if (!results[0].ok) {
|
||||
if (results[0].status === 403 || results[0].status === 401) {
|
||||
|
||||
const result_0 = results[0];
|
||||
if (!result_0) {
|
||||
throw new Error("Standard settings fetch failed.");
|
||||
}
|
||||
|
||||
if (!result_0.ok) {
|
||||
if (result_0.status === 403 || result_0.status === 401) {
|
||||
settings = {
|
||||
auto_scroll: true,
|
||||
application_status: ApplicationStatus.ACTIVE,
|
||||
@@ -59,41 +65,51 @@ export async function fetchSettingsSS(): Promise<CombinedSettings | null> {
|
||||
} else {
|
||||
throw new Error(
|
||||
`fetchStandardSettingsSS failed: status=${
|
||||
results[0].status
|
||||
} body=${await results[0].text()}`
|
||||
result_0.status
|
||||
} body=${await result_0.text()}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
settings = await results[0].json();
|
||||
settings = await result_0.json();
|
||||
}
|
||||
|
||||
let enterpriseSettings: EnterpriseSettings | null = null;
|
||||
if (tasks.length > 1) {
|
||||
if (!results[1].ok) {
|
||||
if (results[1].status !== 403 && results[1].status !== 401) {
|
||||
const result_1 = results[1];
|
||||
if (!result_1) {
|
||||
throw new Error("fetchEnterpriseSettingsSS failed.");
|
||||
}
|
||||
|
||||
if (!result_1.ok) {
|
||||
if (result_1.status !== 403 && result_1.status !== 401) {
|
||||
throw new Error(
|
||||
`fetchEnterpriseSettingsSS failed: status=${
|
||||
results[1].status
|
||||
} body=${await results[1].text()}`
|
||||
result_1.status
|
||||
} body=${await result_1.text()}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
enterpriseSettings = await results[1].json();
|
||||
enterpriseSettings = await result_1.json();
|
||||
}
|
||||
}
|
||||
|
||||
let customAnalyticsScript: string | null = null;
|
||||
if (tasks.length > 2) {
|
||||
if (!results[2].ok) {
|
||||
if (results[2].status !== 403) {
|
||||
const result_2 = results[2];
|
||||
if (!result_2) {
|
||||
throw new Error("fetchCustomAnalyticsScriptSS failed.");
|
||||
}
|
||||
|
||||
if (!result_2.ok) {
|
||||
if (result_2.status !== 403) {
|
||||
throw new Error(
|
||||
`fetchCustomAnalyticsScriptSS failed: status=${
|
||||
results[2].status
|
||||
} body=${await results[2].text()}`
|
||||
result_2.status
|
||||
} body=${await result_2.text()}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
customAnalyticsScript = await results[2].json();
|
||||
customAnalyticsScript = await result_2.json();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,14 +43,21 @@ const CsvContent: React.FC<ContentComponentProps> = ({
|
||||
|
||||
const csvData = await response.text();
|
||||
const rows = csvData.trim().split("\n");
|
||||
const parsedHeaders = rows[0].split(",");
|
||||
const firstRow = rows[0];
|
||||
if (!firstRow) {
|
||||
throw new Error("CSV file is empty");
|
||||
}
|
||||
const parsedHeaders = firstRow.split(",");
|
||||
setHeaders(parsedHeaders);
|
||||
|
||||
const parsedData: Record<string, string>[] = rows.slice(1).map((row) => {
|
||||
const values = row.split(",");
|
||||
return parsedHeaders.reduce<Record<string, string>>(
|
||||
(obj, header, index) => {
|
||||
obj[header] = values[index];
|
||||
const val = values[index];
|
||||
if (val !== undefined) {
|
||||
obj[header] = val;
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
{}
|
||||
|
||||
@@ -139,7 +139,7 @@ const ChartTooltipContent = React.forwardRef<
|
||||
}
|
||||
|
||||
const [item] = payload;
|
||||
const key = `${labelKey || item.dataKey || item.name || "value"}`;
|
||||
const key = `${labelKey || item?.dataKey || item?.name || "value"}`;
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
||||
const value =
|
||||
!labelKey && typeof label === "string"
|
||||
|
||||
@@ -199,9 +199,13 @@ function usePaginatedFetch<T extends PaginatedType>({
|
||||
useEffect(() => {
|
||||
const { batchNum, batchPageNum } = batchAndPageIndices;
|
||||
|
||||
if (cachedBatches[batchNum] && cachedBatches[batchNum][batchPageNum]) {
|
||||
setCurrentPageData(cachedBatches[batchNum][batchPageNum]);
|
||||
setIsLoading(false);
|
||||
const cachedBatch = cachedBatches[batchNum];
|
||||
if (cachedBatch !== undefined) {
|
||||
const cachedBatchPage = cachedBatch[batchPageNum];
|
||||
if (cachedBatchPage !== undefined) {
|
||||
setCurrentPageData(cachedBatchPage);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}, [currentPage, cachedBatches, pagesPerBatch]);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export function generateRandomIconShape(): GridShape {
|
||||
.fill(null)
|
||||
.map(() => Array(4).fill(false));
|
||||
|
||||
const centerSquares = [
|
||||
const centerSquares: number[][] = [
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
[2, 1],
|
||||
@@ -31,14 +31,33 @@ export function generateRandomIconShape(): GridShape {
|
||||
shuffleArray(centerSquares);
|
||||
const centerFillCount = Math.floor(Math.random() * 2) + 3; // 3 or 4
|
||||
for (let i = 0; i < centerFillCount; i++) {
|
||||
const [row, col] = centerSquares[i];
|
||||
grid[row][col] = true;
|
||||
const centerSquare: number[] | undefined = centerSquares[i];
|
||||
if (centerSquare === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const [row, col] = centerSquare;
|
||||
if (row === undefined || col === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const grid_row = grid[row];
|
||||
if (grid_row === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
grid_row[col] = true;
|
||||
}
|
||||
// Randomly fill remaining squares up to 10 total
|
||||
const remainingSquares = [];
|
||||
for (let row = 0; row < 4; row++) {
|
||||
for (let col = 0; col < 4; col++) {
|
||||
if (!grid[row][col]) {
|
||||
const grid_row = grid[row];
|
||||
if (grid_row === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!grid_row[col]) {
|
||||
remainingSquares.push([row, col]);
|
||||
}
|
||||
}
|
||||
@@ -47,15 +66,29 @@ export function generateRandomIconShape(): GridShape {
|
||||
|
||||
let filledSquares = centerFillCount;
|
||||
for (const [row, col] of remainingSquares) {
|
||||
if (row === undefined || col == undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (filledSquares >= 10) break;
|
||||
grid[row][col] = true;
|
||||
|
||||
const grid_row = grid[row];
|
||||
if (grid_row === undefined) {
|
||||
continue;
|
||||
}
|
||||
grid_row[col] = true;
|
||||
filledSquares++;
|
||||
}
|
||||
|
||||
let path = "";
|
||||
for (let row = 0; row < 4; row++) {
|
||||
for (let col = 0; col < 4; col++) {
|
||||
if (grid[row][col]) {
|
||||
const grid_row = grid[row];
|
||||
if (grid_row === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (grid_row[col]) {
|
||||
const x = col * 12;
|
||||
const y = row * 12;
|
||||
path += `M ${x} ${y} L ${x + 12} ${y} L ${x + 12} ${y + 12} L ${x} ${
|
||||
@@ -72,7 +105,12 @@ function encodeGrid(grid: boolean[][]): number {
|
||||
let encoded = 0;
|
||||
for (let row = 0; row < 4; row++) {
|
||||
for (let col = 0; col < 4; col++) {
|
||||
if (grid[row][col]) {
|
||||
const grid_row = grid[row];
|
||||
if (grid_row === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (grid_row[col]) {
|
||||
encoded |= 1 << (row * 4 + col);
|
||||
}
|
||||
}
|
||||
@@ -86,8 +124,13 @@ function decodeGrid(encoded: number): boolean[][] {
|
||||
.map(() => Array(4).fill(false));
|
||||
for (let row = 0; row < 4; row++) {
|
||||
for (let col = 0; col < 4; col++) {
|
||||
const grid_row = grid[row];
|
||||
if (grid_row === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (encoded & (1 << (row * 4 + col))) {
|
||||
grid[row][col] = true;
|
||||
grid_row[col] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +149,12 @@ export function createSVG(
|
||||
let path = "";
|
||||
for (let row = 0; row < 4; row++) {
|
||||
for (let col = 0; col < 4; col++) {
|
||||
if (grid[row][col]) {
|
||||
const grid_row = grid[row];
|
||||
if (grid_row === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (grid_row[col]) {
|
||||
const x = col * 12;
|
||||
const y = row * 12;
|
||||
path += `M ${x} ${y} L ${x + 12} ${y} L ${x + 12} ${y + 12} L ${x} ${
|
||||
|
||||
@@ -48,10 +48,14 @@ export async function moveAssistantUp(
|
||||
): Promise<boolean> {
|
||||
const index = chosenAssistants.indexOf(assistantId);
|
||||
if (index > 0) {
|
||||
[chosenAssistants[index - 1], chosenAssistants[index]] = [
|
||||
chosenAssistants[index],
|
||||
chosenAssistants[index - 1],
|
||||
];
|
||||
const chosenAssistantPrev = chosenAssistants[index - 1];
|
||||
const chosenAssistant = chosenAssistants[index];
|
||||
if (chosenAssistantPrev === undefined || chosenAssistant === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
chosenAssistants[index - 1] = chosenAssistant;
|
||||
chosenAssistants[index] = chosenAssistantPrev;
|
||||
return updateUserAssistantList(chosenAssistants);
|
||||
}
|
||||
return false;
|
||||
@@ -63,10 +67,15 @@ export async function moveAssistantDown(
|
||||
): Promise<boolean> {
|
||||
const index = chosenAssistants.indexOf(assistantId);
|
||||
if (index < chosenAssistants.length - 1) {
|
||||
[chosenAssistants[index + 1], chosenAssistants[index]] = [
|
||||
chosenAssistants[index],
|
||||
chosenAssistants[index + 1],
|
||||
];
|
||||
const chosenAssistantNext = chosenAssistants[index + 1];
|
||||
const chosenAssistant = chosenAssistants[index];
|
||||
if (chosenAssistantNext === undefined || chosenAssistant === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
chosenAssistants[index + 1] = chosenAssistant;
|
||||
chosenAssistants[index] = chosenAssistantNext;
|
||||
|
||||
return updateUserAssistantList(chosenAssistants);
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -135,6 +135,7 @@ export async function deleteConnectorIfExistsAndIsUnlinked({
|
||||
);
|
||||
if (
|
||||
matchingConnectors.length > 0 &&
|
||||
matchingConnectors[0] &&
|
||||
matchingConnectors[0].credential_ids.length === 0
|
||||
) {
|
||||
const errorMsg = await deleteConnector(matchingConnectors[0].id);
|
||||
|
||||
@@ -18,6 +18,10 @@ export function objectsAreEquivalent(
|
||||
|
||||
for (let i = 0; i < aProps.length; i++) {
|
||||
const propName = aProps[i];
|
||||
if (propName === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (a[propName] !== b[propName]) {
|
||||
return false;
|
||||
}
|
||||
@@ -41,8 +45,20 @@ export function isEventWithinRef(
|
||||
if (!ref.current) return false;
|
||||
|
||||
const rect = ref.current.getBoundingClientRect();
|
||||
const clientX = "touches" in event ? event.touches[0].clientX : event.clientX;
|
||||
const clientY = "touches" in event ? event.touches[0].clientY : event.clientY;
|
||||
|
||||
let clientX: number;
|
||||
let clientY: number;
|
||||
if (event instanceof TouchEvent) {
|
||||
const touches_0 = event.touches[0];
|
||||
if (touches_0 === undefined) {
|
||||
throw new Error("Touch event must exist!");
|
||||
}
|
||||
clientX = touches_0.clientX;
|
||||
clientY = touches_0.clientY;
|
||||
} else {
|
||||
clientX = event.clientX;
|
||||
clientY = event.clientY;
|
||||
}
|
||||
|
||||
return (
|
||||
clientX >= rect.left &&
|
||||
|
||||
2
web/src/lib/generated/README.md
Normal file
2
web/src/lib/generated/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- Generated Files
|
||||
* Generated files live here. This directory should be git ignored.
|
||||
@@ -92,7 +92,7 @@ export const filterUploadedCredentials = <
|
||||
credential.credential_json.authentication_method !== "oauth_interactive"
|
||||
);
|
||||
|
||||
if (uploadedCredentials.length > 0) {
|
||||
if (uploadedCredentials.length > 0 && uploadedCredentials[0]) {
|
||||
credential_id = uploadedCredentials[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { DateRangePickerValue } from "@/components/dateRangeSelectors/AdminDateRangeSelector";
|
||||
import { SourceMetadata } from "./search/interfaces";
|
||||
import {
|
||||
destructureValue,
|
||||
parseLlmDescriptor,
|
||||
findProviderForModel,
|
||||
structureValue,
|
||||
} from "./llm/utils";
|
||||
@@ -480,7 +480,7 @@ export function useLlmManager(
|
||||
modelName: string | null | undefined
|
||||
): LlmDescriptor => {
|
||||
if (modelName) {
|
||||
const model = destructureValue(modelName);
|
||||
const model = parseLlmDescriptor(modelName);
|
||||
if (!(model.modelName && model.modelName.length > 0)) {
|
||||
const provider = llmProviders.find((p) =>
|
||||
p.model_configurations
|
||||
@@ -810,7 +810,12 @@ export function getDisplayNameForModel(modelName: string): string {
|
||||
if (modelName.startsWith("bedrock/")) {
|
||||
const parts = modelName.split("/");
|
||||
const lastPart = parts[parts.length - 1];
|
||||
return MODEL_DISPLAY_NAMES[lastPart] || lastPart;
|
||||
if (lastPart === undefined) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const displayName = MODEL_DISPLAY_NAMES[lastPart];
|
||||
return displayName || lastPart;
|
||||
}
|
||||
|
||||
return MODEL_DISPLAY_NAMES[modelName] || modelName;
|
||||
|
||||
@@ -75,12 +75,16 @@ export const structureValue = (
|
||||
return `${name}__${provider}__${modelName}`;
|
||||
};
|
||||
|
||||
export const destructureValue = (value: string): LlmDescriptor => {
|
||||
export const parseLlmDescriptor = (value: string): LlmDescriptor => {
|
||||
const [displayName, provider, modelName] = value.split("__");
|
||||
if (displayName === undefined) {
|
||||
return { name: "Unknown", provider: "", modelName: "" };
|
||||
}
|
||||
|
||||
return {
|
||||
name: displayName,
|
||||
provider,
|
||||
modelName,
|
||||
provider: provider ?? "",
|
||||
modelName: modelName ?? "",
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ export const buildFilters = (
|
||||
documentSets: string[],
|
||||
timeRange: DateRangePickerValue | null,
|
||||
tags: Tag[],
|
||||
userFileIds?: number[] | null,
|
||||
userFolderIds?: number[] | null
|
||||
userFileIds?: number[] | null
|
||||
// userFolderIds?: number[] | null
|
||||
): Filters => {
|
||||
const filters = {
|
||||
source_type:
|
||||
|
||||
@@ -28,7 +28,7 @@ export function transformLinkUri(href: string) {
|
||||
) {
|
||||
return url;
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// If it's not a valid URL with protocol, return the original href
|
||||
return href;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export class UrlBuilder {
|
||||
constructor(baseUrl: string) {
|
||||
try {
|
||||
this.url = new URL(baseUrl);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Handle relative URLs by prepending a base
|
||||
this.url = new URL(baseUrl, "http://placeholder.com");
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
Reference in New Issue
Block a user