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:
pablonyx 2025-04-24 22:20:33 -07:00 committed by GitHub
parent 115cfb6ae9
commit df67ca18d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 1591 additions and 1568 deletions

View File

@ -11,6 +11,7 @@ from onyx.server.features.persona.models import PersonaSharedNotificationData
def make_persona_private( def make_persona_private(
persona_id: int, persona_id: int,
creator_user_id: UUID | None,
user_ids: list[UUID] | None, user_ids: list[UUID] | None,
group_ids: list[int] | None, group_ids: list[int] | None,
db_session: Session, db_session: Session,
@ -29,15 +30,15 @@ def make_persona_private(
user_ids_set = set(user_ids) user_ids_set = set(user_ids)
for user_id in user_ids_set: for user_id in user_ids_set:
db_session.add(Persona__User(persona_id=persona_id, user_id=user_id)) db_session.add(Persona__User(persona_id=persona_id, user_id=user_id))
if user_id != creator_user_id:
create_notification( create_notification(
user_id=user_id, user_id=user_id,
notif_type=NotificationType.PERSONA_SHARED, notif_type=NotificationType.PERSONA_SHARED,
db_session=db_session, db_session=db_session,
additional_data=PersonaSharedNotificationData( additional_data=PersonaSharedNotificationData(
persona_id=persona_id, persona_id=persona_id,
).model_dump(), ).model_dump(),
) )
if group_ids: if group_ids:
group_ids_set = set(group_ids) group_ids_set = set(group_ids)

View File

@ -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, # 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. # the stream will contain AIMessageChunks with tool call information.
for message in messages: for message in messages:
answer_piece = message.content answer_piece = message.content
if not isinstance(answer_piece, str): if not isinstance(answer_piece, str):
# this is only used for logging, so fine to # this is only used for logging, so fine to

View File

@ -51,7 +51,6 @@ def _parse_agent_event(
Parse the event into a typed object. Parse the event into a typed object.
Return None if we are not interested in the event. Return None if we are not interested in the event.
""" """
event_type = event["event"] event_type = event["event"]
# We always just yield the event data, but this piece is useful for two development reasons: # We always just yield the event data, but this piece is useful for two development reasons:

View File

@ -112,7 +112,7 @@ def pre_provision_tenant() -> None:
r = get_redis_client(tenant_id=ONYX_CLOUD_TENANT_ID) r = get_redis_client(tenant_id=ONYX_CLOUD_TENANT_ID)
lock_provision: RedisLock = r.lock( lock_provision: RedisLock = r.lock(
OnyxRedisLocks.PRE_PROVISION_TENANT_LOCK, OnyxRedisLocks.CLOUD_PRE_PROVISION_TENANT_LOCK,
timeout=_TENANT_PROVISIONING_SOFT_TIME_LIMIT, timeout=_TENANT_PROVISIONING_SOFT_TIME_LIMIT,
) )

View File

@ -167,7 +167,6 @@ class Answer:
break break
processed_stream.append(packet) processed_stream.append(packet)
yield packet yield packet
self._processed_stream = processed_stream self._processed_stream = processed_stream
@property @property

View File

@ -334,7 +334,7 @@ class OnyxRedisLocks:
CHECK_USER_FILE_FOLDER_SYNC_BEAT_LOCK = "da_lock:check_user_file_folder_sync_beat" CHECK_USER_FILE_FOLDER_SYNC_BEAT_LOCK = "da_lock:check_user_file_folder_sync_beat"
MONITOR_BACKGROUND_PROCESSES_LOCK = "da_lock:monitor_background_processes" MONITOR_BACKGROUND_PROCESSES_LOCK = "da_lock:monitor_background_processes"
CHECK_AVAILABLE_TENANTS_LOCK = "da_lock:check_available_tenants" 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 = ( CONNECTOR_DOC_PERMISSIONS_SYNC_LOCK_PREFIX = (
"da_lock:connector_doc_permissions_sync" "da_lock:connector_doc_permissions_sync"
@ -405,7 +405,6 @@ class OnyxCeleryTask:
f"{ONYX_CLOUD_CELERY_TASK_PREFIX}_monitor_celery_pidbox" f"{ONYX_CLOUD_CELERY_TASK_PREFIX}_monitor_celery_pidbox"
) )
# Tenant pre-provisioning
UPDATE_USER_FILE_FOLDER_METADATA = "update_user_file_folder_metadata" UPDATE_USER_FILE_FOLDER_METADATA = "update_user_file_folder_metadata"
CHECK_FOR_CONNECTOR_DELETION = "check_for_connector_deletion_task" CHECK_FOR_CONNECTOR_DELETION = "check_for_connector_deletion_task"

View File

@ -3,7 +3,6 @@ from datetime import datetime
from uuid import UUID from uuid import UUID
from fastapi import HTTPException from fastapi import HTTPException
from sqlalchemy import delete
from sqlalchemy import exists from sqlalchemy import exists
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy import not_ from sqlalchemy import not_
@ -168,6 +167,7 @@ def _get_persona_by_name(
def make_persona_private( def make_persona_private(
persona_id: int, persona_id: int,
creator_user_id: UUID | None,
user_ids: list[UUID] | None, user_ids: list[UUID] | None,
group_ids: list[int] | None, group_ids: list[int] | None,
db_session: Session, db_session: Session,
@ -179,15 +179,15 @@ def make_persona_private(
for user_uuid in user_ids: for user_uuid in user_ids:
db_session.add(Persona__User(persona_id=persona_id, user_id=user_uuid)) db_session.add(Persona__User(persona_id=persona_id, user_id=user_uuid))
if user_uuid != creator_user_id:
create_notification( create_notification(
user_id=user_uuid, user_id=user_uuid,
notif_type=NotificationType.PERSONA_SHARED, notif_type=NotificationType.PERSONA_SHARED,
db_session=db_session, db_session=db_session,
additional_data=PersonaSharedNotificationData( additional_data=PersonaSharedNotificationData(
persona_id=persona_id, persona_id=persona_id,
).model_dump(), ).model_dump(),
) )
db_session.commit() db_session.commit()
@ -262,6 +262,7 @@ def create_update_persona(
# Privatize Persona # Privatize Persona
versioned_make_persona_private( versioned_make_persona_private(
persona_id=persona.id, persona_id=persona.id,
creator_user_id=user.id if user else None,
user_ids=create_persona_request.users, user_ids=create_persona_request.users,
group_ids=create_persona_request.groups, group_ids=create_persona_request.groups,
db_session=db_session, db_session=db_session,
@ -297,6 +298,7 @@ def update_persona_shared_users(
# Privatize Persona # Privatize Persona
versioned_make_persona_private( versioned_make_persona_private(
persona_id=persona_id, persona_id=persona_id,
creator_user_id=user.id if user else None,
user_ids=user_ids, user_ids=user_ids,
group_ids=None, group_ids=None,
db_session=db_session, db_session=db_session,
@ -770,8 +772,10 @@ def get_personas_by_ids(
def delete_persona_by_name( def delete_persona_by_name(
persona_name: str, db_session: Session, is_default: bool = True persona_name: str, db_session: Session, is_default: bool = True
) -> None: ) -> None:
stmt = delete(Persona).where( stmt = (
Persona.name == persona_name, Persona.builtin_persona == is_default update(Persona)
.where(Persona.name == persona_name, Persona.builtin_persona == is_default)
.values(deleted=True)
) )
db_session.execute(stmt) db_session.execute(stmt)

View File

@ -69,7 +69,8 @@ def test_llm_configuration(
existing_provider = fetch_existing_llm_provider( existing_provider = fetch_existing_llm_provider(
name=test_llm_request.name, db_session=db_session 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 test_api_key = existing_provider.api_key
# For this "testing" workflow, we do *not* need the actual `max_input_tokens`. # For this "testing" workflow, we do *not* need the actual `max_input_tokens`.

View File

@ -90,8 +90,25 @@ def get_folders(
db_session: Session = Depends(get_session), db_session: Session = Depends(get_session),
) -> list[UserFolderSnapshot]: ) -> list[UserFolderSnapshot]:
user_id = user.id if user else None user_id = user.id if user else None
folders = db_session.query(UserFolder).filter(UserFolder.user_id == user_id).all() # Get folders that belong to the user or have the RECENT_DOCS_FOLDER_ID
return [UserFolderSnapshot.from_model(folder) for folder in folders] 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}") @router.get("/user/folder/{folder_id}")
@ -103,13 +120,25 @@ def get_folder(
user_id = user.id if user else None user_id = user.id if user else None
folder = ( folder = (
db_session.query(UserFolder) 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() .first()
) )
if not folder: if not folder:
raise HTTPException(status_code=404, detail="Folder not found") 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 RECENT_DOCS_FOLDER_ID = -1

View File

@ -27,7 +27,15 @@ ONYX_REQUEST_ID_CONTEXTVAR: contextvars.ContextVar[str | None] = contextvars.Con
def get_current_tenant_id() -> str: def get_current_tenant_id() -> str:
tenant_id = CURRENT_TENANT_ID_CONTEXTVAR.get() tenant_id = CURRENT_TENANT_ID_CONTEXTVAR.get()
if tenant_id is None: if tenant_id is None:
import traceback
if not MULTI_TENANT: if not MULTI_TENANT:
return POSTGRES_DEFAULT_SCHEMA 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 return tenant_id

70
web/package-lock.json generated
View File

@ -1886,7 +1886,6 @@
}, },
"node_modules/@jridgewell/source-map": { "node_modules/@jridgewell/source-map": {
"version": "0.3.6", "version": "0.3.6",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/gen-mapping": "^0.3.5",
@ -2711,7 +2710,7 @@
"version": "1.51.1", "version": "1.51.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.1.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.1.tgz",
"integrity": "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==", "integrity": "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright": "1.51.1" "playwright": "1.51.1"
@ -4812,7 +4811,6 @@
}, },
"node_modules/@types/eslint": { "node_modules/@types/eslint": {
"version": "9.6.1", "version": "9.6.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/estree": "*", "@types/estree": "*",
@ -4821,7 +4819,6 @@
}, },
"node_modules/@types/eslint-scope": { "node_modules/@types/eslint-scope": {
"version": "3.7.7", "version": "3.7.7",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/eslint": "*", "@types/eslint": "*",
@ -4921,7 +4918,6 @@
}, },
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/json5": { "node_modules/@types/json5": {
@ -5276,7 +5272,6 @@
}, },
"node_modules/@webassemblyjs/ast": { "node_modules/@webassemblyjs/ast": {
"version": "1.14.1", "version": "1.14.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-numbers": "1.13.2",
@ -5285,22 +5280,18 @@
}, },
"node_modules/@webassemblyjs/floating-point-hex-parser": { "node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.13.2", "version": "1.13.2",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@webassemblyjs/helper-api-error": { "node_modules/@webassemblyjs/helper-api-error": {
"version": "1.13.2", "version": "1.13.2",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@webassemblyjs/helper-buffer": { "node_modules/@webassemblyjs/helper-buffer": {
"version": "1.14.1", "version": "1.14.1",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@webassemblyjs/helper-numbers": { "node_modules/@webassemblyjs/helper-numbers": {
"version": "1.13.2", "version": "1.13.2",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/floating-point-hex-parser": "1.13.2",
@ -5310,12 +5301,10 @@
}, },
"node_modules/@webassemblyjs/helper-wasm-bytecode": { "node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.13.2", "version": "1.13.2",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@webassemblyjs/helper-wasm-section": { "node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.14.1", "version": "1.14.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.14.1", "@webassemblyjs/ast": "1.14.1",
@ -5326,7 +5315,6 @@
}, },
"node_modules/@webassemblyjs/ieee754": { "node_modules/@webassemblyjs/ieee754": {
"version": "1.13.2", "version": "1.13.2",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@xtuc/ieee754": "^1.2.0" "@xtuc/ieee754": "^1.2.0"
@ -5334,7 +5322,6 @@
}, },
"node_modules/@webassemblyjs/leb128": { "node_modules/@webassemblyjs/leb128": {
"version": "1.13.2", "version": "1.13.2",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@xtuc/long": "4.2.2" "@xtuc/long": "4.2.2"
@ -5342,12 +5329,10 @@
}, },
"node_modules/@webassemblyjs/utf8": { "node_modules/@webassemblyjs/utf8": {
"version": "1.13.2", "version": "1.13.2",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@webassemblyjs/wasm-edit": { "node_modules/@webassemblyjs/wasm-edit": {
"version": "1.14.1", "version": "1.14.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.14.1", "@webassemblyjs/ast": "1.14.1",
@ -5362,7 +5347,6 @@
}, },
"node_modules/@webassemblyjs/wasm-gen": { "node_modules/@webassemblyjs/wasm-gen": {
"version": "1.14.1", "version": "1.14.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.14.1", "@webassemblyjs/ast": "1.14.1",
@ -5374,7 +5358,6 @@
}, },
"node_modules/@webassemblyjs/wasm-opt": { "node_modules/@webassemblyjs/wasm-opt": {
"version": "1.14.1", "version": "1.14.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.14.1", "@webassemblyjs/ast": "1.14.1",
@ -5385,7 +5368,6 @@
}, },
"node_modules/@webassemblyjs/wasm-parser": { "node_modules/@webassemblyjs/wasm-parser": {
"version": "1.14.1", "version": "1.14.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.14.1", "@webassemblyjs/ast": "1.14.1",
@ -5398,7 +5380,6 @@
}, },
"node_modules/@webassemblyjs/wast-printer": { "node_modules/@webassemblyjs/wast-printer": {
"version": "1.14.1", "version": "1.14.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@webassemblyjs/ast": "1.14.1", "@webassemblyjs/ast": "1.14.1",
@ -5407,12 +5388,10 @@
}, },
"node_modules/@xtuc/ieee754": { "node_modules/@xtuc/ieee754": {
"version": "1.2.0", "version": "1.2.0",
"dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/@xtuc/long": { "node_modules/@xtuc/long": {
"version": "4.2.2", "version": "4.2.2",
"dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/acorn": { "node_modules/acorn": {
@ -5467,7 +5446,6 @@
}, },
"node_modules/ajv-formats": { "node_modules/ajv-formats": {
"version": "2.1.1", "version": "2.1.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ajv": "^8.0.0" "ajv": "^8.0.0"
@ -5483,7 +5461,6 @@
}, },
"node_modules/ajv-formats/node_modules/ajv": { "node_modules/ajv-formats/node_modules/ajv": {
"version": "8.17.1", "version": "8.17.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -5498,7 +5475,6 @@
}, },
"node_modules/ajv-formats/node_modules/json-schema-traverse": { "node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0", "version": "1.0.0",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/ajv-keywords": { "node_modules/ajv-keywords": {
@ -6186,7 +6162,6 @@
}, },
"node_modules/buffer-from": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/busboy": { "node_modules/busboy": {
@ -6419,7 +6394,6 @@
}, },
"node_modules/chrome-trace-event": { "node_modules/chrome-trace-event": {
"version": "1.0.4", "version": "1.0.4",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
@ -7253,7 +7227,6 @@
}, },
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.18.1", "version": "5.18.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.4", "graceful-fs": "^4.2.4",
@ -7386,7 +7359,6 @@
}, },
"node_modules/es-module-lexer": { "node_modules/es-module-lexer": {
"version": "1.6.0", "version": "1.6.0",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/es-object-atoms": { "node_modules/es-object-atoms": {
@ -7949,7 +7921,6 @@
}, },
"node_modules/esrecurse": { "node_modules/esrecurse": {
"version": "4.3.0", "version": "4.3.0",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"estraverse": "^5.2.0" "estraverse": "^5.2.0"
@ -7960,7 +7931,6 @@
}, },
"node_modules/estraverse": { "node_modules/estraverse": {
"version": "5.3.0", "version": "5.3.0",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
@ -7992,7 +7962,6 @@
}, },
"node_modules/events": { "node_modules/events": {
"version": "3.3.0", "version": "3.3.0",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.8.x" "node": ">=0.8.x"
@ -8048,7 +8017,6 @@
}, },
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-equals": { "node_modules/fast-equals": {
@ -8094,7 +8062,6 @@
}, },
"node_modules/fast-uri": { "node_modules/fast-uri": {
"version": "3.0.6", "version": "3.0.6",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -8560,7 +8527,6 @@
}, },
"node_modules/glob-to-regexp": { "node_modules/glob-to-regexp": {
"version": "0.4.1", "version": "0.4.1",
"dev": true,
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/glob/node_modules/brace-expansion": { "node_modules/glob/node_modules/brace-expansion": {
@ -8617,7 +8583,6 @@
}, },
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/graphemer": { "node_modules/graphemer": {
@ -10752,7 +10717,6 @@
}, },
"node_modules/loader-runner": { "node_modules/loader-runner": {
"version": "4.3.0", "version": "4.3.0",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.11.5" "node": ">=6.11.5"
@ -11198,7 +11162,6 @@
}, },
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/merge2": { "node_modules/merge2": {
@ -11755,7 +11718,6 @@
}, },
"node_modules/mime-db": { "node_modules/mime-db": {
"version": "1.52.0", "version": "1.52.0",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@ -11763,7 +11725,6 @@
}, },
"node_modules/mime-types": { "node_modules/mime-types": {
"version": "2.1.35", "version": "2.1.35",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-db": "1.52.0" "mime-db": "1.52.0"
@ -11846,7 +11807,6 @@
}, },
"node_modules/neo-async": { "node_modules/neo-async": {
"version": "2.6.2", "version": "2.6.2",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/next": { "node_modules/next": {
@ -15033,7 +14993,7 @@
"version": "1.51.1", "version": "1.51.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz",
"integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==", "integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.51.1" "playwright-core": "1.51.1"
@ -15052,7 +15012,7 @@
"version": "1.51.1", "version": "1.51.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz",
"integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==", "integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"playwright-core": "cli.js" "playwright-core": "cli.js"
@ -15531,7 +15491,6 @@
}, },
"node_modules/randombytes": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"safe-buffer": "^5.1.0" "safe-buffer": "^5.1.0"
@ -16123,7 +16082,6 @@
}, },
"node_modules/require-from-string": { "node_modules/require-from-string": {
"version": "2.0.2", "version": "2.0.2",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -16294,7 +16252,6 @@
}, },
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -16383,7 +16340,6 @@
}, },
"node_modules/serialize-javascript": { "node_modules/serialize-javascript": {
"version": "6.0.2", "version": "6.0.2",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"randombytes": "^2.1.0" "randombytes": "^2.1.0"
@ -16591,7 +16547,6 @@
}, },
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -17205,7 +17160,6 @@
}, },
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.2.1", "version": "2.2.1",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -17213,7 +17167,6 @@
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.39.0", "version": "5.39.0",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
@ -17230,7 +17183,6 @@
}, },
"node_modules/terser-webpack-plugin": { "node_modules/terser-webpack-plugin": {
"version": "5.3.14", "version": "5.3.14",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/trace-mapping": "^0.3.25", "@jridgewell/trace-mapping": "^0.3.25",
@ -17263,7 +17215,6 @@
}, },
"node_modules/terser-webpack-plugin/node_modules/ajv": { "node_modules/terser-webpack-plugin/node_modules/ajv": {
"version": "8.17.1", "version": "8.17.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -17278,7 +17229,6 @@
}, },
"node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
"version": "5.1.0", "version": "5.1.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3" "fast-deep-equal": "^3.1.3"
@ -17289,7 +17239,6 @@
}, },
"node_modules/terser-webpack-plugin/node_modules/jest-worker": { "node_modules/terser-webpack-plugin/node_modules/jest-worker": {
"version": "27.5.1", "version": "27.5.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
@ -17302,12 +17251,10 @@
}, },
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
"version": "1.0.0", "version": "1.0.0",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/terser-webpack-plugin/node_modules/schema-utils": { "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
"version": "4.3.0", "version": "4.3.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",
@ -17325,7 +17272,6 @@
}, },
"node_modules/terser-webpack-plugin/node_modules/supports-color": { "node_modules/terser-webpack-plugin/node_modules/supports-color": {
"version": "8.1.1", "version": "8.1.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
@ -17339,12 +17285,10 @@
}, },
"node_modules/terser/node_modules/commander": { "node_modules/terser/node_modules/commander": {
"version": "2.20.3", "version": "2.20.3",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/terser/node_modules/source-map-support": { "node_modules/terser/node_modules/source-map-support": {
"version": "0.5.21", "version": "0.5.21",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"buffer-from": "^1.0.0", "buffer-from": "^1.0.0",
@ -18144,7 +18088,6 @@
}, },
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.4.2", "version": "2.4.2",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"glob-to-regexp": "^0.4.1", "glob-to-regexp": "^0.4.1",
@ -18172,7 +18115,6 @@
}, },
"node_modules/webpack": { "node_modules/webpack": {
"version": "5.98.0", "version": "5.98.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.7", "@types/eslint-scope": "^3.7.7",
@ -18315,7 +18257,6 @@
}, },
"node_modules/webpack/node_modules/ajv": { "node_modules/webpack/node_modules/ajv": {
"version": "8.17.1", "version": "8.17.1",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -18330,7 +18271,6 @@
}, },
"node_modules/webpack/node_modules/ajv-keywords": { "node_modules/webpack/node_modules/ajv-keywords": {
"version": "5.1.0", "version": "5.1.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3" "fast-deep-equal": "^3.1.3"
@ -18341,7 +18281,6 @@
}, },
"node_modules/webpack/node_modules/eslint-scope": { "node_modules/webpack/node_modules/eslint-scope": {
"version": "5.1.1", "version": "5.1.1",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
@ -18353,7 +18292,6 @@
}, },
"node_modules/webpack/node_modules/estraverse": { "node_modules/webpack/node_modules/estraverse": {
"version": "4.3.0", "version": "4.3.0",
"dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
@ -18361,12 +18299,10 @@
}, },
"node_modules/webpack/node_modules/json-schema-traverse": { "node_modules/webpack/node_modules/json-schema-traverse": {
"version": "1.0.0", "version": "1.0.0",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/webpack/node_modules/schema-utils": { "node_modules/webpack/node_modules/schema-utils": {
"version": "4.3.0", "version": "4.3.0",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.9", "@types/json-schema": "^7.0.9",

View File

@ -25,6 +25,7 @@ import {
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { useAuthType } from "@/lib/hooks"; import { useAuthType } from "@/lib/hooks";
import { InfoIcon } from "lucide-react";
function parseJsonWithTrailingCommas(jsonString: string) { function parseJsonWithTrailingCommas(jsonString: string) {
// Regular expression to remove trailing commas before } or ] // Regular expression to remove trailing commas before } or ]
@ -159,25 +160,14 @@ function ActionForm({
component="div" component="div"
className="mb-4 text-error text-sm" 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 <Link
href="https://docs.onyx.app/tools/custom" href="https://docs.onyx.app/tools/custom"
className="text-link hover:underline flex items-center" className="text-link hover:underline flex items-center"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<svg <InfoIcon className="w-4 h-4 mr-2 " />
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>
Learn more about actions in our documentation Learn more about actions in our documentation
</Link> </Link>
</div> </div>
@ -367,7 +357,7 @@ interface ToolFormValues {
} }
const ToolSchema = Yup.object().shape({ const ToolSchema = Yup.object().shape({
definition: Yup.string().required("Tool definition is required"), definition: Yup.string().required("Action definition is required"),
customHeaders: Yup.array() customHeaders: Yup.array()
.of( .of(
Yup.object().shape({ Yup.object().shape({

View File

@ -27,15 +27,17 @@ export default async function Page(props: {
); );
} else { } else {
body = ( body = (
<div className="w-full my-8"> <div className="w-full mt-8 pb-8">
<div> <div>
<div> <div>
<CardSection> <CardSection>
<ActionEditor tool={tool} /> <ActionEditor tool={tool} />
</CardSection> </CardSection>
<Title className="mt-12">Delete Tool</Title> <Title className="mt-12">Delete Action</Title>
<Text>Click the button below to permanently delete this tool.</Text> <Text>
Click the button below to permanently delete this action.
</Text>
<div className="flex mt-6"> <div className="flex mt-6">
<DeleteToolButton toolId={tool.id} /> <DeleteToolButton toolId={tool.id} />
</div> </div>
@ -50,7 +52,7 @@ export default async function Page(props: {
<BackButton /> <BackButton />
<AdminPageTitle <AdminPageTitle
title="Edit Tool" title="Edit Action"
icon={<ToolIcon size={32} className="my-auto" />} icon={<ToolIcon size={32} className="my-auto" />}
/> />

View File

@ -41,11 +41,8 @@ function NewApiKeyModal({
const [copyClicked, setCopyClicked] = useState(false); const [copyClicked, setCopyClicked] = useState(false);
return ( return (
<Modal onOutsideClick={onClose}> <Modal title="New API Key" onOutsideClick={onClose}>
<div className="px-8 py-8"> <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"> <div className="h-32">
<Text className="mb-4"> <Text className="mb-4">
Make sure you copy your new API key. You wont be able to see this Make sure you copy your new API key. You wont be able to see this

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@ import {
} from "@/components/ui/table"; } from "@/components/ui/table";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { SlackBot } from "@/lib/types"; import { SlackBot } from "@/lib/types";
import { EditIcon } from "@/components/icons/icons";
const NUM_IN_PAGE = 20; const NUM_IN_PAGE = 20;
@ -83,7 +84,7 @@ export const SlackBotTable = ({ slackBots }: { slackBots: SlackBot[] }) => {
> >
<TableCell> <TableCell>
<div className="flex items-center"> <div className="flex items-center">
<FiEdit className="mr-4" /> <EditIcon className="mr-4" />
{slackBot.name} {slackBot.name}
</div> </div>
</TableCell> </TableCell>

View File

@ -424,7 +424,6 @@ export function LLMProviderUpdateForm({
/> />
</div> </div>
)} )}
<IsPublicGroupSelector <IsPublicGroupSelector
formikProps={formikProps} formikProps={formikProps}
objectName="LLM Provider" objectName="LLM Provider"

View File

@ -168,7 +168,7 @@ export function Explorer({
return ( return (
<div> <div>
{popup} {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"> <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 /> <MagnifyingGlass />
<textarea <textarea

View File

@ -17,6 +17,7 @@ import { getErrorMsg } from "@/lib/fetchUtils";
import { HoverPopup } from "@/components/HoverPopup"; import { HoverPopup } from "@/components/HoverPopup";
import { CustomCheckbox } from "@/components/CustomCheckbox"; import { CustomCheckbox } from "@/components/CustomCheckbox";
import { ScoreSection } from "../ScoreEditor"; import { ScoreSection } from "../ScoreEditor";
import { truncateString } from "@/lib/utils";
const IsVisibleSection = ({ const IsVisibleSection = ({
document, document,
@ -109,12 +110,12 @@ export const DocumentFeedbackTable = ({
<TableRow key={document.document_id}> <TableRow key={document.document_id}>
<TableCell className="whitespace-normal break-all"> <TableCell className="whitespace-normal break-all">
<a <a
className="text-blue-600" className="text-blue-600 dark:text-blue-300"
href={document.link} href={document.link}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{document.semantic_id} {truncateString(document.semantic_id, 100)}
</a> </a>
</TableCell> </TableCell>
<TableCell> <TableCell>

View File

@ -144,6 +144,7 @@ function Main() {
/> />
{isPaidEnterpriseFeaturesEnabled && ( {isPaidEnterpriseFeaturesEnabled && (
<Tabs <Tabs
className="mt-2"
value={tabIndex.toString()} value={tabIndex.toString()}
onValueChange={(val) => setTabIndex(parseInt(val))} onValueChange={(val) => setTabIndex(parseInt(val))}
> >

View File

@ -14,7 +14,7 @@ const Page = () => {
Authentication Error Authentication Error
</h2> </h2>
<p className="text-text-700 text-center"> <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> </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"> <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"> <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. please reach out to your system administrator for assistance.
{NEXT_PUBLIC_CLOUD_ENABLED && ( {NEXT_PUBLIC_CLOUD_ENABLED && (
<span className="block mt-1 text-blue-600"> <span className="block mt-1 text-blue-600">
A member of our team has been automatically notified about this If you continue to experience problems please reach out to the
issue. Onyx team at{" "}
<a href="mailto:support@onyx.app" className="text-blue-600">
support@onyx.app
</a>
</span> </span>
)} )}
</p> </p>

View File

@ -141,9 +141,6 @@ export function EmailPasswordForm({
name="password" name="password"
label="Password" label="Password"
type="password" type="password"
includeForgotPassword={
NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && !isSignup
}
placeholder="**************" placeholder="**************"
/> />

View File

@ -29,38 +29,52 @@ export default function LoginPage({
useSendAuthRequiredMessage(); useSendAuthRequiredMessage();
return ( return (
<div className="flex flex-col w-full justify-center"> <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}
/>
</>
)}
{authTypeMetadata?.authType === "cloud" && (
<div className="w-full justify-center">
<h2 className="text-center text-xl text-strong font-bold"> <h2 className="text-center text-xl text-strong font-bold">
<LoginText /> <LoginText />
</h2> </h2>
<SignInButton
authorizeUrl={authUrl}
authType={authTypeMetadata?.authType}
/>
</>
)}
{authTypeMetadata?.authType === "cloud" && (
<div className="mt-4 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>
<EmailPasswordForm shouldVerify={true} nextUrl={nextUrl} /> <EmailPasswordForm shouldVerify={true} nextUrl={nextUrl} />
{NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && ( {NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && (
<div className="flex mt-4 justify-between"> <div className="flex mt-4 justify-between">
<Link <Link
href="/auth/forgot-password" href="/auth/forgot-password"
className="text-link font-medium" className="ml-auto text-link font-medium"
> >
Reset Password Reset Password
</Link> </Link>
</div> </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>
<SignInButton
authorizeUrl={authUrl}
authType={authTypeMetadata?.authType}
/>
</>
)}
</div> </div>
)} )}

View File

@ -40,10 +40,19 @@ const Page = async (props: {
// if user is already logged in, take them to the main app page // if user is already logged in, take them to the main app page
if (currentUser && currentUser.is_active && !currentUser.is_anonymous_user) { 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) { if (authTypeMetadata?.requiresVerification && !currentUser.is_verified) {
return redirect("/auth/waiting-on-verification"); 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 // get where to send the user to authenticate

View File

@ -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 <EmailPasswordForm
isSignup isSignup
shouldVerify={authTypeMetadata?.requiresVerification} shouldVerify={authTypeMetadata?.requiresVerification}
nextUrl={nextUrl} nextUrl={nextUrl}
defaultEmail={defaultEmail} 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> </div>
</> </>
</AuthFlowContainer> </AuthFlowContainer>

View File

@ -104,9 +104,7 @@ import {
SIDEBAR_TOGGLED_COOKIE_NAME, SIDEBAR_TOGGLED_COOKIE_NAME,
} from "@/components/resizable/constants"; } from "@/components/resizable/constants";
import FixedLogo from "@/components/logo/FixedLogo"; import FixedLogo from "@/components/logo/FixedLogo";
import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal"; import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal";
import { SEARCH_TOOL_ID, SEARCH_TOOL_NAME } from "./tools/constants"; import { SEARCH_TOOL_ID, SEARCH_TOOL_NAME } from "./tools/constants";
import { useUser } from "@/components/user/UserProvider"; import { useUser } from "@/components/user/UserProvider";
import { ApiKeyModal } from "@/components/llm/ApiKeyModal"; import { ApiKeyModal } from "@/components/llm/ApiKeyModal";
@ -178,12 +176,10 @@ export function ChatPage({
selectedFolders, selectedFolders,
addSelectedFile, addSelectedFile,
addSelectedFolder, addSelectedFolder,
removeSelectedFolder,
clearSelectedItems, clearSelectedItems,
folders: userFolders, folders: userFolders,
files: allUserFiles, files: allUserFiles,
uploadFile, uploadFile,
removeSelectedFile,
currentMessageFiles, currentMessageFiles,
setCurrentMessageFiles, setCurrentMessageFiles,
} = useDocumentsContext(); } = useDocumentsContext();
@ -224,7 +220,6 @@ export function ChatPage({
const settings = useContext(SettingsContext); const settings = useContext(SettingsContext);
const enterpriseSettings = settings?.enterpriseSettings; const enterpriseSettings = settings?.enterpriseSettings;
const [viewingFilePicker, setViewingFilePicker] = useState(false);
const [toggleDocSelection, setToggleDocSelection] = useState(false); const [toggleDocSelection, setToggleDocSelection] = useState(false);
const [documentSidebarVisible, setDocumentSidebarVisible] = useState(false); const [documentSidebarVisible, setDocumentSidebarVisible] = useState(false);
const [proSearchEnabled, setProSearchEnabled] = useState(proSearchToggled); const [proSearchEnabled, setProSearchEnabled] = useState(proSearchToggled);
@ -706,6 +701,7 @@ export function ChatPage({
chatSessionId || currentSessionId(), chatSessionId || currentSessionId(),
newCompleteMessageMap newCompleteMessageMap
); );
console.log(newCompleteMessageDetail);
return newCompleteMessageDetail; return newCompleteMessageDetail;
}; };
@ -1171,6 +1167,13 @@ export function ChatPage({
}; };
}, [autoScrollEnabled, screenHeight, currentSessionHasSentLocalUserMessage]); }, [autoScrollEnabled, screenHeight, currentSessionHasSentLocalUserMessage]);
const reset = () => {
setMessage("");
setCurrentMessageFiles([]);
clearSelectedItems();
setLoadingError(null);
};
const onSubmit = async ({ const onSubmit = async ({
messageIdToResend, messageIdToResend,
messageOverride, messageOverride,
@ -1201,6 +1204,61 @@ export function ChatPage({
// Mark that we've sent a message for this session in the current page load // Mark that we've sent a message for this session in the current page load
markSessionMessageSent(frozenSessionId); 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() != "input") {
if (currentChatState() == "uploading") { if (currentChatState() == "uploading") {
setPopup({ setPopup({
@ -1270,11 +1328,10 @@ export function ChatPage({
currentSessionId() currentSessionId()
); );
} }
const messageMap = currentMessageMap(completeMessageDetail);
const messageToResendParent = const messageToResendParent =
messageToResend?.parentMessageId !== null && messageToResend?.parentMessageId !== null &&
messageToResend?.parentMessageId !== undefined messageToResend?.parentMessageId !== undefined
? messageMap.get(messageToResend.parentMessageId) ? currentMap.get(messageToResend.parentMessageId)
: null; : null;
const messageToResendIndex = messageToResend const messageToResendIndex = messageToResend
? messageHistory.indexOf(messageToResend) ? messageHistory.indexOf(messageToResend)
@ -1301,15 +1358,15 @@ export function ChatPage({
const currMessageHistory = const currMessageHistory =
messageToResendIndex !== null messageToResendIndex !== null
? messageHistory.slice(0, messageToResendIndex) ? currentHistory.slice(0, messageToResendIndex)
: messageHistory; : currentHistory;
let parentMessage = let parentMessage =
messageToResendParent || messageToResendParent ||
(currMessageHistory.length > 0 (currMessageHistory.length > 0
? currMessageHistory[currMessageHistory.length - 1] ? currMessageHistory[currMessageHistory.length - 1]
: null) || : null) ||
(messageMap.size === 1 ? Array.from(messageMap.values())[0] : null); (currentMap.size === 1 ? Array.from(currentMap.values())[0] : null);
let currentAssistantId; let currentAssistantId;
if (alternativeAssistantOverride) { if (alternativeAssistantOverride) {
@ -1356,13 +1413,9 @@ export function ChatPage({
frozenMessageMap: Map<number, Message>; frozenMessageMap: Map<number, Message>;
} = null; } = null;
try { try {
const mapKeys = Array.from( const mapKeys = Array.from(currentMap.keys());
currentMessageMap(completeMessageDetail).keys()
);
const systemMessage = Math.min(...mapKeys);
const lastSuccessfulMessageId = const lastSuccessfulMessageId =
getLastSuccessfulMessageId(currMessageHistory) || systemMessage; getLastSuccessfulMessageId(currMessageHistory);
const stack = new CurrentMessageFIFO(); const stack = new CurrentMessageFIFO();
@ -1479,11 +1532,12 @@ export function ChatPage({
upsertToCompleteMessageMap({ upsertToCompleteMessageMap({
messages: messageUpdates, messages: messageUpdates,
chatSessionId: currChatSessionId, chatSessionId: currChatSessionId,
completeMessageMapOverride: currentMap,
}); });
currentMap = currentFrozenMessageMap;
const frozenMessageMap = currentFrozenMessageMap;
initialFetchDetails = { initialFetchDetails = {
frozenMessageMap, frozenMessageMap: currentMap,
assistant_message_id, assistant_message_id,
user_message_id, user_message_id,
}; };
@ -1715,14 +1769,18 @@ export function ChatPage({
] as [number, number][]) ] as [number, number][])
: null; : null;
return upsertToCompleteMessageMap({ const newMessageDetails = upsertToCompleteMessageMap({
messages: messages, messages: messages,
replacementsMap: replacementsMap, replacementsMap: replacementsMap,
completeMessageMapOverride: frozenMessageMap, // Pass the latest map state
completeMessageMapOverride: currentMap,
chatSessionId: frozenSessionId!, chatSessionId: frozenSessionId!,
}); });
currentMap = newMessageDetails.messageMap;
return newMessageDetails;
}; };
const systemMessageId = Math.min(...mapKeys);
updateFn([ updateFn([
{ {
messageId: regenerationRequest messageId: regenerationRequest
@ -1732,7 +1790,8 @@ export function ChatPage({
type: "user", type: "user",
files: files, files: files,
toolCall: null, toolCall: null,
parentMessageId: error ? null : lastSuccessfulMessageId, // in the frontend, every message should have a parent ID
parentMessageId: lastSuccessfulMessageId ?? systemMessageId,
childrenMessageIds: [ childrenMessageIds: [
...(regenerationRequest?.parentMessage?.childrenMessageIds || ...(regenerationRequest?.parentMessage?.childrenMessageIds ||
[]), []),
@ -1786,7 +1845,7 @@ export function ChatPage({
} catch (e: any) { } catch (e: any) {
console.log("Error:", e); console.log("Error:", e);
const errorMsg = e.message; const errorMsg = e.message;
upsertToCompleteMessageMap({ const newMessageDetails = upsertToCompleteMessageMap({
messages: [ messages: [
{ {
messageId: messageId:
@ -1809,8 +1868,9 @@ export function ChatPage({
initialFetchDetails?.user_message_id || TEMP_USER_MESSAGE_ID, initialFetchDetails?.user_message_id || TEMP_USER_MESSAGE_ID,
}, },
], ],
completeMessageMapOverride: currentMessageMap(completeMessageDetail), completeMessageMapOverride: currentMap,
}); });
currentMap = newMessageDetails.messageMap;
} }
console.log("Finished streaming"); console.log("Finished streaming");
setAgenticGenerating(false); setAgenticGenerating(false);
@ -1909,15 +1969,21 @@ export function ChatPage({
updateChatState("uploading", currentSessionId()); updateChatState("uploading", currentSessionId());
const [uploadedFiles, error] = await uploadFilesForChat(acceptedFiles); for (let file of acceptedFiles) {
if (error) { const formData = new FormData();
setPopup({ formData.append("files", file);
type: "error", const response = await uploadFile(formData, null);
message: error,
});
}
setCurrentMessageFiles((prev) => [...prev, ...uploadedFiles]); if (response.length > 0) {
const uploadedFile = response[0];
addSelectedFile(uploadedFile);
} else {
setPopup({
type: "error",
message: "Failed to upload file",
});
}
}
updateChatState("input", currentSessionId()); updateChatState("input", currentSessionId());
}; };
@ -2396,7 +2462,7 @@ export function ChatPage({
liveAssistant={liveAssistant} liveAssistant={liveAssistant}
setShowAssistantsModal={setShowAssistantsModal} setShowAssistantsModal={setShowAssistantsModal}
explicitlyUntoggle={explicitlyUntoggle} explicitlyUntoggle={explicitlyUntoggle}
reset={() => setMessage("")} reset={reset}
page="chat" page="chat"
ref={innerSidebarElementRef} ref={innerSidebarElementRef}
toggleSidebar={toggleSidebar} toggleSidebar={toggleSidebar}
@ -3294,7 +3360,7 @@ export function ChatPage({
: "w-[0px]" : "w-[0px]"
} }
`} `}
></div> />
</div> </div>
)} )}
</Dropzone> </Dropzone>

View File

@ -149,7 +149,7 @@ export const FolderDropdown = forwardRef<HTMLDivElement, FolderDropdownProps>(
ref={setNodeRef} ref={setNodeRef}
style={style} style={style}
{...attributes} {...attributes}
className="overflow-visible mt-2 w-full" className="overflow-visible pt-2 w-full"
onDragOver={handleDragOver} onDragOver={handleDragOver}
onDrop={handleDrop} onDrop={handleDrop}
> >
@ -159,13 +159,13 @@ export const FolderDropdown = forwardRef<HTMLDivElement, FolderDropdownProps>(
> >
<div <div
ref={ref} 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 }} style={{ zIndex: 10 - index }}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
> >
<button <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)} onClick={() => !isEditing && setIsOpen(!isOpen)}
{...(isEditing ? {} : listeners)} {...(isEditing ? {} : listeners)}
> >

View File

@ -402,17 +402,16 @@ export function ChatInputBar({
} }
} }
} }
if (!showPrompts && !showSuggestions) { if (!showPrompts && !showSuggestions) {
return; return;
} }
if (e.key === "ArrowDown") { if (e.key === "ArrowDown") {
e.preventDefault(); e.preventDefault();
setTabbingIconIndex((tabbingIconIndex) => setTabbingIconIndex((tabbingIconIndex) =>
Math.min( Math.min(
tabbingIconIndex + 1, tabbingIconIndex + 1,
// showPrompts ? filteredPrompts.length : showPrompts ? filteredPrompts.length : assistantTagOptions.length
assistantTagOptions.length
) )
); );
} else if (e.key === "ArrowUp") { } else if (e.key === "ArrowUp") {
@ -440,13 +439,14 @@ export function ChatInputBar({
ref={suggestionsRef} ref={suggestionsRef}
className="text-sm absolute w-[calc(100%-2rem)] top-0 transform -translate-y-full" 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) => ( {assistantTagOptions.map((currentAssistant, index) => (
<button <button
key={index} key={index}
className={`px-2 ${ className={`px-2 ${
tabbingIconIndex == index && "bg-neutral-200" tabbingIconIndex == index &&
} rounded items-center rounded-lg content-start flex gap-x-1 py-2 w-full hover:bg-neutral-200/90 cursor-pointer`} "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={() => { onClick={() => {
updatedTaggedAssistant(currentAssistant); updatedTaggedAssistant(currentAssistant);
}} }}
@ -468,8 +468,8 @@ export function ChatInputBar({
target="_self" target="_self"
className={`${ className={`${
tabbingIconIndex == assistantTagOptions.length && tabbingIconIndex == assistantTagOptions.length &&
"bg-neutral-200" "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 cursor-pointer`} } 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" href="/assistants/new"
> >
<FiPlus size={17} /> <FiPlus size={17} />
@ -484,14 +484,15 @@ export function ChatInputBar({
ref={suggestionsRef} ref={suggestionsRef}
className="text-sm absolute inset-x-0 top-0 w-full transform -translate-y-full" 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( {filteredPrompts.map(
(currentPrompt: InputPrompt, index: number) => ( (currentPrompt: InputPrompt, index: number) => (
<button <button
key={index} key={index}
className={`px-2 ${ className={`px-2 ${
tabbingIconIndex == index && "bg-background-dark/75" tabbingIconIndex == index &&
} rounded content-start flex gap-x-1 py-1.5 w-full hover:bg-background-dark/90 cursor-pointer`} "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={() => { onClick={() => {
updateInputPrompt(currentPrompt); updateInputPrompt(currentPrompt);
}} }}
@ -509,8 +510,8 @@ export function ChatInputBar({
target="_self" target="_self"
className={`${ className={`${
tabbingIconIndex == filteredPrompts.length && tabbingIconIndex == filteredPrompts.length &&
"bg-background-dark/75" "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 cursor-pointer`} } 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" href="/chat/input-prompts"
> >
<FiPlus size={17} /> <FiPlus size={17} />

View File

@ -18,6 +18,7 @@ export default async function Layout({
); );
if ("redirect" in data) { if ("redirect" in data) {
console.log("redirect", data.redirect);
redirect(data.redirect); redirect(data.redirect);
} }

View File

@ -36,7 +36,10 @@ export const MemoizedAnchor = memo(
if (match) { if (match) {
const isUserFileCitation = userFiles?.length && userFiles.length > 0; const isUserFileCitation = userFiles?.length && userFiles.length > 0;
if (isUserFileCitation) { 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]; const associatedUserFile = userFiles?.[index];
if (!associatedUserFile) { if (!associatedUserFile) {
return <a href={children as string}>{children}</a>; return <a href={children as string}>{children}</a>;

View File

@ -342,12 +342,7 @@ export const AIMessage = ({
} }
const processed = preprocessLaTeX(content); const processed = preprocessLaTeX(content);
// Escape $ that are preceded by a space and followed by a non-$ character return processed + (!isComplete && !toolCallGenerating ? " [*]() " : "");
const escapedDollarSigns = processed.replace(/([\s])\$([^\$])/g, "$1\\$$2");
return (
escapedDollarSigns + (!isComplete && !toolCallGenerating ? " [*]() " : "")
);
}; };
const finalContentProcessed = processContent(finalContent as string); const finalContentProcessed = processContent(finalContent as string);

View File

@ -5,7 +5,7 @@ import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces"
import { destructureValue, structureValue } from "@/lib/llm/utils"; import { destructureValue, structureValue } from "@/lib/llm/utils";
import { setUserDefaultModel } from "@/lib/users/UserSettings"; 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 { PopupSpec } from "@/components/admin/connectors/Popup";
import { useUser } from "@/components/user/UserProvider"; import { useUser } from "@/components/user/UserProvider";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
@ -207,6 +207,8 @@ export function UserSettingsModal({
setIsLoading(false); setIsLoading(false);
} }
}; };
const pathname = usePathname();
const showPasswordSection = user?.password_configured; const showPasswordSection = user?.password_configured;
const handleDeleteAllChats = async () => { const handleDeleteAllChats = async () => {
@ -219,7 +221,9 @@ export function UserSettingsModal({
type: "success", type: "success",
}); });
refreshChatSessions(); refreshChatSessions();
router.push("/chat"); if (pathname.includes("/chat")) {
router.push("/chat");
}
} else { } else {
throw new Error("Failed to delete all chat sessions"); throw new Error("Failed to delete all chat sessions");
} }
@ -382,7 +386,7 @@ export function UserSettingsModal({
<div className="pt-4 border-t border-border"> <div className="pt-4 border-t border-border">
{!showDeleteConfirmation ? ( {!showDeleteConfirmation ? (
<div className="space-y-3"> <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 This will permanently delete all your chat sessions and
cannot be undone. cannot be undone.
</p> </p>
@ -397,7 +401,7 @@ export function UserSettingsModal({
</div> </div>
) : ( ) : (
<div className="space-y-3"> <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? Are you sure you want to delete all your chat sessions?
</p> </p>
<div className="flex gap-2"> <div className="flex gap-2">

View File

@ -160,6 +160,7 @@ export const DocumentsProvider: React.FC<DocumentsProviderProps> = ({
const refreshFolders = async () => { const refreshFolders = async () => {
try { try {
console.log("fetching folders");
const data = await documentsService.fetchFolders(); const data = await documentsService.fetchFolders();
setFolders(data); setFolders(data);
} catch (error) { } catch (error) {

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import React, { useMemo, useState, useTransition } from "react"; import React, { useEffect, useMemo, useState, useTransition } from "react";
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import { import {
Plus, Plus,
@ -68,11 +68,23 @@ export default function MyDocuments() {
const [sortDirection, setSortDirection] = useState<SortDirection>( const [sortDirection, setSortDirection] = useState<SortDirection>(
SortDirection.Descending SortDirection.Descending
); );
const pageLimit = 10;
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const router = useRouter(); const router = useRouter();
const { popup, setPopup } = usePopup(); const { popup, setPopup } = usePopup();
const [isCreateFolderOpen, setIsCreateFolderOpen] = useState(false); 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 [isPending, startTransition] = useTransition();
const [hoveredColumn, setHoveredColumn] = useState<SortType | null>(null); const [hoveredColumn, setHoveredColumn] = useState<SortType | null>(null);
@ -118,120 +130,24 @@ export default function MyDocuments() {
}; };
const handleDeleteItem = async (itemId: number, isFolder: boolean) => { 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`,
type: "success",
});
await refreshFolders();
} catch (error) {
console.error("Error deleting item:", error);
setPopup({
message: `Failed to delete 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 { try {
await downloadItem(documentId); await deleteItem(itemId, isFolder);
} catch (error) {
console.error("Error downloading file:", error);
setPopup({ setPopup({
message: "Failed to download file", 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 ${isFolder ? "folder" : "file"}`,
type: "error", 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(() => { const filteredFolders = useMemo(() => {
return folders return folders
.filter( .filter(
@ -438,11 +354,7 @@ export default function MyDocuments() {
onClick={handleFolderClick} onClick={handleFolderClick}
description={folder.description} description={folder.description}
lastUpdated={folder.created_at} lastUpdated={folder.created_at}
onRename={() => onRenameItem(folder.id, folder.name, true)}
onDelete={() => handleDeleteItem(folder.id, true)} onDelete={() => handleDeleteItem(folder.id, true)}
onMove={() =>
handleMoveItem(folder.id, currentFolder, true)
}
/> />
))} ))}
</div> </div>

View File

@ -588,29 +588,6 @@ export default function UserFolderContent({ folderId }: { folderId: number }) {
{/* Invalid file message */} {/* 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 <DeleteEntityModal
isOpen={isDeleteModalOpen} isOpen={isDeleteModalOpen}
onClose={() => setIsDeleteModalOpen(false)} 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 -mt-[1px] flex-col w-full">
<div className="flex items-center mb-3"> <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 <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} onClick={handleBack}
> >
My Documents My Documents

View File

@ -255,7 +255,7 @@ export const FileListItem: React.FC<FileListItemProps> = ({
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
variant="ghost" 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" /> <MoreHorizontal className="h-4 w-4" />
</Button> </Button>

View File

@ -104,7 +104,7 @@ const DraggableItem: React.FC<{
<div className="w-6 flex items-center justify-center shrink-0"> <div className="w-6 flex items-center justify-center shrink-0">
<div <div
className={`${ className={`${
isSelected ? "" : "opacity-0 group-hover:opacity-100" isSelected ? "" : "desktop:opacity-0 group-hover:opacity-100"
} transition-opacity duration-150`} } transition-opacity duration-150`}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -199,7 +199,7 @@ const FilePickerFolderItem: React.FC<{
className={`transition-opacity duration-150 ${ className={`transition-opacity duration-150 ${
isSelected || allFilesSelected isSelected || allFilesSelected
? "opacity-100" ? "opacity-100"
: "opacity-0 group-hover:opacity-100" : "desktop:opacity-0 group-hover:opacity-100"
}`} }`}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@ -276,7 +276,10 @@ const FilePickerFolderItem: React.FC<{
export interface FilePickerModalProps { export interface FilePickerModalProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
onSave: () => void; onSave: (
selectedFiles: FileResponse[],
selectedFolders: FolderResponse[]
) => void;
buttonContent: string; buttonContent: string;
setPresentingDocument: (onyxDocument: MinimalOnyxDocument) => void; setPresentingDocument: (onyxDocument: MinimalOnyxDocument) => void;
} }
@ -323,8 +326,6 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
createFileFromLink, createFileFromLink,
} = useDocumentsContext(); } = useDocumentsContext();
const router = useRouter();
const [linkUrl, setLinkUrl] = useState("");
const [isCreatingFileFromLink, setIsCreatingFileFromLink] = useState(false); const [isCreatingFileFromLink, setIsCreatingFileFromLink] = useState(false);
const [isUploadingFile, setIsUploadingFile] = useState(false); const [isUploadingFile, setIsUploadingFile] = useState(false);
@ -395,12 +396,6 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
} }
}, [isOpen, selectedFiles, selectedFolders]); }, [isOpen, selectedFiles, selectedFolders]);
useEffect(() => {
if (isOpen) {
refreshFolders();
}
}, [isOpen, refreshFolders]);
useEffect(() => { useEffect(() => {
if (currentFolder) { if (currentFolder) {
if (currentFolder === -1) { 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="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="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="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"> <div className="w-full relative">
@ -1251,16 +1246,16 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
) : folders.length > 0 ? ( ) : folders.length > 0 ? (
<div className="flex-grow overflow-y-auto px-4"> <div className="flex-grow overflow-y-auto px-4">
<p className="text-text-subtle dark:text-neutral-400"> <p className="text-text-subtle dark:text-neutral-400">
No groups found No folders found
</p> </p>
</div> </div>
) : ( ) : (
<div className="flex-grow flex-col overflow-y-auto px-4 flex items-start justify-start gap-y-2"> <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"> <p className="text-sm text-muted-foreground dark:text-neutral-400">
No groups found No folders found
</p> </p>
<a <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" 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" /> <FolderIcon className="mr-2 h-4 w-4" />
@ -1270,14 +1265,20 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
)} )}
</div> </div>
<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" : "" isHoveringRight ? "bg-neutral-100 dark:bg-neutral-800/30" : ""
}`} }`}
onDragEnter={() => setIsHoveringRight(true)} onDragEnter={() => setIsHoveringRight(true)}
onDragLeave={() => setIsHoveringRight(false)} onDragLeave={() => setIsHoveringRight(false)}
> >
<div className="px-5 pb-5 flex-1 flex flex-col"> <div className="px-5 h-full flex flex-col">
<div className="shrink default-scrollbar flex h-full overflow-y-auto mb-3"> {/* 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 <SelectedItemsList
uploadingFiles={uploadingFiles} uploadingFiles={uploadingFiles}
setPresentingDocument={setPresentingDocument} setPresentingDocument={setPresentingDocument}
@ -1288,69 +1289,68 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
/> />
</div> </div>
<div className="flex flex-col space-y-3"> {/* Bottom section: fixed height, doesn't flex */}
<div className="flex flex-col space-y-2"> <div className="flex-none py-2">
<FileUploadSection <FileUploadSection
disabled={isUploadingFile || isCreatingFileFromLink} disabled={isUploadingFile || isCreatingFileFromLink}
onUpload={(files: File[]) => { onUpload={(files: File[]) => {
setIsUploadingFile(true); setIsUploadingFile(true);
setUploadStartTime(Date.now()); // Record start time setUploadStartTime(Date.now()); // Record start time
// Add files to uploading files state // Start the refresh interval to simulate progress
startRefreshInterval();
// Start the refresh interval to simulate progress // Convert File[] to FileList for addUploadedFileToContext
startRefreshInterval(); const fileListArray = Array.from(files);
const fileList = new DataTransfer();
fileListArray.forEach((file) => fileList.items.add(file));
// Convert File[] to FileList for addUploadedFileToContext addUploadedFileToContext(fileList.files)
const fileListArray = Array.from(files); .then(() => refreshFolders())
const fileList = new DataTransfer(); .finally(() => {
fileListArray.forEach((file) => fileList.items.add(file)); setIsUploadingFile(false);
});
}}
onUrlUpload={async (url: string) => {
setIsCreatingFileFromLink(true);
setUploadStartTime(Date.now()); // Record start time
addUploadedFileToContext(fileList.files) // Add URL to uploading files
.then(() => refreshFolders()) setUploadingFiles((prev) => [
.finally(() => { ...prev,
setIsUploadingFile(false); { name: url, progress: 0 },
}); ]);
}}
onUrlUpload={async (url: string) => {
setIsCreatingFileFromLink(true);
setUploadStartTime(Date.now()); // Record start time
// Add URL to uploading files // Start the refresh interval to simulate progress
setUploadingFiles((prev) => [ startRefreshInterval();
...prev,
{ name: url, progress: 0 },
]);
// Start the refresh interval to simulate progress try {
startRefreshInterval(); const response: FileResponse[] = await createFileFromLink(
url,
-1
);
try { if (response.length > 0) {
const response: FileResponse[] = // Extract domain from URL to help with detection
await createFileFromLink(url, -1); const urlObj = new URL(url);
if (response.length > 0) { const createdFile: FileResponse = response[0];
// Extract domain from URL to help with detection addSelectedFile(createdFile);
const urlObj = new URL(url); // Make sure to remove the uploading file indicator when done
const createdFile: FileResponse = response[0];
addSelectedFile(createdFile);
// Make sure to remove the uploading file indicator when done
markFileComplete(url);
}
await refreshFolders();
} catch (e) {
console.error("Error creating file from link:", e);
// Also remove the uploading indicator on error
markFileComplete(url); markFileComplete(url);
} finally {
setIsCreatingFileFromLink(false);
} }
}}
isUploading={isUploadingFile || isCreatingFileFromLink} await refreshFolders();
/> } catch (e) {
</div> console.error("Error creating file from link:", e);
// Also remove the uploading indicator on error
markFileComplete(url);
} finally {
setIsCreatingFileFromLink(false);
}
}}
isUploading={isUploadingFile || isCreatingFileFromLink}
/>
</div> </div>
</div> </div>
</div> </div>
@ -1375,7 +1375,10 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div> <div>
<Button <Button
onClick={onSave} type="button"
onClick={() =>
onSave(selectedItems.files, selectedItems.folders)
}
className="px-8 py-2 w-48" className="px-8 py-2 w-48"
disabled={ disabled={
isUploadingFile || isUploadingFile ||

View File

@ -26,7 +26,8 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
onRemoveFolder, onRemoveFolder,
setPresentingDocument, setPresentingDocument,
}) => { }) => {
const hasItems = folders.length > 0 || files.length > 0; const hasItems =
folders.length > 0 || files.length > 0 || uploadingFiles.length > 0;
const openFile = (file: FileResponse) => { const openFile = (file: FileResponse) => {
if (file.link_url) { if (file.link_url) {
window.open(file.link_url, "_blank"); window.open(file.link_url, "_blank");
@ -40,89 +41,143 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
return ( return (
<div className="h-full w-full flex flex-col"> <div className="h-full w-full flex flex-col">
<div className="flex items-center justify-between mb-3"> <div className="space-y-2.5 pb-2">
<h3 className="text-sm font-semibold text-neutral-800 dark:text-neutral-100"> {folders.length > 0 && (
Selected Items <div className="space-y-2.5">
</h3> {folders.map((folder: FolderResponse) => (
</div> <div key={folder.id} className="group flex items-center gap-2">
<ScrollArea className="h-[200px] flex-grow pr-1">
<div className="space-y-2.5">
{folders.length > 0 && (
<div className="space-y-2.5">
{folders.map((folder: FolderResponse) => (
<div key={folder.id} className="group flex items-center gap-2">
<div
className={cn(
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-100/80 border-neutral-200 hover:bg-neutral-200/60",
"dark:bg-neutral-800/80 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150"
)}
>
<div className="flex items-center min-w-0 flex-1">
<FolderIcon className="h-5 w-5 mr-2 text-black dark:text-black shrink-0 fill-black dark:fill-black" />
<span className="text-sm font-medium truncate text-neutral-800 dark:text-neutral-100">
{truncateString(folder.name, 34)}
</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => onRemoveFolder(folder)}
className={cn(
"bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100",
"h-6 w-6 p-0 rounded-full shrink-0",
"hover:text-neutral-700",
"dark:text-neutral-300 dark:hover:text-neutral-100",
"dark:focus:ring-1 dark:focus:ring-neutral-500",
"dark:active:bg-neutral-500 dark:active:text-white",
"transition-all duration-150 ease-in-out"
)}
aria-label={`Remove folder ${folder.name}`}
>
<X className="h-3 w-3 dark:text-neutral-200" />
</Button>
</div>
))}
</div>
)}
{files.length > 0 && (
<div className="space-y-2.5 ">
{files.map((file: FileResponse) => (
<div <div
key={file.id} className={cn(
className="group w-full flex items-center gap-2" "group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-100/80 border-neutral-200 hover:bg-neutral-200/60",
"dark:bg-neutral-800/80 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150"
)}
> >
<div <div className="flex items-center min-w-0 flex-1">
className={cn( <FolderIcon className="h-5 w-5 mr-2 text-black dark:text-black shrink-0 fill-black dark:fill-black" />
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-50 border-neutral-200 hover:bg-neutral-100", <span className="text-sm font-medium truncate text-neutral-800 dark:text-neutral-100">
"dark:bg-neutral-800/70 dark:border-neutral-700 dark:hover:bg-neutral-750", {truncateString(folder.name, 34)}
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600", </span>
"dark:active:bg-neutral-700 dark:active:border-neutral-600", </div>
"transition-colors duration-150", </div>
"cursor-pointer"
)} <Button
onClick={() => openFile(file)} variant="ghost"
> size="sm"
<div className="flex items-center min-w-0 flex-1"> onClick={() => onRemoveFolder(folder)}
{getFileIconFromFileNameAndLink(file.name, file.link_url)} className={cn(
<span className="text-sm truncate text-neutral-700 dark:text-neutral-200 ml-2.5"> "bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100",
{truncateString(file.name, 34)} "h-6 w-6 p-0 rounded-full shrink-0",
"hover:text-neutral-700",
"dark:text-neutral-300 dark:hover:text-neutral-100",
"dark:focus:ring-1 dark:focus:ring-neutral-500",
"dark:active:bg-neutral-500 dark:active:text-white",
"transition-all duration-150 ease-in-out"
)}
aria-label={`Remove folder ${folder.name}`}
>
<X className="h-3 w-3 dark:text-neutral-200" />
</Button>
</div>
))}
</div>
)}
{files.length > 0 && (
<div className="space-y-2.5 ">
{files.map((file: FileResponse) => (
<div
key={file.id}
className="group w-full flex items-center gap-2"
>
<div
className={cn(
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-50 border-neutral-200 hover:bg-neutral-100",
"dark:bg-neutral-800/70 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150",
"cursor-pointer"
)}
onClick={() => openFile(file)}
>
<div className="flex items-center min-w-0 flex-1">
{getFileIconFromFileNameAndLink(file.name, file.link_url)}
<span className="text-sm truncate text-neutral-700 dark:text-neutral-200 ml-2.5">
{truncateString(file.name, 34)}
</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => onRemoveFile(file)}
className={cn(
"bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100",
"h-6 w-6 p-0 rounded-full shrink-0",
"hover:text-neutral-700",
"dark:text-neutral-300 dark:hover:text-neutral-100",
"dark:focus:ring-1 dark:focus:ring-neutral-500",
"dark:active:bg-neutral-500 dark:active:text-white",
"transition-all duration-150 ease-in-out"
)}
aria-label={`Remove file ${file.name}`}
>
<X className="h-3 w-3 dark:text-neutral-200" />
</Button>
</div>
))}
</div>
)}
<div className="max-w-full space-y-2.5">
{uploadingFiles
.filter(
(uploadingFile) =>
!files.map((file) => file.name).includes(uploadingFile.name)
)
.map((uploadingFile, index) => (
<div key={index} className="mr-8 flex items-center gap-2">
<div
key={`uploading-${index}`}
className={cn(
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-50 border-neutral-200 hover:bg-neutral-100",
"dark:bg-neutral-800/70 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150",
"cursor-pointer"
)}
>
<div className="flex items-center min-w-0 flex-1">
<div className="flex items-center gap-2 min-w-0">
{uploadingFile.name.startsWith("http") ? (
<Loader2 className="w-4 h-4 animate-spin text-blue-500" />
) : (
<CircularProgress
progress={uploadingFile.progress}
size={18}
showPercentage={false}
/>
)}
<span className="truncate text-sm text-text-dark dark:text-text-dark">
{uploadingFile.name.startsWith("http")
? `${uploadingFile.name.substring(0, 30)}${
uploadingFile.name.length > 30 ? "..." : ""
}`
: truncateString(uploadingFile.name, 34)}
</span> </span>
</div> </div>
</div> </div>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => onRemoveFile(file)} // onClick={() => onRemoveFile(file)}
className={cn( className={cn(
"bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100", "bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100",
"h-6 w-6 p-0 rounded-full shrink-0", "h-6 w-6 p-0 rounded-full shrink-0",
@ -132,82 +187,20 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
"dark:active:bg-neutral-500 dark:active:text-white", "dark:active:bg-neutral-500 dark:active:text-white",
"transition-all duration-150 ease-in-out" "transition-all duration-150 ease-in-out"
)} )}
aria-label={`Remove file ${file.name}`} // aria-label={`Remove file ${file.name}`}
> >
<X className="h-3 w-3 dark:text-neutral-200" /> <X className="h-3 w-3 dark:text-neutral-200" />
</Button> </Button>
</div> </div>
))} </div>
</div> ))}
)}
<div className="max-w-full space-y-2.5">
{uploadingFiles
.filter(
(uploadingFile) =>
!files.map((file) => file.name).includes(uploadingFile.name)
)
.map((uploadingFile, index) => (
<div key={index} className="mr-8 flex items-center gap-2">
<div
key={`uploading-${index}`}
className={cn(
"group flex-1 flex items-center rounded-md border p-2.5",
"bg-neutral-50 border-neutral-200 hover:bg-neutral-100",
"dark:bg-neutral-800/70 dark:border-neutral-700 dark:hover:bg-neutral-750",
"dark:focus:ring-1 dark:focus:ring-neutral-500 dark:focus:border-neutral-600",
"dark:active:bg-neutral-700 dark:active:border-neutral-600",
"transition-colors duration-150",
"cursor-pointer"
)}
>
<div className="flex items-center min-w-0 flex-1">
<div className="flex items-center gap-2 min-w-0">
{uploadingFile.name.startsWith("http") ? (
<Loader2 className="w-4 h-4 animate-spin text-blue-500" />
) : (
<CircularProgress
progress={uploadingFile.progress}
size={18}
showPercentage={false}
/>
)}
<span className="truncate text-sm text-text-dark dark:text-text-dark">
{uploadingFile.name.startsWith("http")
? `${uploadingFile.name.substring(0, 30)}${
uploadingFile.name.length > 30 ? "..." : ""
}`
: truncateString(uploadingFile.name, 34)}
</span>
</div>
</div>
<Button
variant="ghost"
size="sm"
// onClick={() => onRemoveFile(file)}
className={cn(
"bg-transparent hover:bg-transparent opacity-0 group-hover:opacity-100",
"h-6 w-6 p-0 rounded-full shrink-0",
"hover:text-neutral-700",
"dark:text-neutral-300 dark:hover:text-neutral-100",
"dark:focus:ring-1 dark:focus:ring-neutral-500",
"dark:active:bg-neutral-500 dark:active:text-white",
"transition-all duration-150 ease-in-out"
)}
// aria-label={`Remove file ${file.name}`}
>
<X className="h-3 w-3 dark:text-neutral-200" />
</Button>
</div>
</div>
))}
</div>
{!hasItems && (
<div className="flex items-center justify-center h-24 text-sm text-neutral-500 dark:text-neutral-400 italic bg-neutral-50/50 dark:bg-neutral-800/30 rounded-md border border-neutral-200/50 dark:border-neutral-700/50">
No items selected
</div>
)}
</div> </div>
</ScrollArea> {!hasItems && (
<div className="flex items-center justify-center h-24 text-sm text-neutral-500 dark:text-neutral-400 italic bg-neutral-50/50 dark:bg-neutral-800/30 rounded-md border border-neutral-200/50 dark:border-neutral-700/50">
No items selected
</div>
)}
</div>
</div> </div>
); );
}; };

View File

@ -26,9 +26,7 @@ interface SharedFolderItemProps {
onClick: (folderId: number) => void; onClick: (folderId: number) => void;
description?: string; description?: string;
lastUpdated?: string; lastUpdated?: string;
onRename: () => void;
onDelete: () => void; onDelete: () => void;
onMove: () => void;
} }
export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({ export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({
@ -36,9 +34,7 @@ export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({
onClick, onClick,
description, description,
lastUpdated, lastUpdated,
onRename,
onDelete, onDelete,
onMove,
}) => { }) => {
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
@ -99,7 +95,7 @@ export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
variant="ghost" 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" : "" folder.id === -1 ? "!invisible pointer-events-none" : ""
}`} }`}
> >
@ -108,14 +104,6 @@ export const SharedFolderItem: React.FC<SharedFolderItemProps> = ({
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="!p-0 w-40"> <PopoverContent className="!p-0 w-40">
<div className="space-y-0"> <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}> <Button variant="menu" onClick={handleDeleteClick}>
<FiTrash className="h-4 w-4" /> <FiTrash className="h-4 w-4" />
Delete Delete

View File

@ -23,21 +23,21 @@ import { Modal } from "@/components/Modal";
import FunctionalHeader from "@/components/chat/Header"; import FunctionalHeader from "@/components/chat/Header";
import FixedLogo from "@/components/logo/FixedLogo"; import FixedLogo from "@/components/logo/FixedLogo";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Link from "next/link";
function BackToOnyxButton({ function BackToOnyxButton({
documentSidebarVisible, documentSidebarVisible,
}: { }: {
documentSidebarVisible: boolean; documentSidebarVisible: boolean;
}) { }) {
const router = useRouter();
const enterpriseSettings = useContext(SettingsContext)?.enterpriseSettings; const enterpriseSettings = useContext(SettingsContext)?.enterpriseSettings;
return ( return (
<div className="absolute bottom-0 bg-background w-full flex border-t border-border py-4"> <div className="absolute bottom-0 bg-background w-full flex border-t border-border py-4">
<div className="mx-auto"> <div className="mx-auto">
<Button onClick={() => router.push("/chat")}> <Link href="/chat">
Back to {enterpriseSettings?.application_name || "Onyx Chat"} Back to {enterpriseSettings?.application_name || "Onyx Chat"}
</Button> </Link>
</div> </div>
<div <div
style={{ transition: "width 0.30s ease-out" }} style={{ transition: "width 0.30s ease-out" }}

View File

@ -124,10 +124,16 @@ const StandardAnswersTableRow = ({
? `\`${standardAnswer.keyword}\`` ? `\`${standardAnswer.keyword}\``
: standardAnswer.keyword} : standardAnswer.keyword}
</ReactMarkdown>, </ReactMarkdown>,
<CustomCheckbox <div
key={`match_regex-${standardAnswer.id}`} 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 <ReactMarkdown
key={`answer-${standardAnswer.id}`} key={`answer-${standardAnswer.id}`}
className="prose dark:prose-invert" className="prose dark:prose-invert"
@ -290,8 +296,8 @@ const StandardAnswersTable = ({
))} ))}
</div> </div>
</div> </div>
<div className="mx-auto"> <div className="flex flex-col w-full mx-auto">
<Table className="w-full flex items-stretch"> <Table className="w-full">
<TableHeader> <TableHeader>
<TableRow> <TableRow>
{columns.map((column) => ( {columns.map((column) => (
@ -314,11 +320,13 @@ const StandardAnswersTable = ({
)} )}
</TableBody> </TableBody>
</Table> </Table>
{paginatedStandardAnswers.length === 0 && ( <div>
<div className="flex justify-center"> {paginatedStandardAnswers.length === 0 && (
<Text>No matching standard answers found...</Text> <div className="flex justify-center">
</div> <Text>No matching standard answers found...</Text>
)} </div>
)}
</div>
{paginatedStandardAnswers.length > 0 && ( {paginatedStandardAnswers.length > 0 && (
<> <>
<div className="mt-4"> <div className="mt-4">

View File

@ -144,51 +144,52 @@ export function WhitelabelingForm() {
placeholder="Custom name which will replace 'Onyx'" placeholder="Custom name which will replace 'Onyx'"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<div>
<Label className="mt-4">Custom Logo</Label>
<Label className="mt-4">Custom Logo</Label> {values.use_custom_logo ? (
<div className="mt-3">
<SubLabel>Current Custom Logo: </SubLabel>
<img
src={"/api/enterprise-settings/logo?u=" + Date.now()}
alt="logo"
style={{ objectFit: "contain" }}
className="w-32 h-32 mb-10 mt-4"
/>
{values.use_custom_logo ? ( <Button
<div className="mt-3"> variant="destructive"
<SubLabel>Current Custom Logo: </SubLabel> size="sm"
<img type="button"
src={"/api/enterprise-settings/logo?u=" + Date.now()} className="mb-8"
alt="logo" onClick={async () => {
style={{ objectFit: "contain" }} const valuesWithoutLogo = {
className="w-32 h-32 mb-10 mt-4" ...values,
/> use_custom_logo: false,
};
<Button await updateEnterpriseSettings(valuesWithoutLogo);
variant="destructive" setValues(valuesWithoutLogo);
size="sm" }}
type="button" >
className="mb-8" Delete
onClick={async () => { </Button>
const valuesWithoutLogo = {
...values,
use_custom_logo: false,
};
await updateEnterpriseSettings(valuesWithoutLogo);
setValues(valuesWithoutLogo);
}}
>
Delete
</Button>
<SubLabel>
Override the current custom logo by uploading a new image
below and clicking the Update button.
</SubLabel>
</div>
) : (
<SubLabel> <SubLabel>
Override the current custom logo by uploading a new image Specify your own logo to replace the standard Onyx logo.
below and clicking the Update button.
</SubLabel> </SubLabel>
</div> )}
) : (
<SubLabel>
Specify your own logo to replace the standard Onyx logo.
</SubLabel>
)}
<ImageUpload <ImageUpload
selectedFile={selectedLogo} selectedFile={selectedLogo}
setSelectedFile={setSelectedLogo} setSelectedFile={setSelectedLogo}
/> />
</div>
<Separator /> <Separator />

View File

@ -674,3 +674,7 @@ ul > li > p {
.animate-fadeIn { .animate-fadeIn {
animation: fadeIn 0.2s ease-out forwards; animation: fadeIn 0.2s ease-out forwards;
} }
.container {
margin-bottom: 1rem;
}

View File

@ -115,17 +115,17 @@ export const ConnectorMultiSelect = ({
<div className="flex flex-col w-full space-y-2 mb-4"> <div className="flex flex-col w-full space-y-2 mb-4">
{label && <Label className="text-base font-medium">{label}</Label>} {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 All documents indexed by the selected connectors will be part of this
document set. document set.
</p> </p>
<div className="relative"> <div className="relative">
<div <div
className={`flex items-center border border-input rounded-md border border-neutral-200 ${ className={`flex items-center border border-input rounded-md border-neutral-200 dark:border-neutral-700 ${
allConnectorsSelected ? "bg-neutral-50" : "" allConnectorsSelected ? "bg-neutral-50 dark:bg-neutral-800" : ""
} focus-within:ring-1 focus-within:ring-ring focus-within:border-neutral-400 transition-colors`} } 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 <input
ref={inputRef} ref={inputRef}
type="text" type="text"
@ -141,8 +141,10 @@ export const ConnectorMultiSelect = ({
}} }}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder={effectivePlaceholder} 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 ${ 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" : "" allConnectorsSelected
? "text-neutral-500 dark:text-neutral-400"
: ""
}`} }`}
disabled={isInputDisabled} disabled={isInputDisabled}
/> />
@ -151,10 +153,10 @@ export const ConnectorMultiSelect = ({
{open && !allConnectorsSelected && ( {open && !allConnectorsSelected && (
<div <div
ref={dropdownRef} 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 ? ( {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 {searchQuery
? "No matching connectors found" ? "No matching connectors found"
: "No more connectors available"} : "No more connectors available"}
@ -164,7 +166,7 @@ export const ConnectorMultiSelect = ({
{filteredUnselectedConnectors.map((connector) => ( {filteredUnselectedConnectors.map((connector) => (
<div <div
key={connector.cc_pair_id} 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)} onClick={() => selectConnector(connector.cc_pair_id)}
> >
<div className="flex items-center truncate mr-2"> <div className="flex items-center truncate mr-2">
@ -185,12 +187,12 @@ export const ConnectorMultiSelect = ({
</div> </div>
{selectedConnectors.length > 0 ? ( {selectedConnectors.length > 0 ? (
<div className="mt-3 "> <div className="mt-3">
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5">
{selectedConnectors.map((connector) => ( {selectedConnectors.map((connector) => (
<div <div
key={connector.cc_pair_id} 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 items-center overflow-hidden">
<div className="flex-shrink-0 text-xs"> <div className="flex-shrink-0 text-xs">
@ -204,7 +206,7 @@ export const ConnectorMultiSelect = ({
</div> </div>
</div> </div>
<button <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)} onClick={() => removeConnector(connector.cc_pair_id)}
aria-label="Remove connector" aria-label="Remove connector"
> >
@ -215,7 +217,7 @@ export const ConnectorMultiSelect = ({
</div> </div>
</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. No connectors selected. Search and select connectors above.
</div> </div>
)} )}
@ -224,7 +226,7 @@ export const ConnectorMultiSelect = ({
<ErrorMessage <ErrorMessage
name={name} name={name}
component="div" component="div"
className="text-red-500 text-xs mt-1" className="text-red-500 dark:text-red-400 text-xs mt-1"
/> />
)} )}
</div> </div>

View File

@ -92,8 +92,9 @@ export function Modal({
${className || ""} ${className || ""}
flex flex
flex-col flex-col
${heightOverride ? `h-${heightOverride}` : "max-h-[90vh]"} ${heightOverride ? `h-${heightOverride}` : "max-h-[90vh]"}
${hideOverflow ? "overflow-hidden" : "overflow-auto"} ${hideOverflow ? "overflow-hidden" : "overflow-visible"}
`} `}
> >
{onOutsideClick && !hideCloseButton && ( {onOutsideClick && !hideCloseButton && (

View File

@ -69,7 +69,7 @@ const MultiSelectDropdown = ({
}; };
return ( 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> <Label>{label}</Label>
{creatable ? ( {creatable ? (
<CreatableSelect <CreatableSelect

View File

@ -27,9 +27,9 @@ export function TokenDisplay({
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <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="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 <div
className={`absolute top-0 left-0 h-full rounded-full ${ className={` absolute top-0 left-0 h-full rounded-full ${
tokenPercentage >= 100 tokenPercentage >= 100
? "bg-yellow-500 dark:bg-yellow-600" ? "bg-yellow-500 dark:bg-yellow-600"
: "bg-green-500 dark:bg-green-600" : "bg-green-500 dark:bg-green-600"

View File

@ -379,18 +379,24 @@ export function ClientLayout({
}, },
] ]
: []), : []),
{ ...(!enableCloud
name: ( ? [
<div className="flex"> {
<FiBarChart2 name: (
className="text-text-700" <div className="flex">
size={18} <FiBarChart2
/> className="text-text-700"
<div className="ml-1">Custom Analytics</div> size={18}
</div> />
), <div className="ml-1">
link: "/admin/performance/custom-analytics", 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"> <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} /> <UserDropdown toggleUserSettings={toggleUserSettings} />
</div> </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} {children}
</div> </div>
</div> </div>

View File

@ -201,7 +201,6 @@ export function TextFormField({
maxWidth, maxWidth,
removeLabel, removeLabel,
min, min,
includeForgotPassword,
onChange, onChange,
width, width,
vertical, vertical,
@ -229,7 +228,6 @@ export function TextFormField({
explanationLink?: string; explanationLink?: string;
small?: boolean; small?: boolean;
min?: number; min?: number;
includeForgotPassword?: boolean;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void; onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
width?: string; width?: string;
vertical?: boolean; vertical?: boolean;
@ -339,14 +337,6 @@ export function TextFormField({
placeholder={placeholder} placeholder={placeholder}
autoComplete={autoCompleteDisabled ? "off" : undefined} 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> </div>
{explanationText && ( {explanationText && (

View File

@ -16,7 +16,6 @@ interface TextViewProps {
presentingDocument: MinimalOnyxDocument; presentingDocument: MinimalOnyxDocument;
onClose: () => void; onClose: () => void;
} }
export default function TextView({ export default function TextView({
presentingDocument, presentingDocument,
onClose, onClose,
@ -27,6 +26,13 @@ export default function TextView({
const [fileName, setFileName] = useState(""); const [fileName, setFileName] = useState("");
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [fileType, setFileType] = useState("application/octet-stream"); 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 // Detect if a given MIME type is one of the recognized markdown formats
const isMarkdownFormat = (mimeType: string): boolean => { const isMarkdownFormat = (mimeType: string): boolean => {
@ -63,6 +69,7 @@ export default function TextView({
}; };
const fetchFile = useCallback(async () => { const fetchFile = useCallback(async () => {
console.log("fetching file");
setIsLoading(true); setIsLoading(true);
const fileId = const fileId =
presentingDocument.document_id.split("__")[1] || presentingDocument.document_id.split("__")[1] ||
@ -107,13 +114,14 @@ export default function TextView({
// Keep the slight delay for a smoother loading experience // Keep the slight delay for a smoother loading experience
setTimeout(() => { setTimeout(() => {
setIsLoading(false); setIsLoading(false);
console.log("finished loading");
}, 1000); }, 1000);
} }
}, [presentingDocument]); }, [presentingDocument]);
useEffect(() => { useEffect(() => {
fetchFile(); fetchFile();
}, [fetchFile]); }, []);
const handleDownload = () => { const handleDownload = () => {
const link = document.createElement("a"); const link = document.createElement("a");

View File

@ -327,7 +327,7 @@ export function HorizontalFilters({
); );
return ( return (
<div> <div className="b">
<div className="flex gap-x-3"> <div className="flex gap-x-3">
<div className="w-52"> <div className="w-52">
<DateRangeSelector value={timeRange} onValueChange={setTimeRange} /> <DateRangeSelector value={timeRange} onValueChange={setTimeRange} />
@ -387,7 +387,7 @@ export function HorizontalFilters({
)} )}
</div> </div>
<div className="flex pb-4 mt-2 h-12"> <div className="flex mt-2">
<div className="flex flex-wrap gap-x-2"> <div className="flex flex-wrap gap-x-2">
{timeRange && timeRange.selectValue && ( {timeRange && timeRange.selectValue && (
<SelectedBubble onClick={() => setTimeRange(null)}> <SelectedBubble onClick={() => setTimeRange(null)}>

View File

@ -1303,7 +1303,7 @@ export const ProductboardIcon = ({
size = 16, size = 16,
className = defaultTailwindCSS, className = defaultTailwindCSS,
}: IconProps) => ( }: IconProps) => (
<LogoIcon size={size} className={className} src="/Productboard.webp" /> <LogoIcon size={size} className={className} src="/Productboard.png" />
); );
export const AzureIcon = ({ export const AzureIcon = ({

View File

@ -45,7 +45,7 @@ export default function CreateEntityModal({
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{trigger}</DialogTrigger> <DialogTrigger asChild>{trigger}</DialogTrigger>
<DialogContent className="sm:max-w-[425px]"> <DialogContent className="max-w-[95%] sm:max-w-[425px]">
<DialogHeader> <DialogHeader>
<DialogTitle>{title}</DialogTitle> <DialogTitle>{title}</DialogTitle>
</DialogHeader> </DialogHeader>

View File

@ -437,19 +437,20 @@ export function CompactDocumentCard({
url?: string; url?: string;
updatePresentingDocument: (document: OnyxDocument) => void; updatePresentingDocument: (document: OnyxDocument) => void;
}) { }) {
console.log("document", document);
return ( return (
<div <div
onClick={() => { onClick={() => {
openDocument(document, updatePresentingDocument); 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} {icon}
{(document.semantic_identifier || document.document_id).slice(0, 40)} <p className="gap-0 p-0 m-0 line-clamp-2">
{(document.semantic_identifier || document.document_id).length > 40 && {(document.semantic_identifier || document.document_id).slice(0, 40)}
"..."} {(document.semantic_identifier || document.document_id).length > 40 &&
"..."}
</p>
</div> </div>
{document.blurb && ( {document.blurb && (
<div className="text-xs mb-0 text-neutral-600 dark:text-neutral-300 line-clamp-2"> <div className="text-xs mb-0 text-neutral-600 dark:text-neutral-300 line-clamp-2">
@ -479,7 +480,7 @@ export function CompactQuestionCard({
return ( return (
<div <div
onClick={() => openQuestion(question)} 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"> <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 Question

View File

@ -9,6 +9,7 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { openDocument } from "@/lib/search/utils"; import { openDocument } from "@/lib/search/utils";
import { SubQuestionDetail } from "@/app/chat/interfaces"; import { SubQuestionDetail } from "@/app/chat/interfaces";
import { getFileIconFromFileNameAndLink } from "@/lib/assistantIconUtils";
export interface DocumentCardProps { export interface DocumentCardProps {
document: LoadedOnyxDocument; document: LoadedOnyxDocument;
@ -39,6 +40,13 @@ export function Citation({
if (!document_info && !question_info) { if (!document_info && !question_info) {
return <>{children}</>; return <>{children}</>;
} }
const icon = document_info?.document
? getFileIconFromFileNameAndLink(
document_info.document.semantic_identifier || "",
document_info.document.link || ""
)
: null;
return ( return (
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<Tooltip> <Tooltip>
@ -72,7 +80,7 @@ export function Citation({
<CompactDocumentCard <CompactDocumentCard
updatePresentingDocument={document_info.updatePresentingDocument} updatePresentingDocument={document_info.updatePresentingDocument}
url={document_info.url} url={document_info.url}
icon={document_info.icon} icon={icon}
document={document_info.document} document={document_info.document}
/> />
) : ( ) : (

View File

@ -112,7 +112,24 @@ export async function fetchChatData(searchParams: {
? `${fullUrl}?${searchParamsString}` ? `${fullUrl}?${searchParamsString}`
: fullUrl; : 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 { return {
redirect: `/auth/login?next=${encodeURIComponent(redirectUrl)}`, redirect: `/auth/login?next=${encodeURIComponent(redirectUrl)}`,
}; };

View File

@ -77,7 +77,8 @@ export const SERVER_SIDE_ONLY__CLOUD_ENABLED =
process.env.NEXT_PUBLIC_CLOUD_ENABLED?.toLowerCase() === "true"; process.env.NEXT_PUBLIC_CLOUD_ENABLED?.toLowerCase() === "true";
export const NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED = 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 = export const NEXT_PUBLIC_TEST_ENV =
process.env.NEXT_PUBLIC_TEST_ENV?.toLowerCase() === "true"; process.env.NEXT_PUBLIC_TEST_ENV?.toLowerCase() === "true";

View File

@ -130,12 +130,10 @@ export async function renameItem(
} }
export async function downloadItem(documentId: string): Promise<Blob> { export async function downloadItem(documentId: string): Promise<Blob> {
const response = await fetch( const fileId = documentId.split("__")[1] || documentId;
`/api/chat/file/${encodeURIComponent(documentId)}`, const response = await fetch(`/api/chat/file/${encodeURIComponent(fileId)}`, {
{ method: "GET",
method: "GET", });
}
);
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to fetch file"); throw new Error("Failed to fetch file");
} }