mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-05-03 16:30:21 +02:00
My docs cleanup (#4519)
* update * improved my docs * nit * nit * k * push changes * update * looking good * k * fix preprocessing * try a fix * k * update * nit * k * quick nits * Cleanup / fixes * Fixes * Fix build * fix * fix quality checks --------- Co-authored-by: Weves <chrisweaver101@gmail.com>
This commit is contained in:
parent
115cfb6ae9
commit
df67ca18d8
@ -11,6 +11,7 @@ from onyx.server.features.persona.models import PersonaSharedNotificationData
|
||||
|
||||
def make_persona_private(
|
||||
persona_id: int,
|
||||
creator_user_id: UUID | None,
|
||||
user_ids: list[UUID] | None,
|
||||
group_ids: list[int] | None,
|
||||
db_session: Session,
|
||||
@ -29,7 +30,7 @@ def make_persona_private(
|
||||
user_ids_set = set(user_ids)
|
||||
for user_id in user_ids_set:
|
||||
db_session.add(Persona__User(persona_id=persona_id, user_id=user_id))
|
||||
|
||||
if user_id != creator_user_id:
|
||||
create_notification(
|
||||
user_id=user_id,
|
||||
notif_type=NotificationType.PERSONA_SHARED,
|
||||
|
@ -40,6 +40,7 @@ def process_llm_stream(
|
||||
# This stream will be the llm answer if no tool is chosen. When a tool is chosen,
|
||||
# the stream will contain AIMessageChunks with tool call information.
|
||||
for message in messages:
|
||||
|
||||
answer_piece = message.content
|
||||
if not isinstance(answer_piece, str):
|
||||
# this is only used for logging, so fine to
|
||||
|
@ -51,7 +51,6 @@ def _parse_agent_event(
|
||||
Parse the event into a typed object.
|
||||
Return None if we are not interested in the event.
|
||||
"""
|
||||
|
||||
event_type = event["event"]
|
||||
|
||||
# We always just yield the event data, but this piece is useful for two development reasons:
|
||||
|
@ -112,7 +112,7 @@ def pre_provision_tenant() -> None:
|
||||
|
||||
r = get_redis_client(tenant_id=ONYX_CLOUD_TENANT_ID)
|
||||
lock_provision: RedisLock = r.lock(
|
||||
OnyxRedisLocks.PRE_PROVISION_TENANT_LOCK,
|
||||
OnyxRedisLocks.CLOUD_PRE_PROVISION_TENANT_LOCK,
|
||||
timeout=_TENANT_PROVISIONING_SOFT_TIME_LIMIT,
|
||||
)
|
||||
|
||||
|
@ -167,7 +167,6 @@ class Answer:
|
||||
break
|
||||
processed_stream.append(packet)
|
||||
yield packet
|
||||
|
||||
self._processed_stream = processed_stream
|
||||
|
||||
@property
|
||||
|
@ -334,7 +334,7 @@ class OnyxRedisLocks:
|
||||
CHECK_USER_FILE_FOLDER_SYNC_BEAT_LOCK = "da_lock:check_user_file_folder_sync_beat"
|
||||
MONITOR_BACKGROUND_PROCESSES_LOCK = "da_lock:monitor_background_processes"
|
||||
CHECK_AVAILABLE_TENANTS_LOCK = "da_lock:check_available_tenants"
|
||||
PRE_PROVISION_TENANT_LOCK = "da_lock:pre_provision_tenant"
|
||||
CLOUD_PRE_PROVISION_TENANT_LOCK = "da_lock:pre_provision_tenant"
|
||||
|
||||
CONNECTOR_DOC_PERMISSIONS_SYNC_LOCK_PREFIX = (
|
||||
"da_lock:connector_doc_permissions_sync"
|
||||
@ -405,7 +405,6 @@ class OnyxCeleryTask:
|
||||
f"{ONYX_CLOUD_CELERY_TASK_PREFIX}_monitor_celery_pidbox"
|
||||
)
|
||||
|
||||
# Tenant pre-provisioning
|
||||
UPDATE_USER_FILE_FOLDER_METADATA = "update_user_file_folder_metadata"
|
||||
|
||||
CHECK_FOR_CONNECTOR_DELETION = "check_for_connector_deletion_task"
|
||||
|
@ -3,7 +3,6 @@ from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import delete
|
||||
from sqlalchemy import exists
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy import not_
|
||||
@ -168,6 +167,7 @@ def _get_persona_by_name(
|
||||
|
||||
def make_persona_private(
|
||||
persona_id: int,
|
||||
creator_user_id: UUID | None,
|
||||
user_ids: list[UUID] | None,
|
||||
group_ids: list[int] | None,
|
||||
db_session: Session,
|
||||
@ -179,7 +179,7 @@ def make_persona_private(
|
||||
|
||||
for user_uuid in user_ids:
|
||||
db_session.add(Persona__User(persona_id=persona_id, user_id=user_uuid))
|
||||
|
||||
if user_uuid != creator_user_id:
|
||||
create_notification(
|
||||
user_id=user_uuid,
|
||||
notif_type=NotificationType.PERSONA_SHARED,
|
||||
@ -262,6 +262,7 @@ def create_update_persona(
|
||||
# Privatize Persona
|
||||
versioned_make_persona_private(
|
||||
persona_id=persona.id,
|
||||
creator_user_id=user.id if user else None,
|
||||
user_ids=create_persona_request.users,
|
||||
group_ids=create_persona_request.groups,
|
||||
db_session=db_session,
|
||||
@ -297,6 +298,7 @@ def update_persona_shared_users(
|
||||
# Privatize Persona
|
||||
versioned_make_persona_private(
|
||||
persona_id=persona_id,
|
||||
creator_user_id=user.id if user else None,
|
||||
user_ids=user_ids,
|
||||
group_ids=None,
|
||||
db_session=db_session,
|
||||
@ -770,8 +772,10 @@ def get_personas_by_ids(
|
||||
def delete_persona_by_name(
|
||||
persona_name: str, db_session: Session, is_default: bool = True
|
||||
) -> None:
|
||||
stmt = delete(Persona).where(
|
||||
Persona.name == persona_name, Persona.builtin_persona == is_default
|
||||
stmt = (
|
||||
update(Persona)
|
||||
.where(Persona.name == persona_name, Persona.builtin_persona == is_default)
|
||||
.values(deleted=True)
|
||||
)
|
||||
|
||||
db_session.execute(stmt)
|
||||
|
@ -69,7 +69,8 @@ def test_llm_configuration(
|
||||
existing_provider = fetch_existing_llm_provider(
|
||||
name=test_llm_request.name, db_session=db_session
|
||||
)
|
||||
if existing_provider:
|
||||
# if an API key is not provided, use the existing provider's API key
|
||||
if existing_provider and test_api_key is None:
|
||||
test_api_key = existing_provider.api_key
|
||||
|
||||
# For this "testing" workflow, we do *not* need the actual `max_input_tokens`.
|
||||
|
@ -90,8 +90,25 @@ def get_folders(
|
||||
db_session: Session = Depends(get_session),
|
||||
) -> list[UserFolderSnapshot]:
|
||||
user_id = user.id if user else None
|
||||
folders = db_session.query(UserFolder).filter(UserFolder.user_id == user_id).all()
|
||||
return [UserFolderSnapshot.from_model(folder) for folder in folders]
|
||||
# Get folders that belong to the user or have the RECENT_DOCS_FOLDER_ID
|
||||
folders = (
|
||||
db_session.query(UserFolder)
|
||||
.filter(
|
||||
(UserFolder.user_id == user_id) | (UserFolder.id == RECENT_DOCS_FOLDER_ID)
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
# For each folder, filter files to only include those belonging to the current user
|
||||
result = []
|
||||
for folder in folders:
|
||||
folder_snapshot = UserFolderSnapshot.from_model(folder)
|
||||
folder_snapshot.files = [
|
||||
file for file in folder_snapshot.files if file.user_id == user_id
|
||||
]
|
||||
result.append(folder_snapshot)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.get("/user/folder/{folder_id}")
|
||||
@ -103,13 +120,25 @@ def get_folder(
|
||||
user_id = user.id if user else None
|
||||
folder = (
|
||||
db_session.query(UserFolder)
|
||||
.filter(UserFolder.id == folder_id, UserFolder.user_id == user_id)
|
||||
.filter(
|
||||
UserFolder.id == folder_id,
|
||||
(
|
||||
(UserFolder.user_id == user_id)
|
||||
| (UserFolder.id == RECENT_DOCS_FOLDER_ID)
|
||||
),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not folder:
|
||||
raise HTTPException(status_code=404, detail="Folder not found")
|
||||
|
||||
return UserFolderSnapshot.from_model(folder)
|
||||
folder_snapshot = UserFolderSnapshot.from_model(folder)
|
||||
# Filter files to only include those belonging to the current user
|
||||
folder_snapshot.files = [
|
||||
file for file in folder_snapshot.files if file.user_id == user_id
|
||||
]
|
||||
|
||||
return folder_snapshot
|
||||
|
||||
|
||||
RECENT_DOCS_FOLDER_ID = -1
|
||||
|
@ -27,7 +27,15 @@ ONYX_REQUEST_ID_CONTEXTVAR: contextvars.ContextVar[str | None] = contextvars.Con
|
||||
def get_current_tenant_id() -> str:
|
||||
tenant_id = CURRENT_TENANT_ID_CONTEXTVAR.get()
|
||||
if tenant_id is None:
|
||||
import traceback
|
||||
|
||||
if not MULTI_TENANT:
|
||||
return POSTGRES_DEFAULT_SCHEMA
|
||||
raise RuntimeError("Tenant ID is not set. This should never happen.")
|
||||
|
||||
stack_trace = traceback.format_stack()
|
||||
error_message = (
|
||||
"Tenant ID is not set. This should never happen.\nStack trace:\n"
|
||||
+ "".join(stack_trace)
|
||||
)
|
||||
raise RuntimeError(error_message)
|
||||
return tenant_id
|
||||
|
70
web/package-lock.json
generated
70
web/package-lock.json
generated
@ -1886,7 +1886,6 @@
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.6",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
@ -2711,7 +2710,7 @@
|
||||
"version": "1.51.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.1.tgz",
|
||||
"integrity": "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.51.1"
|
||||
@ -4812,7 +4811,6 @@
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "9.6.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
@ -4821,7 +4819,6 @@
|
||||
},
|
||||
"node_modules/@types/eslint-scope": {
|
||||
"version": "3.7.7",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "*",
|
||||
@ -4921,7 +4918,6 @@
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
@ -5276,7 +5272,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/ast": {
|
||||
"version": "1.14.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/helper-numbers": "1.13.2",
|
||||
@ -5285,22 +5280,18 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/floating-point-hex-parser": {
|
||||
"version": "1.13.2",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-api-error": {
|
||||
"version": "1.13.2",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-buffer": {
|
||||
"version": "1.14.1",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-numbers": {
|
||||
"version": "1.13.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
|
||||
@ -5310,12 +5301,10 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
|
||||
"version": "1.13.2",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-section": {
|
||||
"version": "1.14.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
@ -5326,7 +5315,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/ieee754": {
|
||||
"version": "1.13.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@xtuc/ieee754": "^1.2.0"
|
||||
@ -5334,7 +5322,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/leb128": {
|
||||
"version": "1.13.2",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@xtuc/long": "4.2.2"
|
||||
@ -5342,12 +5329,10 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/utf8": {
|
||||
"version": "1.13.2",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-edit": {
|
||||
"version": "1.14.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
@ -5362,7 +5347,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-gen": {
|
||||
"version": "1.14.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
@ -5374,7 +5358,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-opt": {
|
||||
"version": "1.14.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
@ -5385,7 +5368,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-parser": {
|
||||
"version": "1.14.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
@ -5398,7 +5380,6 @@
|
||||
},
|
||||
"node_modules/@webassemblyjs/wast-printer": {
|
||||
"version": "1.14.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
@ -5407,12 +5388,10 @@
|
||||
},
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@xtuc/long": {
|
||||
"version": "4.2.2",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
@ -5467,7 +5446,6 @@
|
||||
},
|
||||
"node_modules/ajv-formats": {
|
||||
"version": "2.1.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
@ -5483,7 +5461,6 @@
|
||||
},
|
||||
"node_modules/ajv-formats/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@ -5498,7 +5475,6 @@
|
||||
},
|
||||
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ajv-keywords": {
|
||||
@ -6186,7 +6162,6 @@
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
@ -6419,7 +6394,6 @@
|
||||
},
|
||||
"node_modules/chrome-trace-event": {
|
||||
"version": "1.0.4",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
@ -7253,7 +7227,6 @@
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
@ -7386,7 +7359,6 @@
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.6.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
@ -7949,7 +7921,6 @@
|
||||
},
|
||||
"node_modules/esrecurse": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"estraverse": "^5.2.0"
|
||||
@ -7960,7 +7931,6 @@
|
||||
},
|
||||
"node_modules/estraverse": {
|
||||
"version": "5.3.0",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
@ -7992,7 +7962,6 @@
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
@ -8048,7 +8017,6 @@
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
@ -8094,7 +8062,6 @@
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.0.6",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -8560,7 +8527,6 @@
|
||||
},
|
||||
"node_modules/glob-to-regexp": {
|
||||
"version": "0.4.1",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
@ -8617,7 +8583,6 @@
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/graphemer": {
|
||||
@ -10752,7 +10717,6 @@
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.11.5"
|
||||
@ -11198,7 +11162,6 @@
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
@ -11755,7 +11718,6 @@
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@ -11763,7 +11725,6 @@
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
@ -11846,7 +11807,6 @@
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.2",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next": {
|
||||
@ -15033,7 +14993,7 @@
|
||||
"version": "1.51.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz",
|
||||
"integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.51.1"
|
||||
@ -15052,7 +15012,7 @@
|
||||
"version": "1.51.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz",
|
||||
"integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
@ -15531,7 +15491,6 @@
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
@ -16123,7 +16082,6 @@
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@ -16294,7 +16252,6 @@
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -16383,7 +16340,6 @@
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.2",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
@ -16591,7 +16547,6 @@
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@ -17205,7 +17160,6 @@
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@ -17213,7 +17167,6 @@
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.39.0",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
@ -17230,7 +17183,6 @@
|
||||
},
|
||||
"node_modules/terser-webpack-plugin": {
|
||||
"version": "5.3.14",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
@ -17263,7 +17215,6 @@
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@ -17278,7 +17229,6 @@
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
|
||||
"version": "5.1.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
@ -17289,7 +17239,6 @@
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/jest-worker": {
|
||||
"version": "27.5.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
@ -17302,12 +17251,10 @@
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
@ -17325,7 +17272,6 @@
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
@ -17339,12 +17285,10 @@
|
||||
},
|
||||
"node_modules/terser/node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/terser/node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
@ -18144,7 +18088,6 @@
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
@ -18172,7 +18115,6 @@
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.98.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
@ -18315,7 +18257,6 @@
|
||||
},
|
||||
"node_modules/webpack/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@ -18330,7 +18271,6 @@
|
||||
},
|
||||
"node_modules/webpack/node_modules/ajv-keywords": {
|
||||
"version": "5.1.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
@ -18341,7 +18281,6 @@
|
||||
},
|
||||
"node_modules/webpack/node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
@ -18353,7 +18292,6 @@
|
||||
},
|
||||
"node_modules/webpack/node_modules/estraverse": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
@ -18361,12 +18299,10 @@
|
||||
},
|
||||
"node_modules/webpack/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/webpack/node_modules/schema-utils": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useAuthType } from "@/lib/hooks";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
|
||||
function parseJsonWithTrailingCommas(jsonString: string) {
|
||||
// Regular expression to remove trailing commas before } or ]
|
||||
@ -159,25 +160,14 @@ function ActionForm({
|
||||
component="div"
|
||||
className="mb-4 text-error text-sm"
|
||||
/>
|
||||
<div className="mt-4 text-sm bg-blue-50 p-4 rounded-md border border-blue-200">
|
||||
<div className="mt-4 text-sm bg-blue-50 text-blue-700 dark:text-blue-300 dark:bg-blue-900 p-4 rounded-md border border-blue-200 dark:border-blue-800">
|
||||
<Link
|
||||
href="https://docs.onyx.app/tools/custom"
|
||||
className="text-link hover:underline flex items-center"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5 mr-2"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<InfoIcon className="w-4 h-4 mr-2 " />
|
||||
Learn more about actions in our documentation
|
||||
</Link>
|
||||
</div>
|
||||
@ -367,7 +357,7 @@ interface ToolFormValues {
|
||||
}
|
||||
|
||||
const ToolSchema = Yup.object().shape({
|
||||
definition: Yup.string().required("Tool definition is required"),
|
||||
definition: Yup.string().required("Action definition is required"),
|
||||
customHeaders: Yup.array()
|
||||
.of(
|
||||
Yup.object().shape({
|
||||
|
@ -27,15 +27,17 @@ export default async function Page(props: {
|
||||
);
|
||||
} else {
|
||||
body = (
|
||||
<div className="w-full my-8">
|
||||
<div className="w-full mt-8 pb-8">
|
||||
<div>
|
||||
<div>
|
||||
<CardSection>
|
||||
<ActionEditor tool={tool} />
|
||||
</CardSection>
|
||||
|
||||
<Title className="mt-12">Delete Tool</Title>
|
||||
<Text>Click the button below to permanently delete this tool.</Text>
|
||||
<Title className="mt-12">Delete Action</Title>
|
||||
<Text>
|
||||
Click the button below to permanently delete this action.
|
||||
</Text>
|
||||
<div className="flex mt-6">
|
||||
<DeleteToolButton toolId={tool.id} />
|
||||
</div>
|
||||
@ -50,7 +52,7 @@ export default async function Page(props: {
|
||||
<BackButton />
|
||||
|
||||
<AdminPageTitle
|
||||
title="Edit Tool"
|
||||
title="Edit Action"
|
||||
icon={<ToolIcon size={32} className="my-auto" />}
|
||||
/>
|
||||
|
||||
|
@ -41,11 +41,8 @@ function NewApiKeyModal({
|
||||
const [copyClicked, setCopyClicked] = useState(false);
|
||||
|
||||
return (
|
||||
<Modal onOutsideClick={onClose}>
|
||||
<Modal title="New API Key" onOutsideClick={onClose}>
|
||||
<div className="px-8 py-8">
|
||||
<div className="flex w-full border-b border-border mb-4 pb-4">
|
||||
<Title>New API Key</Title>
|
||||
</div>
|
||||
<div className="h-32">
|
||||
<Text className="mb-4">
|
||||
Make sure you copy your new API key. You won’t be able to see this
|
||||
|
@ -232,7 +232,7 @@ export function AssistantEditor({
|
||||
enabledToolsMap[tool.id] = personaCurrentToolIds.includes(tool.id);
|
||||
});
|
||||
|
||||
const { selectedFiles, selectedFolders } = useDocumentsContext();
|
||||
const { files, folders, refreshFolders } = useDocumentsContext();
|
||||
|
||||
const [showVisibilityWarning, setShowVisibilityWarning] = useState(false);
|
||||
|
||||
@ -393,19 +393,6 @@ export function AssistantEditor({
|
||||
<BackButton />
|
||||
</div>
|
||||
)}
|
||||
{filePickerModalOpen && (
|
||||
<FilePickerModal
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
isOpen={filePickerModalOpen}
|
||||
onClose={() => {
|
||||
setFilePickerModalOpen(false);
|
||||
}}
|
||||
onSave={() => {
|
||||
setFilePickerModalOpen(false);
|
||||
}}
|
||||
buttonContent="Add to Assistant"
|
||||
/>
|
||||
)}
|
||||
|
||||
{presentingDocument && (
|
||||
<TextView
|
||||
@ -556,6 +543,8 @@ export function AssistantEditor({
|
||||
|
||||
// don't set groups if marked as public
|
||||
const groups = values.is_public ? [] : values.selectedGroups;
|
||||
const teamKnowledge = values.knowledge_source === "team_knowledge";
|
||||
|
||||
const submissionData: PersonaUpsertParameters = {
|
||||
...values,
|
||||
existing_prompt_id: existingPrompt?.id ?? null,
|
||||
@ -573,8 +562,9 @@ export function AssistantEditor({
|
||||
? new Date(values.search_start_date)
|
||||
: null,
|
||||
num_chunks: numChunks,
|
||||
user_file_ids: selectedFiles.map((file) => file.id),
|
||||
user_folder_ids: selectedFolders.map((folder) => folder.id),
|
||||
document_set_ids: teamKnowledge ? values.document_set_ids : [],
|
||||
user_file_ids: teamKnowledge ? [] : values.user_file_ids,
|
||||
user_folder_ids: teamKnowledge ? [] : values.user_folder_ids,
|
||||
};
|
||||
|
||||
let personaResponse;
|
||||
@ -625,6 +615,7 @@ export function AssistantEditor({
|
||||
}
|
||||
|
||||
await refreshAssistants();
|
||||
await refreshFolders();
|
||||
|
||||
router.push(
|
||||
isAdminPage
|
||||
@ -656,7 +647,38 @@ export function AssistantEditor({
|
||||
values.llm_model_version_override || defaultModelName || ""
|
||||
);
|
||||
|
||||
// TODO: memoize this / make more efficient
|
||||
const selectedFiles = files.filter((file) =>
|
||||
values.user_file_ids.includes(file.id)
|
||||
);
|
||||
|
||||
const selectedFolders = folders.filter((folder) =>
|
||||
values.user_folder_ids.includes(folder.id)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{filePickerModalOpen && (
|
||||
<FilePickerModal
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
isOpen={filePickerModalOpen}
|
||||
onClose={() => {
|
||||
setFilePickerModalOpen(false);
|
||||
}}
|
||||
onSave={(selectedFiles, selectedFolders) => {
|
||||
setFieldValue(
|
||||
"user_file_ids",
|
||||
selectedFiles.map((file) => file.id)
|
||||
);
|
||||
setFieldValue(
|
||||
"user_folder_ids",
|
||||
selectedFolders.map((folder) => folder.id)
|
||||
);
|
||||
setFilePickerModalOpen(false);
|
||||
}}
|
||||
buttonContent="Add to Assistant"
|
||||
/>
|
||||
)}
|
||||
<Form className="w-full text-text-950 assistant-editor">
|
||||
{/* Refresh starter messages when name or description changes */}
|
||||
<p className="text-base font-normal text-2xl">
|
||||
@ -701,7 +723,10 @@ export function AssistantEditor({
|
||||
className="w-12 h-12 rounded-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
generateIdenticon((values.icon_shape || 0).toString(), 36)
|
||||
generateIdenticon(
|
||||
(values.icon_shape || 0).toString(),
|
||||
36
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -741,7 +766,9 @@ export function AssistantEditor({
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="h-3 w-3" />
|
||||
{removePersonaImage ? "Revert to Previous " : "Remove "}
|
||||
{removePersonaImage
|
||||
? "Revert to Previous "
|
||||
: "Remove "}
|
||||
Image
|
||||
</Button>
|
||||
)}
|
||||
@ -759,7 +786,9 @@ export function AssistantEditor({
|
||||
const newShape = generateRandomIconShape();
|
||||
const randomColor =
|
||||
colorOptions[
|
||||
Math.floor(Math.random() * colorOptions.length)
|
||||
Math.floor(
|
||||
Math.random() * colorOptions.length
|
||||
)
|
||||
];
|
||||
setFieldValue("icon_shape", newShape.encodedGrid);
|
||||
setFieldValue("icon_color", randomColor);
|
||||
@ -876,8 +905,9 @@ export function AssistantEditor({
|
||||
{ccPairs.length === 0 && (
|
||||
<TooltipContent side="top" align="center">
|
||||
<p className="bg-background-900 max-w-[200px] text-sm rounded-lg p-1.5 text-white">
|
||||
To use the Knowledge Action, you need to
|
||||
have at least one Connector configured.
|
||||
To use the Knowledge Action, you need
|
||||
to have at least one Connector
|
||||
configured.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
)}
|
||||
@ -950,8 +980,8 @@ export function AssistantEditor({
|
||||
Click below to add documents or folders from the
|
||||
My Document feature
|
||||
</SubLabel>
|
||||
{(selectedFiles.length > 0 ||
|
||||
selectedFolders.length > 0) && (
|
||||
{(values.user_file_ids.length > 0 ||
|
||||
values.user_folder_ids.length > 0) && (
|
||||
<div className="flex flex-wrap mb-2 max-w-sm gap-2">
|
||||
{selectedFiles.map((file) => (
|
||||
<SourceChip
|
||||
@ -972,6 +1002,7 @@ export function AssistantEditor({
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFilePickerModalOpen(true)}
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
@ -1027,7 +1058,9 @@ export function AssistantEditor({
|
||||
if (index !== -1) {
|
||||
arrayHelpers.remove(index);
|
||||
} else {
|
||||
arrayHelpers.push(documentSet.id);
|
||||
arrayHelpers.push(
|
||||
documentSet.id
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@ -1102,7 +1135,9 @@ export function AssistantEditor({
|
||||
|
||||
<div className="-mt-2">
|
||||
<div className="flex gap-x-2 mb-2 items-center">
|
||||
<div className="block font-medium text-sm">Default Model</div>
|
||||
<div className="block font-medium text-sm">
|
||||
Default Model
|
||||
</div>
|
||||
</div>
|
||||
<LLMSelector
|
||||
llmProviders={llmProviders}
|
||||
@ -1128,7 +1163,10 @@ export function AssistantEditor({
|
||||
const { modelName, provider, name } =
|
||||
destructureValue(selected);
|
||||
if (modelName && name) {
|
||||
setFieldValue("llm_model_version_override", modelName);
|
||||
setFieldValue(
|
||||
"llm_model_version_override",
|
||||
modelName
|
||||
);
|
||||
setFieldValue("llm_model_provider_override", name);
|
||||
}
|
||||
}
|
||||
@ -1177,7 +1215,10 @@ export function AssistantEditor({
|
||||
name="is_public"
|
||||
size="md"
|
||||
onCheckedChange={(checked) => {
|
||||
if (values.is_default_persona && !checked) {
|
||||
if (
|
||||
values.is_default_persona &&
|
||||
!checked
|
||||
) {
|
||||
setShowVisibilityWarning(true);
|
||||
} else {
|
||||
setFieldValue("is_public", checked);
|
||||
@ -1217,8 +1258,8 @@ export function AssistantEditor({
|
||||
<div className="flex items-center text-warning mt-2">
|
||||
<InfoIcon size={16} className="mr-2" />
|
||||
<span className="text-sm">
|
||||
Default persona must be public. Visibility has been
|
||||
automatically set to organization public.
|
||||
Default persona must be public. Visibility has
|
||||
been automatically set to organization public.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@ -1343,8 +1384,9 @@ export function AssistantEditor({
|
||||
|
||||
<SubLabel>
|
||||
Sample messages that help users understand what this
|
||||
assistant can do and how to interact with it effectively.
|
||||
New input fields will appear automatically as you type.
|
||||
assistant can do and how to interact with it
|
||||
effectively. New input fields will appear automatically
|
||||
as you type.
|
||||
</SubLabel>
|
||||
|
||||
<div className="w-full">
|
||||
@ -1419,9 +1461,8 @@ export function AssistantEditor({
|
||||
(l) => l.name === option.value
|
||||
);
|
||||
if (label) {
|
||||
const isSelected = values.label_ids.includes(
|
||||
label.id
|
||||
);
|
||||
const isSelected =
|
||||
values.label_ids.includes(label.id);
|
||||
const newLabelIds = isSelected
|
||||
? values.label_ids.filter(
|
||||
(id: number) => id !== label.id
|
||||
@ -1480,7 +1521,9 @@ export function AssistantEditor({
|
||||
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<h3 className="font-medium text-sm">Knowledge Options</h3>
|
||||
<h3 className="font-medium text-sm">
|
||||
Knowledge Options
|
||||
</h3>
|
||||
<div className="flex flex-col gap-y-4 ml-4">
|
||||
<TextFormField
|
||||
small={true}
|
||||
@ -1578,6 +1621,7 @@ export function AssistantEditor({
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
} from "@/components/ui/table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { SlackBot } from "@/lib/types";
|
||||
import { EditIcon } from "@/components/icons/icons";
|
||||
|
||||
const NUM_IN_PAGE = 20;
|
||||
|
||||
@ -83,7 +84,7 @@ export const SlackBotTable = ({ slackBots }: { slackBots: SlackBot[] }) => {
|
||||
>
|
||||
<TableCell>
|
||||
<div className="flex items-center">
|
||||
<FiEdit className="mr-4" />
|
||||
<EditIcon className="mr-4" />
|
||||
{slackBot.name}
|
||||
</div>
|
||||
</TableCell>
|
||||
|
@ -424,7 +424,6 @@ export function LLMProviderUpdateForm({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<IsPublicGroupSelector
|
||||
formikProps={formikProps}
|
||||
objectName="LLM Provider"
|
||||
|
@ -168,7 +168,7 @@ export function Explorer({
|
||||
return (
|
||||
<div>
|
||||
{popup}
|
||||
<div className="justify-center py-2">
|
||||
<div className="justify-center pt-2">
|
||||
<div className="flex items-center w-full border-2 border-border rounded-lg px-4 py-2 focus-within:border-accent bg-background-search dark:bg-transparent">
|
||||
<MagnifyingGlass />
|
||||
<textarea
|
||||
|
@ -17,6 +17,7 @@ import { getErrorMsg } from "@/lib/fetchUtils";
|
||||
import { HoverPopup } from "@/components/HoverPopup";
|
||||
import { CustomCheckbox } from "@/components/CustomCheckbox";
|
||||
import { ScoreSection } from "../ScoreEditor";
|
||||
import { truncateString } from "@/lib/utils";
|
||||
|
||||
const IsVisibleSection = ({
|
||||
document,
|
||||
@ -109,12 +110,12 @@ export const DocumentFeedbackTable = ({
|
||||
<TableRow key={document.document_id}>
|
||||
<TableCell className="whitespace-normal break-all">
|
||||
<a
|
||||
className="text-blue-600"
|
||||
className="text-blue-600 dark:text-blue-300"
|
||||
href={document.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{document.semantic_id}
|
||||
{truncateString(document.semantic_id, 100)}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
|
@ -144,6 +144,7 @@ function Main() {
|
||||
/>
|
||||
{isPaidEnterpriseFeaturesEnabled && (
|
||||
<Tabs
|
||||
className="mt-2"
|
||||
value={tabIndex.toString()}
|
||||
onValueChange={(val) => setTabIndex(parseInt(val))}
|
||||
>
|
||||
|
@ -14,7 +14,7 @@ const Page = () => {
|
||||
Authentication Error
|
||||
</h2>
|
||||
<p className="text-text-700 text-center">
|
||||
We encountered an issue while attempting to log you in.
|
||||
There was a problem with your login attempt.
|
||||
</p>
|
||||
<div className="bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800 rounded-lg p-4 shadow-sm">
|
||||
<h3 className="text-red-800 dark:text-red-400 font-semibold mb-2">
|
||||
@ -46,8 +46,11 @@ const Page = () => {
|
||||
please reach out to your system administrator for assistance.
|
||||
{NEXT_PUBLIC_CLOUD_ENABLED && (
|
||||
<span className="block mt-1 text-blue-600">
|
||||
A member of our team has been automatically notified about this
|
||||
issue.
|
||||
If you continue to experience problems please reach out to the
|
||||
Onyx team at{" "}
|
||||
<a href="mailto:support@onyx.app" className="text-blue-600">
|
||||
support@onyx.app
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
|
@ -141,9 +141,6 @@ export function EmailPasswordForm({
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
includeForgotPassword={
|
||||
NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && !isSignup
|
||||
}
|
||||
placeholder="**************"
|
||||
/>
|
||||
|
||||
|
@ -29,12 +29,15 @@ export default function LoginPage({
|
||||
useSendAuthRequiredMessage();
|
||||
return (
|
||||
<div className="flex flex-col w-full justify-center">
|
||||
{authUrl && authTypeMetadata && (
|
||||
{authUrl &&
|
||||
authTypeMetadata &&
|
||||
authTypeMetadata.authType !== "cloud" &&
|
||||
// basic auth is handled below w/ the EmailPasswordForm
|
||||
authTypeMetadata.authType !== "basic" && (
|
||||
<>
|
||||
<h2 className="text-center text-xl text-strong font-bold">
|
||||
<LoginText />
|
||||
</h2>
|
||||
|
||||
<SignInButton
|
||||
authorizeUrl={authUrl}
|
||||
authType={authTypeMetadata?.authType}
|
||||
@ -43,23 +46,34 @@ export default function LoginPage({
|
||||
)}
|
||||
|
||||
{authTypeMetadata?.authType === "cloud" && (
|
||||
<div className="mt-4 w-full justify-center">
|
||||
<div className="w-full justify-center">
|
||||
<h2 className="text-center text-xl text-strong font-bold">
|
||||
<LoginText />
|
||||
</h2>
|
||||
<EmailPasswordForm shouldVerify={true} nextUrl={nextUrl} />
|
||||
{NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && (
|
||||
<div className="flex mt-4 justify-between">
|
||||
<Link
|
||||
href="/auth/forgot-password"
|
||||
className="ml-auto text-link font-medium"
|
||||
>
|
||||
Reset Password
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{authUrl && authTypeMetadata && (
|
||||
<>
|
||||
<div className="flex items-center w-full my-4">
|
||||
<div className="flex-grow border-t border-background-300"></div>
|
||||
<span className="px-4 text-text-500">or</span>
|
||||
<div className="flex-grow border-t border-background-300"></div>
|
||||
</div>
|
||||
<EmailPasswordForm shouldVerify={true} nextUrl={nextUrl} />
|
||||
|
||||
{NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && (
|
||||
<div className="flex mt-4 justify-between">
|
||||
<Link
|
||||
href="/auth/forgot-password"
|
||||
className="text-link font-medium"
|
||||
>
|
||||
Reset Password
|
||||
</Link>
|
||||
</div>
|
||||
<SignInButton
|
||||
authorizeUrl={authUrl}
|
||||
authType={authTypeMetadata?.authType}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
@ -40,10 +40,19 @@ const Page = async (props: {
|
||||
|
||||
// if user is already logged in, take them to the main app page
|
||||
if (currentUser && currentUser.is_active && !currentUser.is_anonymous_user) {
|
||||
console.log("Login page: User is logged in, redirecting to chat", {
|
||||
userId: currentUser.id,
|
||||
is_active: currentUser.is_active,
|
||||
is_anonymous: currentUser.is_anonymous_user,
|
||||
});
|
||||
|
||||
if (authTypeMetadata?.requiresVerification && !currentUser.is_verified) {
|
||||
return redirect("/auth/waiting-on-verification");
|
||||
}
|
||||
return redirect("/chat");
|
||||
|
||||
// Add a query parameter to indicate this is a redirect from login
|
||||
// This will help prevent redirect loops
|
||||
return redirect("/chat?from=login");
|
||||
}
|
||||
|
||||
// get where to send the user to authenticate
|
||||
|
@ -82,23 +82,22 @@ const Page = async (props: {
|
||||
</>
|
||||
)}
|
||||
|
||||
{cloud && authUrl && (
|
||||
<div className="w-full justify-center">
|
||||
<SignInButton authorizeUrl={authUrl} authType="cloud" />
|
||||
<div className="flex items-center w-full my-4">
|
||||
<div className="flex-grow border-t border-background-300"></div>
|
||||
<span className="px-4 text-text-500">or</span>
|
||||
<div className="flex-grow border-t border-background-300"></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EmailPasswordForm
|
||||
isSignup
|
||||
shouldVerify={authTypeMetadata?.requiresVerification}
|
||||
nextUrl={nextUrl}
|
||||
defaultEmail={defaultEmail}
|
||||
/>
|
||||
{cloud && authUrl && (
|
||||
<div className="w-full justify-center">
|
||||
<div className="flex items-center w-full my-4">
|
||||
<div className="flex-grow border-t border-background-300"></div>
|
||||
<span className="px-4 text-text-500">or</span>
|
||||
<div className="flex-grow border-t border-background-300"></div>
|
||||
</div>
|
||||
<SignInButton authorizeUrl={authUrl} authType="cloud" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</AuthFlowContainer>
|
||||
|
@ -104,9 +104,7 @@ import {
|
||||
SIDEBAR_TOGGLED_COOKIE_NAME,
|
||||
} from "@/components/resizable/constants";
|
||||
import FixedLogo from "@/components/logo/FixedLogo";
|
||||
|
||||
import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal";
|
||||
|
||||
import { SEARCH_TOOL_ID, SEARCH_TOOL_NAME } from "./tools/constants";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
import { ApiKeyModal } from "@/components/llm/ApiKeyModal";
|
||||
@ -178,12 +176,10 @@ export function ChatPage({
|
||||
selectedFolders,
|
||||
addSelectedFile,
|
||||
addSelectedFolder,
|
||||
removeSelectedFolder,
|
||||
clearSelectedItems,
|
||||
folders: userFolders,
|
||||
files: allUserFiles,
|
||||
uploadFile,
|
||||
removeSelectedFile,
|
||||
currentMessageFiles,
|
||||
setCurrentMessageFiles,
|
||||
} = useDocumentsContext();
|
||||
@ -224,7 +220,6 @@ export function ChatPage({
|
||||
const settings = useContext(SettingsContext);
|
||||
const enterpriseSettings = settings?.enterpriseSettings;
|
||||
|
||||
const [viewingFilePicker, setViewingFilePicker] = useState(false);
|
||||
const [toggleDocSelection, setToggleDocSelection] = useState(false);
|
||||
const [documentSidebarVisible, setDocumentSidebarVisible] = useState(false);
|
||||
const [proSearchEnabled, setProSearchEnabled] = useState(proSearchToggled);
|
||||
@ -706,6 +701,7 @@ export function ChatPage({
|
||||
chatSessionId || currentSessionId(),
|
||||
newCompleteMessageMap
|
||||
);
|
||||
console.log(newCompleteMessageDetail);
|
||||
return newCompleteMessageDetail;
|
||||
};
|
||||
|
||||
@ -1171,6 +1167,13 @@ export function ChatPage({
|
||||
};
|
||||
}, [autoScrollEnabled, screenHeight, currentSessionHasSentLocalUserMessage]);
|
||||
|
||||
const reset = () => {
|
||||
setMessage("");
|
||||
setCurrentMessageFiles([]);
|
||||
clearSelectedItems();
|
||||
setLoadingError(null);
|
||||
};
|
||||
|
||||
const onSubmit = async ({
|
||||
messageIdToResend,
|
||||
messageOverride,
|
||||
@ -1201,6 +1204,61 @@ export function ChatPage({
|
||||
// Mark that we've sent a message for this session in the current page load
|
||||
markSessionMessageSent(frozenSessionId);
|
||||
|
||||
// Check if the last message was an error and remove it before proceeding with a new message
|
||||
// Ensure this isn't a regeneration or resend, as those operations should preserve the history leading up to the point of regeneration/resend.
|
||||
let currentMap = currentMessageMap(completeMessageDetail);
|
||||
let currentHistory = buildLatestMessageChain(currentMap);
|
||||
let lastMessage = currentHistory[currentHistory.length - 1];
|
||||
|
||||
if (
|
||||
lastMessage &&
|
||||
lastMessage.type === "error" &&
|
||||
!messageIdToResend &&
|
||||
!regenerationRequest
|
||||
) {
|
||||
const newMap = new Map(currentMap);
|
||||
const parentId = lastMessage.parentMessageId;
|
||||
|
||||
// Remove the error message itself
|
||||
newMap.delete(lastMessage.messageId);
|
||||
|
||||
// Remove the parent message + update the parent of the parent to no longer
|
||||
// link to the parent
|
||||
if (parentId !== null && parentId !== undefined) {
|
||||
const parentOfError = newMap.get(parentId);
|
||||
if (parentOfError) {
|
||||
const grandparentId = parentOfError.parentMessageId;
|
||||
if (grandparentId !== null && grandparentId !== undefined) {
|
||||
const grandparent = newMap.get(grandparentId);
|
||||
if (grandparent) {
|
||||
// Update grandparent to no longer link to parent
|
||||
const updatedGrandparent = {
|
||||
...grandparent,
|
||||
childrenMessageIds: (
|
||||
grandparent.childrenMessageIds || []
|
||||
).filter((id) => id !== parentId),
|
||||
latestChildMessageId:
|
||||
grandparent.latestChildMessageId === parentId
|
||||
? null
|
||||
: grandparent.latestChildMessageId,
|
||||
};
|
||||
newMap.set(grandparentId, updatedGrandparent);
|
||||
}
|
||||
}
|
||||
// Remove the parent message
|
||||
newMap.delete(parentId);
|
||||
}
|
||||
}
|
||||
// Update the state immediately so subsequent logic uses the cleaned map
|
||||
updateCompleteMessageDetail(frozenSessionId, newMap);
|
||||
console.log("Removed previous error message ID:", lastMessage.messageId);
|
||||
|
||||
// update state for the new world (with the error message removed)
|
||||
currentHistory = buildLatestMessageChain(newMap);
|
||||
currentMap = newMap;
|
||||
lastMessage = currentHistory[currentHistory.length - 1];
|
||||
}
|
||||
|
||||
if (currentChatState() != "input") {
|
||||
if (currentChatState() == "uploading") {
|
||||
setPopup({
|
||||
@ -1270,11 +1328,10 @@ export function ChatPage({
|
||||
currentSessionId()
|
||||
);
|
||||
}
|
||||
const messageMap = currentMessageMap(completeMessageDetail);
|
||||
const messageToResendParent =
|
||||
messageToResend?.parentMessageId !== null &&
|
||||
messageToResend?.parentMessageId !== undefined
|
||||
? messageMap.get(messageToResend.parentMessageId)
|
||||
? currentMap.get(messageToResend.parentMessageId)
|
||||
: null;
|
||||
const messageToResendIndex = messageToResend
|
||||
? messageHistory.indexOf(messageToResend)
|
||||
@ -1301,15 +1358,15 @@ export function ChatPage({
|
||||
|
||||
const currMessageHistory =
|
||||
messageToResendIndex !== null
|
||||
? messageHistory.slice(0, messageToResendIndex)
|
||||
: messageHistory;
|
||||
? currentHistory.slice(0, messageToResendIndex)
|
||||
: currentHistory;
|
||||
|
||||
let parentMessage =
|
||||
messageToResendParent ||
|
||||
(currMessageHistory.length > 0
|
||||
? currMessageHistory[currMessageHistory.length - 1]
|
||||
: null) ||
|
||||
(messageMap.size === 1 ? Array.from(messageMap.values())[0] : null);
|
||||
(currentMap.size === 1 ? Array.from(currentMap.values())[0] : null);
|
||||
|
||||
let currentAssistantId;
|
||||
if (alternativeAssistantOverride) {
|
||||
@ -1356,13 +1413,9 @@ export function ChatPage({
|
||||
frozenMessageMap: Map<number, Message>;
|
||||
} = null;
|
||||
try {
|
||||
const mapKeys = Array.from(
|
||||
currentMessageMap(completeMessageDetail).keys()
|
||||
);
|
||||
const systemMessage = Math.min(...mapKeys);
|
||||
|
||||
const mapKeys = Array.from(currentMap.keys());
|
||||
const lastSuccessfulMessageId =
|
||||
getLastSuccessfulMessageId(currMessageHistory) || systemMessage;
|
||||
getLastSuccessfulMessageId(currMessageHistory);
|
||||
|
||||
const stack = new CurrentMessageFIFO();
|
||||
|
||||
@ -1479,11 +1532,12 @@ export function ChatPage({
|
||||
upsertToCompleteMessageMap({
|
||||
messages: messageUpdates,
|
||||
chatSessionId: currChatSessionId,
|
||||
completeMessageMapOverride: currentMap,
|
||||
});
|
||||
currentMap = currentFrozenMessageMap;
|
||||
|
||||
const frozenMessageMap = currentFrozenMessageMap;
|
||||
initialFetchDetails = {
|
||||
frozenMessageMap,
|
||||
frozenMessageMap: currentMap,
|
||||
assistant_message_id,
|
||||
user_message_id,
|
||||
};
|
||||
@ -1715,14 +1769,18 @@ export function ChatPage({
|
||||
] as [number, number][])
|
||||
: null;
|
||||
|
||||
return upsertToCompleteMessageMap({
|
||||
const newMessageDetails = upsertToCompleteMessageMap({
|
||||
messages: messages,
|
||||
replacementsMap: replacementsMap,
|
||||
completeMessageMapOverride: frozenMessageMap,
|
||||
// Pass the latest map state
|
||||
completeMessageMapOverride: currentMap,
|
||||
chatSessionId: frozenSessionId!,
|
||||
});
|
||||
currentMap = newMessageDetails.messageMap;
|
||||
return newMessageDetails;
|
||||
};
|
||||
|
||||
const systemMessageId = Math.min(...mapKeys);
|
||||
updateFn([
|
||||
{
|
||||
messageId: regenerationRequest
|
||||
@ -1732,7 +1790,8 @@ export function ChatPage({
|
||||
type: "user",
|
||||
files: files,
|
||||
toolCall: null,
|
||||
parentMessageId: error ? null : lastSuccessfulMessageId,
|
||||
// in the frontend, every message should have a parent ID
|
||||
parentMessageId: lastSuccessfulMessageId ?? systemMessageId,
|
||||
childrenMessageIds: [
|
||||
...(regenerationRequest?.parentMessage?.childrenMessageIds ||
|
||||
[]),
|
||||
@ -1786,7 +1845,7 @@ export function ChatPage({
|
||||
} catch (e: any) {
|
||||
console.log("Error:", e);
|
||||
const errorMsg = e.message;
|
||||
upsertToCompleteMessageMap({
|
||||
const newMessageDetails = upsertToCompleteMessageMap({
|
||||
messages: [
|
||||
{
|
||||
messageId:
|
||||
@ -1809,8 +1868,9 @@ export function ChatPage({
|
||||
initialFetchDetails?.user_message_id || TEMP_USER_MESSAGE_ID,
|
||||
},
|
||||
],
|
||||
completeMessageMapOverride: currentMessageMap(completeMessageDetail),
|
||||
completeMessageMapOverride: currentMap,
|
||||
});
|
||||
currentMap = newMessageDetails.messageMap;
|
||||
}
|
||||
console.log("Finished streaming");
|
||||
setAgenticGenerating(false);
|
||||
@ -1909,15 +1969,21 @@ export function ChatPage({
|
||||
|
||||
updateChatState("uploading", currentSessionId());
|
||||
|
||||
const [uploadedFiles, error] = await uploadFilesForChat(acceptedFiles);
|
||||
if (error) {
|
||||
for (let file of acceptedFiles) {
|
||||
const formData = new FormData();
|
||||
formData.append("files", file);
|
||||
const response = await uploadFile(formData, null);
|
||||
|
||||
if (response.length > 0) {
|
||||
const uploadedFile = response[0];
|
||||
addSelectedFile(uploadedFile);
|
||||
} else {
|
||||
setPopup({
|
||||
type: "error",
|
||||
message: error,
|
||||
message: "Failed to upload file",
|
||||
});
|
||||
}
|
||||
|
||||
setCurrentMessageFiles((prev) => [...prev, ...uploadedFiles]);
|
||||
}
|
||||
|
||||
updateChatState("input", currentSessionId());
|
||||
};
|
||||
@ -2396,7 +2462,7 @@ export function ChatPage({
|
||||
liveAssistant={liveAssistant}
|
||||
setShowAssistantsModal={setShowAssistantsModal}
|
||||
explicitlyUntoggle={explicitlyUntoggle}
|
||||
reset={() => setMessage("")}
|
||||
reset={reset}
|
||||
page="chat"
|
||||
ref={innerSidebarElementRef}
|
||||
toggleSidebar={toggleSidebar}
|
||||
@ -3294,7 +3360,7 @@ export function ChatPage({
|
||||
: "w-[0px]"
|
||||
}
|
||||
`}
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
|
@ -149,7 +149,7 @@ export const FolderDropdown = forwardRef<HTMLDivElement, FolderDropdownProps>(
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
className="overflow-visible mt-2 w-full"
|
||||
className="overflow-visible pt-2 w-full"
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
@ -159,13 +159,13 @@ export const FolderDropdown = forwardRef<HTMLDivElement, FolderDropdownProps>(
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex overflow-visible items-center w-full text-text-darker rounded-md p-1 relative sticky top-0"
|
||||
className="flex overflow-visible items-center w-full text-text-darker rounded-md p-1 bg-background-sidebar dark:bg-[#000] relative sticky top-0"
|
||||
style={{ zIndex: 10 - index }}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<button
|
||||
className="flex overflow-hidden items-center flex-grow"
|
||||
className="flex overflow-hidden bg-background-sidebar dark:bg-[#000] items-center flex-grow"
|
||||
onClick={() => !isEditing && setIsOpen(!isOpen)}
|
||||
{...(isEditing ? {} : listeners)}
|
||||
>
|
||||
|
@ -402,17 +402,16 @@ export function ChatInputBar({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!showPrompts && !showSuggestions) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setTabbingIconIndex((tabbingIconIndex) =>
|
||||
Math.min(
|
||||
tabbingIconIndex + 1,
|
||||
// showPrompts ? filteredPrompts.length :
|
||||
assistantTagOptions.length
|
||||
showPrompts ? filteredPrompts.length : assistantTagOptions.length
|
||||
)
|
||||
);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
@ -440,13 +439,14 @@ export function ChatInputBar({
|
||||
ref={suggestionsRef}
|
||||
className="text-sm absolute w-[calc(100%-2rem)] top-0 transform -translate-y-full"
|
||||
>
|
||||
<div className="rounded-lg py-1 sm-1.5 bg-input-background border border-border dark:border-none shadow-lg px-1.5 mt-2 z-10">
|
||||
<div className="rounded-lg py-1 overflow-y-auto max-h-[200px] sm-1.5 bg-input-background border border-border dark:border-none shadow-lg px-1.5 mt-2 z-10">
|
||||
{assistantTagOptions.map((currentAssistant, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={`px-2 ${
|
||||
tabbingIconIndex == index && "bg-neutral-200"
|
||||
} rounded items-center rounded-lg content-start flex gap-x-1 py-2 w-full hover:bg-neutral-200/90 cursor-pointer`}
|
||||
tabbingIconIndex == index &&
|
||||
"bg-neutral-200 dark:bg-neutral-800"
|
||||
} rounded items-center rounded-lg content-start flex gap-x-1 py-2 w-full hover:bg-neutral-200/90 dark:hover:bg-neutral-800/90 cursor-pointer`}
|
||||
onClick={() => {
|
||||
updatedTaggedAssistant(currentAssistant);
|
||||
}}
|
||||
@ -468,8 +468,8 @@ export function ChatInputBar({
|
||||
target="_self"
|
||||
className={`${
|
||||
tabbingIconIndex == assistantTagOptions.length &&
|
||||
"bg-neutral-200"
|
||||
} rounded rounded-lg px-3 flex gap-x-1 py-2 w-full items-center hover:bg-neutral-200/90 cursor-pointer`}
|
||||
"bg-neutral-200 dark:bg-neutral-800"
|
||||
} rounded rounded-lg px-3 flex gap-x-1 py-2 w-full items-center hover:bg-neutral-200/90 dark:hover:bg-neutral-800/90 cursor-pointer`}
|
||||
href="/assistants/new"
|
||||
>
|
||||
<FiPlus size={17} />
|
||||
@ -484,14 +484,15 @@ export function ChatInputBar({
|
||||
ref={suggestionsRef}
|
||||
className="text-sm absolute inset-x-0 top-0 w-full transform -translate-y-full"
|
||||
>
|
||||
<div className="rounded-lg py-1.5 bg-input-background dark:border-none border border-border shadow-lg mx-2 px-1.5 mt-2 rounded z-10">
|
||||
<div className="rounded-lg overflow-y-auto max-h-[200px] py-1.5 bg-input-background dark:border-none border border-border shadow-lg mx-2 px-1.5 mt-2 rounded z-10">
|
||||
{filteredPrompts.map(
|
||||
(currentPrompt: InputPrompt, index: number) => (
|
||||
<button
|
||||
key={index}
|
||||
className={`px-2 ${
|
||||
tabbingIconIndex == index && "bg-background-dark/75"
|
||||
} rounded content-start flex gap-x-1 py-1.5 w-full hover:bg-background-dark/90 cursor-pointer`}
|
||||
tabbingIconIndex == index &&
|
||||
"bg-background-dark/75 dark:bg-neutral-800/75"
|
||||
} rounded content-start flex gap-x-1 py-1.5 w-full hover:bg-background-dark/90 dark:hover:bg-neutral-800/90 cursor-pointer`}
|
||||
onClick={() => {
|
||||
updateInputPrompt(currentPrompt);
|
||||
}}
|
||||
@ -509,8 +510,8 @@ export function ChatInputBar({
|
||||
target="_self"
|
||||
className={`${
|
||||
tabbingIconIndex == filteredPrompts.length &&
|
||||
"bg-background-dark/75"
|
||||
} px-3 flex gap-x-1 py-2 w-full rounded-lg items-center hover:bg-background-dark/90 cursor-pointer`}
|
||||
"bg-background-dark/75 dark:bg-neutral-800/75"
|
||||
} px-3 flex gap-x-1 py-2 w-full rounded-lg items-center hover:bg-background-dark/90 dark:hover:bg-neutral-800/90 cursor-pointer`}
|
||||
href="/chat/input-prompts"
|
||||
>
|
||||
<FiPlus size={17} />
|
||||
|
@ -18,6 +18,7 @@ export default async function Layout({
|
||||
);
|
||||
|
||||
if ("redirect" in data) {
|
||||
console.log("redirect", data.redirect);
|
||||
redirect(data.redirect);
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,10 @@ export const MemoizedAnchor = memo(
|
||||
if (match) {
|
||||
const isUserFileCitation = userFiles?.length && userFiles.length > 0;
|
||||
if (isUserFileCitation) {
|
||||
const index = parseInt(match[2], 10) - 1;
|
||||
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>;
|
||||
|
@ -342,12 +342,7 @@ export const AIMessage = ({
|
||||
}
|
||||
const processed = preprocessLaTeX(content);
|
||||
|
||||
// Escape $ that are preceded by a space and followed by a non-$ character
|
||||
const escapedDollarSigns = processed.replace(/([\s])\$([^\$])/g, "$1\\$$2");
|
||||
|
||||
return (
|
||||
escapedDollarSigns + (!isComplete && !toolCallGenerating ? " [*]() " : "")
|
||||
);
|
||||
return processed + (!isComplete && !toolCallGenerating ? " [*]() " : "");
|
||||
};
|
||||
|
||||
const finalContentProcessed = processContent(finalContent as string);
|
||||
|
@ -5,7 +5,7 @@ import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces"
|
||||
|
||||
import { destructureValue, structureValue } from "@/lib/llm/utils";
|
||||
import { setUserDefaultModel } from "@/lib/users/UserSettings";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
@ -207,6 +207,8 @@ export function UserSettingsModal({
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
const pathname = usePathname();
|
||||
|
||||
const showPasswordSection = user?.password_configured;
|
||||
|
||||
const handleDeleteAllChats = async () => {
|
||||
@ -219,7 +221,9 @@ export function UserSettingsModal({
|
||||
type: "success",
|
||||
});
|
||||
refreshChatSessions();
|
||||
if (pathname.includes("/chat")) {
|
||||
router.push("/chat");
|
||||
}
|
||||
} else {
|
||||
throw new Error("Failed to delete all chat sessions");
|
||||
}
|
||||
@ -382,7 +386,7 @@ export function UserSettingsModal({
|
||||
<div className="pt-4 border-t border-border">
|
||||
{!showDeleteConfirmation ? (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-neutral-600 ">
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
This will permanently delete all your chat sessions and
|
||||
cannot be undone.
|
||||
</p>
|
||||
@ -397,7 +401,7 @@ export function UserSettingsModal({
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-neutral-600 ">
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Are you sure you want to delete all your chat sessions?
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
|
@ -160,6 +160,7 @@ export const DocumentsProvider: React.FC<DocumentsProviderProps> = ({
|
||||
|
||||
const refreshFolders = async () => {
|
||||
try {
|
||||
console.log("fetching folders");
|
||||
const data = await documentsService.fetchFolders();
|
||||
setFolders(data);
|
||||
} catch (error) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo, useState, useTransition } from "react";
|
||||
import React, { useEffect, useMemo, useState, useTransition } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
Plus,
|
||||
@ -68,11 +68,23 @@ export default function MyDocuments() {
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>(
|
||||
SortDirection.Descending
|
||||
);
|
||||
const pageLimit = 10;
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const router = useRouter();
|
||||
const { popup, setPopup } = usePopup();
|
||||
const [isCreateFolderOpen, setIsCreateFolderOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const createFolder = searchParams.get("createFolder");
|
||||
if (createFolder) {
|
||||
setIsCreateFolderOpen(true);
|
||||
const newSearchParams = new URLSearchParams(searchParams);
|
||||
newSearchParams.delete("createFolder");
|
||||
router.replace(`?${newSearchParams.toString()}`);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [hoveredColumn, setHoveredColumn] = useState<SortType | null>(null);
|
||||
|
||||
@ -118,118 +130,22 @@ export default function MyDocuments() {
|
||||
};
|
||||
|
||||
const handleDeleteItem = async (itemId: number, isFolder: boolean) => {
|
||||
if (!isFolder) {
|
||||
// For files, keep the old confirmation
|
||||
const confirmDelete = window.confirm(
|
||||
`Are you sure you want to delete this file?`
|
||||
);
|
||||
|
||||
if (confirmDelete) {
|
||||
try {
|
||||
await deleteItem(itemId, isFolder);
|
||||
setPopup({
|
||||
message: `File deleted successfully`,
|
||||
message: isFolder
|
||||
? `Folder deleted successfully`
|
||||
: `File deleted successfully`,
|
||||
type: "success",
|
||||
});
|
||||
await refreshFolders();
|
||||
} catch (error) {
|
||||
console.error("Error deleting item:", error);
|
||||
setPopup({
|
||||
message: `Failed to delete file`,
|
||||
message: `Failed to delete ${isFolder ? "folder" : "file"}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If it's a folder, the SharedFolderItem component will handle it
|
||||
};
|
||||
|
||||
const handleMoveItem = async (
|
||||
itemId: number,
|
||||
currentFolderId: number | null,
|
||||
isFolder: boolean
|
||||
) => {
|
||||
const availableFolders = folders
|
||||
.filter((folder) => folder.id !== itemId)
|
||||
.map((folder) => `${folder.id}: ${folder.name}`)
|
||||
.join("\n");
|
||||
|
||||
const promptMessage = `Enter the ID of the destination folder:\n\nAvailable folders:\n${availableFolders}\n\nEnter 0 to move to the root folder.`;
|
||||
const destinationFolderId = prompt(promptMessage);
|
||||
|
||||
if (destinationFolderId !== null) {
|
||||
const newFolderId = parseInt(destinationFolderId, 10);
|
||||
if (isNaN(newFolderId)) {
|
||||
setPopup({
|
||||
message: "Invalid folder ID",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await moveItem(
|
||||
itemId,
|
||||
newFolderId === 0 ? null : newFolderId,
|
||||
isFolder
|
||||
);
|
||||
setPopup({
|
||||
message: `${
|
||||
isFolder ? "Knowledge Group" : "File"
|
||||
} moved successfully`,
|
||||
type: "success",
|
||||
});
|
||||
await refreshFolders();
|
||||
} catch (error) {
|
||||
console.error("Error moving item:", error);
|
||||
setPopup({
|
||||
message: "Failed to move item",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadItem = async (documentId: string) => {
|
||||
try {
|
||||
await downloadItem(documentId);
|
||||
} catch (error) {
|
||||
console.error("Error downloading file:", error);
|
||||
setPopup({
|
||||
message: "Failed to download file",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onRenameItem = async (
|
||||
itemId: number,
|
||||
currentName: string,
|
||||
isFolder: boolean
|
||||
) => {
|
||||
const newName = prompt(
|
||||
`Enter new name for ${isFolder ? "Knowledge Group" : "File"}:`,
|
||||
currentName
|
||||
);
|
||||
if (newName && newName !== currentName) {
|
||||
try {
|
||||
await renameItem(itemId, newName, isFolder);
|
||||
setPopup({
|
||||
message: `${
|
||||
isFolder ? "Knowledge Group" : "File"
|
||||
} renamed successfully`,
|
||||
type: "success",
|
||||
});
|
||||
await refreshFolders();
|
||||
} catch (error) {
|
||||
console.error("Error renaming item:", error);
|
||||
setPopup({
|
||||
message: `Failed to rename ${isFolder ? "Knowledge Group" : "File"}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const filteredFolders = useMemo(() => {
|
||||
@ -438,11 +354,7 @@ export default function MyDocuments() {
|
||||
onClick={handleFolderClick}
|
||||
description={folder.description}
|
||||
lastUpdated={folder.created_at}
|
||||
onRename={() => onRenameItem(folder.id, folder.name, true)}
|
||||
onDelete={() => handleDeleteItem(folder.id, true)}
|
||||
onMove={() =>
|
||||
handleMoveItem(folder.id, currentFolder, true)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -588,29 +588,6 @@ export default function UserFolderContent({ folderId }: { folderId: number }) {
|
||||
|
||||
{/* Invalid file message */}
|
||||
|
||||
{/* Add a visual overlay when dragging files */}
|
||||
{isDraggingOver && (
|
||||
<div className="fixed inset-0 bg-neutral-950/10 backdrop-blur-sm z-50 pointer-events-none flex items-center justify-center transition-all duration-200 ease-in-out">
|
||||
<div className="bg-white dark:bg-neutral-900 rounded-lg p-8 shadow-lg text-center border border-neutral-200 dark:border-neutral-800 max-w-md mx-auto">
|
||||
<div className="bg-neutral-100 dark:bg-neutral-800 p-4 rounded-full w-20 h-20 mx-auto mb-5 flex items-center justify-center">
|
||||
<Upload
|
||||
className="w-10 h-10 text-neutral-600 dark:text-neutral-300"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-xl font-medium mb-2 text-neutral-900 dark:text-neutral-50">
|
||||
Drop files to upload
|
||||
</h3>
|
||||
<p className="text-neutral-500 dark:text-neutral-400 text-sm">
|
||||
Files will be uploaded to{" "}
|
||||
<span className="font-medium text-neutral-900 dark:text-neutral-200">
|
||||
{folderDetails?.name || "this folder"}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DeleteEntityModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
onClose={() => setIsDeleteModalOpen(false)}
|
||||
@ -634,9 +611,9 @@ export default function UserFolderContent({ folderId }: { folderId: number }) {
|
||||
|
||||
<div className="flex -mt-[1px] flex-col w-full">
|
||||
<div className="flex items-center mb-3">
|
||||
<nav className="flex text-lg gap-x-1 items-center">
|
||||
<nav className="flex text-base md:text-lg gap-x-1 items-center">
|
||||
<span
|
||||
className="font-medium leading-tight tracking-tight text-lg text-neutral-800 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-100 cursor-pointer flex items-center text-base"
|
||||
className="font-medium leading-tight tracking-tight text-neutral-800 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-100 cursor-pointer flex items-center"
|
||||
onClick={handleBack}
|
||||
>
|
||||
My Documents
|
||||
|
@ -255,7 +255,7 @@ export const FileListItem: React.FC<FileListItemProps> = ({
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="group-hover:visible invisible h-8 w-8 p-0"
|
||||
className="group-hover:visible mobile:visible invisible h-8 w-8 p-0"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
|
@ -104,7 +104,7 @@ const DraggableItem: React.FC<{
|
||||
<div className="w-6 flex items-center justify-center shrink-0">
|
||||
<div
|
||||
className={`${
|
||||
isSelected ? "" : "opacity-0 group-hover:opacity-100"
|
||||
isSelected ? "" : "desktop:opacity-0 group-hover:opacity-100"
|
||||
} transition-opacity duration-150`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@ -199,7 +199,7 @@ const FilePickerFolderItem: React.FC<{
|
||||
className={`transition-opacity duration-150 ${
|
||||
isSelected || allFilesSelected
|
||||
? "opacity-100"
|
||||
: "opacity-0 group-hover:opacity-100"
|
||||
: "desktop:opacity-0 group-hover:opacity-100"
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -276,7 +276,10 @@ const FilePickerFolderItem: React.FC<{
|
||||
export interface FilePickerModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: () => void;
|
||||
onSave: (
|
||||
selectedFiles: FileResponse[],
|
||||
selectedFolders: FolderResponse[]
|
||||
) => void;
|
||||
buttonContent: string;
|
||||
setPresentingDocument: (onyxDocument: MinimalOnyxDocument) => void;
|
||||
}
|
||||
@ -323,8 +326,6 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
createFileFromLink,
|
||||
} = useDocumentsContext();
|
||||
|
||||
const router = useRouter();
|
||||
const [linkUrl, setLinkUrl] = useState("");
|
||||
const [isCreatingFileFromLink, setIsCreatingFileFromLink] = useState(false);
|
||||
const [isUploadingFile, setIsUploadingFile] = useState(false);
|
||||
|
||||
@ -395,12 +396,6 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
}
|
||||
}, [isOpen, selectedFiles, selectedFolders]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
refreshFolders();
|
||||
}
|
||||
}, [isOpen, refreshFolders]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentFolder) {
|
||||
if (currentFolder === -1) {
|
||||
@ -1087,7 +1082,7 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
}
|
||||
>
|
||||
<div className="h-[calc(70vh-5rem)] flex overflow-visible flex-col">
|
||||
<div className="grid overflow-x-visible h-full overflow-y-hidden flex-1 w-full divide-x divide-neutral-200 dark:divide-neutral-700 grid-cols-2">
|
||||
<div className="grid overflow-x-visible h-full overflow-y-hidden flex-1 w-full divide-x divide-neutral-200 dark:divide-neutral-700 desktop:grid-cols-2">
|
||||
<div className="w-full h-full pb-4 overflow-hidden ">
|
||||
<div className="px-6 sticky flex flex-col gap-y-2 z-[1000] top-0 mb-2 flex gap-x-2 w-full pr-4">
|
||||
<div className="w-full relative">
|
||||
@ -1251,16 +1246,16 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
) : folders.length > 0 ? (
|
||||
<div className="flex-grow overflow-y-auto px-4">
|
||||
<p className="text-text-subtle dark:text-neutral-400">
|
||||
No groups found
|
||||
No folders found
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-grow flex-col overflow-y-auto px-4 flex items-start justify-start gap-y-2">
|
||||
<p className="text-sm text-muted-foreground dark:text-neutral-400">
|
||||
No groups found
|
||||
No folders found
|
||||
</p>
|
||||
<a
|
||||
href="/chat/my-documents"
|
||||
href="/chat/my-documents?createFolder=true"
|
||||
className="inline-flex items-center text-sm justify-center text-neutral-600 dark:text-neutral-400 hover:underline"
|
||||
>
|
||||
<FolderIcon className="mr-2 h-4 w-4" />
|
||||
@ -1270,14 +1265,20 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`w-full h-full flex flex-col ${
|
||||
className={`mobile:hidden overflow-y-auto w-full h-full flex flex-col ${
|
||||
isHoveringRight ? "bg-neutral-100 dark:bg-neutral-800/30" : ""
|
||||
}`}
|
||||
onDragEnter={() => setIsHoveringRight(true)}
|
||||
onDragLeave={() => setIsHoveringRight(false)}
|
||||
>
|
||||
<div className="px-5 pb-5 flex-1 flex flex-col">
|
||||
<div className="shrink default-scrollbar flex h-full overflow-y-auto mb-3">
|
||||
<div className="px-5 h-full flex flex-col">
|
||||
{/* Top section: scrollable, takes remaining space */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-semibold text-neutral-800 dark:text-neutral-100">
|
||||
Selected Items
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||
<SelectedItemsList
|
||||
uploadingFiles={uploadingFiles}
|
||||
setPresentingDocument={setPresentingDocument}
|
||||
@ -1288,16 +1289,14 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-3">
|
||||
<div className="flex flex-col space-y-2">
|
||||
{/* Bottom section: fixed height, doesn't flex */}
|
||||
<div className="flex-none py-2">
|
||||
<FileUploadSection
|
||||
disabled={isUploadingFile || isCreatingFileFromLink}
|
||||
onUpload={(files: File[]) => {
|
||||
setIsUploadingFile(true);
|
||||
setUploadStartTime(Date.now()); // Record start time
|
||||
|
||||
// Add files to uploading files state
|
||||
|
||||
// Start the refresh interval to simulate progress
|
||||
startRefreshInterval();
|
||||
|
||||
@ -1326,8 +1325,10 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
startRefreshInterval();
|
||||
|
||||
try {
|
||||
const response: FileResponse[] =
|
||||
await createFileFromLink(url, -1);
|
||||
const response: FileResponse[] = await createFileFromLink(
|
||||
url,
|
||||
-1
|
||||
);
|
||||
|
||||
if (response.length > 0) {
|
||||
// Extract domain from URL to help with detection
|
||||
@ -1354,7 +1355,6 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-5 pt-4 border-t border-neutral-200 dark:border-neutral-700">
|
||||
<div className="flex flex-col items-center justify-center py-2 space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
@ -1375,7 +1375,10 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Button
|
||||
onClick={onSave}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onSave(selectedItems.files, selectedItems.folders)
|
||||
}
|
||||
className="px-8 py-2 w-48"
|
||||
disabled={
|
||||
isUploadingFile ||
|
||||
|
@ -26,7 +26,8 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
|
||||
onRemoveFolder,
|
||||
setPresentingDocument,
|
||||
}) => {
|
||||
const hasItems = folders.length > 0 || files.length > 0;
|
||||
const hasItems =
|
||||
folders.length > 0 || files.length > 0 || uploadingFiles.length > 0;
|
||||
const openFile = (file: FileResponse) => {
|
||||
if (file.link_url) {
|
||||
window.open(file.link_url, "_blank");
|
||||
@ -40,14 +41,7 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
|
||||
|
||||
return (
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-semibold text-neutral-800 dark:text-neutral-100">
|
||||
Selected Items
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="h-[200px] flex-grow pr-1">
|
||||
<div className="space-y-2.5">
|
||||
<div className="space-y-2.5 pb-2">
|
||||
{folders.length > 0 && (
|
||||
<div className="space-y-2.5">
|
||||
{folders.map((folder: FolderResponse) => (
|
||||
@ -207,7 +201,6 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -26,9 +26,7 @@ interface SharedFolderItemProps {
|
||||
onClick: (folderId: number) => void;
|
||||
description?: string;
|
||||
lastUpdated?: string;
|
||||
onRename: () => void;
|
||||
onDelete: () => void;
|
||||
onMove: () => void;
|
||||
}
|
||||
|
||||
export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({
|
||||
@ -36,9 +34,7 @@ export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({
|
||||
onClick,
|
||||
description,
|
||||
lastUpdated,
|
||||
onRename,
|
||||
onDelete,
|
||||
onMove,
|
||||
}) => {
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
||||
@ -99,7 +95,7 @@ export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={`group-hover:visible invisible h-8 w-8 p-0 ${
|
||||
className={`group-hover:visible mobile:visible invisible h-8 w-8 p-0 ${
|
||||
folder.id === -1 ? "!invisible pointer-events-none" : ""
|
||||
}`}
|
||||
>
|
||||
@ -108,14 +104,6 @@ export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="!p-0 w-40">
|
||||
<div className="space-y-0">
|
||||
{/* <Button variant="menu" onClick={onMove}>
|
||||
<FiArrowDown className="h-4 w-4" />
|
||||
Move
|
||||
</Button>
|
||||
<Button variant="menu" onClick={onRename}>
|
||||
<FiEdit className="h-4 w-4" />
|
||||
Rename
|
||||
</Button> */}
|
||||
<Button variant="menu" onClick={handleDeleteClick}>
|
||||
<FiTrash className="h-4 w-4" />
|
||||
Delete
|
||||
|
@ -23,21 +23,21 @@ import { Modal } from "@/components/Modal";
|
||||
import FunctionalHeader from "@/components/chat/Header";
|
||||
import FixedLogo from "@/components/logo/FixedLogo";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
function BackToOnyxButton({
|
||||
documentSidebarVisible,
|
||||
}: {
|
||||
documentSidebarVisible: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const enterpriseSettings = useContext(SettingsContext)?.enterpriseSettings;
|
||||
|
||||
return (
|
||||
<div className="absolute bottom-0 bg-background w-full flex border-t border-border py-4">
|
||||
<div className="mx-auto">
|
||||
<Button onClick={() => router.push("/chat")}>
|
||||
<Link href="/chat">
|
||||
Back to {enterpriseSettings?.application_name || "Onyx Chat"}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<div
|
||||
style={{ transition: "width 0.30s ease-out" }}
|
||||
|
@ -124,10 +124,16 @@ const StandardAnswersTableRow = ({
|
||||
? `\`${standardAnswer.keyword}\``
|
||||
: standardAnswer.keyword}
|
||||
</ReactMarkdown>,
|
||||
<CustomCheckbox
|
||||
<div
|
||||
key={`match_regex-${standardAnswer.id}`}
|
||||
checked={standardAnswer.match_regex}
|
||||
/>,
|
||||
className="flex items-center"
|
||||
>
|
||||
{standardAnswer.match_regex ? (
|
||||
<span className="text-green-500 font-medium">Yes</span>
|
||||
) : (
|
||||
<span className="text-gray-500">No</span>
|
||||
)}
|
||||
</div>,
|
||||
<ReactMarkdown
|
||||
key={`answer-${standardAnswer.id}`}
|
||||
className="prose dark:prose-invert"
|
||||
@ -290,8 +296,8 @@ const StandardAnswersTable = ({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto">
|
||||
<Table className="w-full flex items-stretch">
|
||||
<div className="flex flex-col w-full mx-auto">
|
||||
<Table className="w-full">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{columns.map((column) => (
|
||||
@ -314,11 +320,13 @@ const StandardAnswersTable = ({
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div>
|
||||
{paginatedStandardAnswers.length === 0 && (
|
||||
<div className="flex justify-center">
|
||||
<Text>No matching standard answers found...</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{paginatedStandardAnswers.length > 0 && (
|
||||
<>
|
||||
<div className="mt-4">
|
||||
|
@ -144,7 +144,7 @@ export function WhitelabelingForm() {
|
||||
placeholder="Custom name which will replace 'Onyx'"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Label className="mt-4">Custom Logo</Label>
|
||||
|
||||
{values.use_custom_logo ? (
|
||||
@ -189,6 +189,7 @@ export function WhitelabelingForm() {
|
||||
selectedFile={selectedLogo}
|
||||
setSelectedFile={setSelectedLogo}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
|
@ -674,3 +674,7 @@ ul > li > p {
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
@ -115,17 +115,17 @@ export const ConnectorMultiSelect = ({
|
||||
<div className="flex flex-col w-full space-y-2 mb-4">
|
||||
{label && <Label className="text-base font-medium">{label}</Label>}
|
||||
|
||||
<p className="text-xs text-neutral-500 ">
|
||||
<p className="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
All documents indexed by the selected connectors will be part of this
|
||||
document set.
|
||||
</p>
|
||||
<div className="relative">
|
||||
<div
|
||||
className={`flex items-center border border-input rounded-md border border-neutral-200 ${
|
||||
allConnectorsSelected ? "bg-neutral-50" : ""
|
||||
} focus-within:ring-1 focus-within:ring-ring focus-within:border-neutral-400 transition-colors`}
|
||||
className={`flex items-center border border-input rounded-md border-neutral-200 dark:border-neutral-700 ${
|
||||
allConnectorsSelected ? "bg-neutral-50 dark:bg-neutral-800" : ""
|
||||
} focus-within:ring-1 focus-within:ring-ring focus-within:border-neutral-400 dark:focus-within:border-neutral-500 transition-colors`}
|
||||
>
|
||||
<Search className="absolute left-3 h-4 w-4 text-neutral-500" />
|
||||
<Search className="absolute left-3 h-4 w-4 text-neutral-500 dark:text-neutral-400" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
@ -141,8 +141,10 @@ export const ConnectorMultiSelect = ({
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={effectivePlaceholder}
|
||||
className={`h-9 w-full pl-9 pr-10 py-2 bg-transparent text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50 ${
|
||||
allConnectorsSelected ? "text-neutral-500" : ""
|
||||
className={`h-9 w-full pl-9 pr-10 py-2 bg-transparent dark:bg-transparent text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50 ${
|
||||
allConnectorsSelected
|
||||
? "text-neutral-500 dark:text-neutral-400"
|
||||
: ""
|
||||
}`}
|
||||
disabled={isInputDisabled}
|
||||
/>
|
||||
@ -151,10 +153,10 @@ export const ConnectorMultiSelect = ({
|
||||
{open && !allConnectorsSelected && (
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
className="absolute z-50 w-full mt-1 rounded-md border border-neutral-200 bg-white shadow-md default-scrollbar max-h-[300px] overflow-auto"
|
||||
className="absolute z-50 w-full mt-1 rounded-md border border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-900 shadow-md default-scrollbar max-h-[300px] overflow-auto"
|
||||
>
|
||||
{filteredUnselectedConnectors.length === 0 ? (
|
||||
<div className="py-4 text-center text-xs text-neutral-500">
|
||||
<div className="py-4 text-center text-xs text-neutral-500 dark:text-neutral-400">
|
||||
{searchQuery
|
||||
? "No matching connectors found"
|
||||
: "No more connectors available"}
|
||||
@ -164,7 +166,7 @@ export const ConnectorMultiSelect = ({
|
||||
{filteredUnselectedConnectors.map((connector) => (
|
||||
<div
|
||||
key={connector.cc_pair_id}
|
||||
className="flex items-center justify-between py-2 px-3 cursor-pointer hover:bg-neutral-50 text-xs"
|
||||
className="flex items-center justify-between py-2 px-3 cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-800 text-xs"
|
||||
onClick={() => selectConnector(connector.cc_pair_id)}
|
||||
>
|
||||
<div className="flex items-center truncate mr-2">
|
||||
@ -185,12 +187,12 @@ export const ConnectorMultiSelect = ({
|
||||
</div>
|
||||
|
||||
{selectedConnectors.length > 0 ? (
|
||||
<div className="mt-3 ">
|
||||
<div className="mt-3">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{selectedConnectors.map((connector) => (
|
||||
<div
|
||||
key={connector.cc_pair_id}
|
||||
className="flex items-center bg-white rounded-md border border-neutral-300 transition-all px-2 py-1 max-w-full group text-xs"
|
||||
className="flex items-center bg-white dark:bg-neutral-800 rounded-md border border-neutral-300 dark:border-neutral-700 transition-all px-2 py-1 max-w-full group text-xs"
|
||||
>
|
||||
<div className="flex items-center overflow-hidden">
|
||||
<div className="flex-shrink-0 text-xs">
|
||||
@ -204,7 +206,7 @@ export const ConnectorMultiSelect = ({
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="ml-1 flex-shrink-0 rounded-full w-4 h-4 flex items-center justify-center bg-neutral-100 text-neutral-500 hover:bg-neutral-200 hover:text-neutral-700 transition-colors group-hover:bg-neutral-200"
|
||||
className="ml-1 flex-shrink-0 rounded-full w-4 h-4 flex items-center justify-center bg-neutral-100 dark:bg-neutral-700 text-neutral-500 dark:text-neutral-400 hover:bg-neutral-200 dark:hover:bg-neutral-600 hover:text-neutral-700 dark:hover:text-neutral-300 transition-colors group-hover:bg-neutral-200 dark:group-hover:bg-neutral-600"
|
||||
onClick={() => removeConnector(connector.cc_pair_id)}
|
||||
aria-label="Remove connector"
|
||||
>
|
||||
@ -215,7 +217,7 @@ export const ConnectorMultiSelect = ({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 p-3 border border-dashed border-neutral-300 rounded-md bg-neutral-50 text-neutral-500 text-xs">
|
||||
<div className="mt-3 p-3 border border-dashed border-neutral-300 dark:border-neutral-700 rounded-md bg-neutral-50 dark:bg-neutral-800 text-neutral-500 dark:text-neutral-400 text-xs">
|
||||
No connectors selected. Search and select connectors above.
|
||||
</div>
|
||||
)}
|
||||
@ -224,7 +226,7 @@ export const ConnectorMultiSelect = ({
|
||||
<ErrorMessage
|
||||
name={name}
|
||||
component="div"
|
||||
className="text-red-500 text-xs mt-1"
|
||||
className="text-red-500 dark:text-red-400 text-xs mt-1"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -92,8 +92,9 @@ export function Modal({
|
||||
${className || ""}
|
||||
flex
|
||||
flex-col
|
||||
|
||||
${heightOverride ? `h-${heightOverride}` : "max-h-[90vh]"}
|
||||
${hideOverflow ? "overflow-hidden" : "overflow-auto"}
|
||||
${hideOverflow ? "overflow-hidden" : "overflow-visible"}
|
||||
`}
|
||||
>
|
||||
{onOutsideClick && !hideCloseButton && (
|
||||
|
@ -69,7 +69,7 @@ const MultiSelectDropdown = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-4 mb-4">
|
||||
<div className="flex flex-col text-white space-y-4 mb-4">
|
||||
<Label>{label}</Label>
|
||||
{creatable ? (
|
||||
<CreatableSelect
|
||||
|
@ -27,9 +27,9 @@ export function TokenDisplay({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center space-x-3 bg-neutral-100 dark:bg-neutral-800 rounded-full px-4 py-1.5">
|
||||
<div className="relative w-36 h-2 bg-neutral-200 dark:bg-neutral-700 rounded-full overflow-hidden">
|
||||
<div className="hidden sm:block relative w-24 h-2 bg-neutral-200 dark:bg-neutral-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`absolute top-0 left-0 h-full rounded-full ${
|
||||
className={` absolute top-0 left-0 h-full rounded-full ${
|
||||
tokenPercentage >= 100
|
||||
? "bg-yellow-500 dark:bg-yellow-600"
|
||||
: "bg-green-500 dark:bg-green-600"
|
||||
|
@ -379,6 +379,8 @@ export function ClientLayout({
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!enableCloud
|
||||
? [
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
@ -386,11 +388,15 @@ export function ClientLayout({
|
||||
className="text-text-700"
|
||||
size={18}
|
||||
/>
|
||||
<div className="ml-1">Custom Analytics</div>
|
||||
<div className="ml-1">
|
||||
Custom Analytics
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/performance/custom-analytics",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
]
|
||||
@ -453,7 +459,7 @@ export function ClientLayout({
|
||||
<div className="fixed left-0 gap-x-4 px-4 top-4 h-8 px-0 mb-auto w-full items-start flex justify-end">
|
||||
<UserDropdown toggleUserSettings={toggleUserSettings} />
|
||||
</div>
|
||||
<div className="pt-20 flex w-full overflow-y-auto overflow-x-hidden h-full px-4 md:px-12">
|
||||
<div className="pt-20 pb-4 flex w-full overflow-y-auto overflow-x-hidden h-full px-4 md:px-12">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -201,7 +201,6 @@ export function TextFormField({
|
||||
maxWidth,
|
||||
removeLabel,
|
||||
min,
|
||||
includeForgotPassword,
|
||||
onChange,
|
||||
width,
|
||||
vertical,
|
||||
@ -229,7 +228,6 @@ export function TextFormField({
|
||||
explanationLink?: string;
|
||||
small?: boolean;
|
||||
min?: number;
|
||||
includeForgotPassword?: boolean;
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
width?: string;
|
||||
vertical?: boolean;
|
||||
@ -339,14 +337,6 @@ export function TextFormField({
|
||||
placeholder={placeholder}
|
||||
autoComplete={autoCompleteDisabled ? "off" : undefined}
|
||||
/>
|
||||
{includeForgotPassword && (
|
||||
<Link
|
||||
href="/auth/forgot-password"
|
||||
className="absolute right-3 top-1/2 mt-[3px] transform -translate-y-1/2 text-xs text-blue-500 cursor-pointer"
|
||||
>
|
||||
Forgot password?
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{explanationText && (
|
||||
|
@ -16,7 +16,6 @@ interface TextViewProps {
|
||||
presentingDocument: MinimalOnyxDocument;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function TextView({
|
||||
presentingDocument,
|
||||
onClose,
|
||||
@ -27,6 +26,13 @@ export default function TextView({
|
||||
const [fileName, setFileName] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [fileType, setFileType] = useState("application/octet-stream");
|
||||
const [renderCount, setRenderCount] = useState(0);
|
||||
|
||||
// Log render count on each render
|
||||
useEffect(() => {
|
||||
setRenderCount((prevCount) => prevCount + 1);
|
||||
console.log(`TextView component rendered ${renderCount + 1} times`);
|
||||
}, []);
|
||||
|
||||
// Detect if a given MIME type is one of the recognized markdown formats
|
||||
const isMarkdownFormat = (mimeType: string): boolean => {
|
||||
@ -63,6 +69,7 @@ export default function TextView({
|
||||
};
|
||||
|
||||
const fetchFile = useCallback(async () => {
|
||||
console.log("fetching file");
|
||||
setIsLoading(true);
|
||||
const fileId =
|
||||
presentingDocument.document_id.split("__")[1] ||
|
||||
@ -107,13 +114,14 @@ export default function TextView({
|
||||
// Keep the slight delay for a smoother loading experience
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
console.log("finished loading");
|
||||
}, 1000);
|
||||
}
|
||||
}, [presentingDocument]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchFile();
|
||||
}, [fetchFile]);
|
||||
}, []);
|
||||
|
||||
const handleDownload = () => {
|
||||
const link = document.createElement("a");
|
||||
|
@ -327,7 +327,7 @@ export function HorizontalFilters({
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="b">
|
||||
<div className="flex gap-x-3">
|
||||
<div className="w-52">
|
||||
<DateRangeSelector value={timeRange} onValueChange={setTimeRange} />
|
||||
@ -387,7 +387,7 @@ export function HorizontalFilters({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex pb-4 mt-2 h-12">
|
||||
<div className="flex mt-2">
|
||||
<div className="flex flex-wrap gap-x-2">
|
||||
{timeRange && timeRange.selectValue && (
|
||||
<SelectedBubble onClick={() => setTimeRange(null)}>
|
||||
|
@ -1303,7 +1303,7 @@ export const ProductboardIcon = ({
|
||||
size = 16,
|
||||
className = defaultTailwindCSS,
|
||||
}: IconProps) => (
|
||||
<LogoIcon size={size} className={className} src="/Productboard.webp" />
|
||||
<LogoIcon size={size} className={className} src="/Productboard.png" />
|
||||
);
|
||||
|
||||
export const AzureIcon = ({
|
||||
|
@ -45,7 +45,7 @@ export default function CreateEntityModal({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogContent className="max-w-[95%] sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
@ -437,19 +437,20 @@ export function CompactDocumentCard({
|
||||
url?: string;
|
||||
updatePresentingDocument: (document: OnyxDocument) => void;
|
||||
}) {
|
||||
console.log("document", document);
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
openDocument(document, updatePresentingDocument);
|
||||
}}
|
||||
className="max-w-[200px] gap-y-0 cursor-pointer pb-0 pt-0 mt-0 flex gap-y-0 flex-col content-start items-start gap-0 "
|
||||
className="max-w-[250px] gap-y-1 cursor-pointer pb-0 pt-0 mt-0 flex gap-y-0 flex-col content-start items-start gap-0 "
|
||||
>
|
||||
<div className="text-sm !pb-0 !mb-0 font-semibold flex items-center gap-x-1 text-text-900 pt-0 mt-0 truncate w-full">
|
||||
<div className="text-sm flex gap-x-2 !pb-0 !mb-0 font-semibold flex items-center gap-x-1 text-text-900 pt-0 mt-0 w-full">
|
||||
{icon}
|
||||
<p className="gap-0 p-0 m-0 line-clamp-2">
|
||||
{(document.semantic_identifier || document.document_id).slice(0, 40)}
|
||||
{(document.semantic_identifier || document.document_id).length > 40 &&
|
||||
"..."}
|
||||
</p>
|
||||
</div>
|
||||
{document.blurb && (
|
||||
<div className="text-xs mb-0 text-neutral-600 dark:text-neutral-300 line-clamp-2">
|
||||
@ -479,7 +480,7 @@ export function CompactQuestionCard({
|
||||
return (
|
||||
<div
|
||||
onClick={() => openQuestion(question)}
|
||||
className="max-w-[250px] gap-y-0 cursor-pointer pb-0 pt-0 mt-0 flex gap-y-0 flex-col content-start items-start gap-0"
|
||||
className="max-w-[350px] gap-y-1 cursor-pointer pb-0 pt-0 mt-0 flex gap-y-0 flex-col content-start items-start gap-0"
|
||||
>
|
||||
<div className="text-sm !pb-0 !mb-0 font-semibold flex items-center gap-x-1 text-text-900 pt-0 mt-0 truncate w-full">
|
||||
Question
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
} from "@/components/ui/tooltip";
|
||||
import { openDocument } from "@/lib/search/utils";
|
||||
import { SubQuestionDetail } from "@/app/chat/interfaces";
|
||||
import { getFileIconFromFileNameAndLink } from "@/lib/assistantIconUtils";
|
||||
|
||||
export interface DocumentCardProps {
|
||||
document: LoadedOnyxDocument;
|
||||
@ -39,6 +40,13 @@ export function Citation({
|
||||
if (!document_info && !question_info) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
const icon = document_info?.document
|
||||
? getFileIconFromFileNameAndLink(
|
||||
document_info.document.semantic_identifier || "",
|
||||
document_info.document.link || ""
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
@ -72,7 +80,7 @@ export function Citation({
|
||||
<CompactDocumentCard
|
||||
updatePresentingDocument={document_info.updatePresentingDocument}
|
||||
url={document_info.url}
|
||||
icon={document_info.icon}
|
||||
icon={icon}
|
||||
document={document_info.document}
|
||||
/>
|
||||
) : (
|
||||
|
@ -112,7 +112,24 @@ export async function fetchChatData(searchParams: {
|
||||
? `${fullUrl}?${searchParamsString}`
|
||||
: fullUrl;
|
||||
|
||||
if (!NEXT_PUBLIC_ENABLE_CHROME_EXTENSION) {
|
||||
// Check the referrer to prevent redirect loops
|
||||
const referrer = headersList.get("referer") || "";
|
||||
const isComingFromLogin = referrer.includes("/auth/login");
|
||||
|
||||
// Also check for the from=login query parameter
|
||||
const isRedirectedFromLogin = searchParams["from"] === "login";
|
||||
|
||||
console.log(
|
||||
`Auth check: authDisabled=${authDisabled}, user=${!!user}, referrer=${referrer}, fromLogin=${isRedirectedFromLogin}`
|
||||
);
|
||||
|
||||
// Only redirect if we're not already coming from the login page
|
||||
if (
|
||||
!NEXT_PUBLIC_ENABLE_CHROME_EXTENSION &&
|
||||
!isComingFromLogin &&
|
||||
!isRedirectedFromLogin
|
||||
) {
|
||||
console.log("Redirecting to login from chat page");
|
||||
return {
|
||||
redirect: `/auth/login?next=${encodeURIComponent(redirectUrl)}`,
|
||||
};
|
||||
|
@ -77,7 +77,8 @@ export const SERVER_SIDE_ONLY__CLOUD_ENABLED =
|
||||
process.env.NEXT_PUBLIC_CLOUD_ENABLED?.toLowerCase() === "true";
|
||||
|
||||
export const NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED =
|
||||
process.env.NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED?.toLowerCase() === "true";
|
||||
process.env.NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED?.toLowerCase() === "true" &&
|
||||
!NEXT_PUBLIC_CLOUD_ENABLED;
|
||||
|
||||
export const NEXT_PUBLIC_TEST_ENV =
|
||||
process.env.NEXT_PUBLIC_TEST_ENV?.toLowerCase() === "true";
|
||||
|
@ -130,12 +130,10 @@ export async function renameItem(
|
||||
}
|
||||
|
||||
export async function downloadItem(documentId: string): Promise<Blob> {
|
||||
const response = await fetch(
|
||||
`/api/chat/file/${encodeURIComponent(documentId)}`,
|
||||
{
|
||||
const fileId = documentId.split("__")[1] || documentId;
|
||||
const response = await fetch(`/api/chat/file/${encodeURIComponent(fileId)}`, {
|
||||
method: "GET",
|
||||
}
|
||||
);
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch file");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user