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(
persona_id: int,
creator_user_id: UUID | None,
user_ids: list[UUID] | None,
group_ids: list[int] | None,
db_session: Session,
@ -29,7 +30,7 @@ def make_persona_private(
user_ids_set = set(user_ids)
for user_id in user_ids_set:
db_session.add(Persona__User(persona_id=persona_id, user_id=user_id))
if user_id != creator_user_id:
create_notification(
user_id=user_id,
notif_type=NotificationType.PERSONA_SHARED,

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -69,7 +69,8 @@ def test_llm_configuration(
existing_provider = fetch_existing_llm_provider(
name=test_llm_request.name, db_session=db_session
)
if existing_provider:
# if an API key is not provided, use the existing provider's API key
if existing_provider and test_api_key is None:
test_api_key = existing_provider.api_key
# For this "testing" workflow, we do *not* need the actual `max_input_tokens`.

View File

@ -90,8 +90,25 @@ def get_folders(
db_session: Session = Depends(get_session),
) -> list[UserFolderSnapshot]:
user_id = user.id if user else None
folders = db_session.query(UserFolder).filter(UserFolder.user_id == user_id).all()
return [UserFolderSnapshot.from_model(folder) for folder in folders]
# Get folders that belong to the user or have the RECENT_DOCS_FOLDER_ID
folders = (
db_session.query(UserFolder)
.filter(
(UserFolder.user_id == user_id) | (UserFolder.id == RECENT_DOCS_FOLDER_ID)
)
.all()
)
# For each folder, filter files to only include those belonging to the current user
result = []
for folder in folders:
folder_snapshot = UserFolderSnapshot.from_model(folder)
folder_snapshot.files = [
file for file in folder_snapshot.files if file.user_id == user_id
]
result.append(folder_snapshot)
return result
@router.get("/user/folder/{folder_id}")
@ -103,13 +120,25 @@ def get_folder(
user_id = user.id if user else None
folder = (
db_session.query(UserFolder)
.filter(UserFolder.id == folder_id, UserFolder.user_id == user_id)
.filter(
UserFolder.id == folder_id,
(
(UserFolder.user_id == user_id)
| (UserFolder.id == RECENT_DOCS_FOLDER_ID)
),
)
.first()
)
if not folder:
raise HTTPException(status_code=404, detail="Folder not found")
return UserFolderSnapshot.from_model(folder)
folder_snapshot = UserFolderSnapshot.from_model(folder)
# Filter files to only include those belonging to the current user
folder_snapshot.files = [
file for file in folder_snapshot.files if file.user_id == user_id
]
return folder_snapshot
RECENT_DOCS_FOLDER_ID = -1

View File

@ -27,7 +27,15 @@ ONYX_REQUEST_ID_CONTEXTVAR: contextvars.ContextVar[str | None] = contextvars.Con
def get_current_tenant_id() -> str:
tenant_id = CURRENT_TENANT_ID_CONTEXTVAR.get()
if tenant_id is None:
import traceback
if not MULTI_TENANT:
return POSTGRES_DEFAULT_SCHEMA
raise RuntimeError("Tenant ID is not set. This should never happen.")
stack_trace = traceback.format_stack()
error_message = (
"Tenant ID is not set. This should never happen.\nStack trace:\n"
+ "".join(stack_trace)
)
raise RuntimeError(error_message)
return tenant_id

70
web/package-lock.json generated
View File

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

View File

@ -25,6 +25,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useAuthType } from "@/lib/hooks";
import { InfoIcon } from "lucide-react";
function parseJsonWithTrailingCommas(jsonString: string) {
// Regular expression to remove trailing commas before } or ]
@ -159,25 +160,14 @@ function ActionForm({
component="div"
className="mb-4 text-error text-sm"
/>
<div className="mt-4 text-sm bg-blue-50 p-4 rounded-md border border-blue-200">
<div className="mt-4 text-sm bg-blue-50 text-blue-700 dark:text-blue-300 dark:bg-blue-900 p-4 rounded-md border border-blue-200 dark:border-blue-800">
<Link
href="https://docs.onyx.app/tools/custom"
className="text-link hover:underline flex items-center"
target="_blank"
rel="noopener noreferrer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5 mr-2"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
clipRule="evenodd"
/>
</svg>
<InfoIcon className="w-4 h-4 mr-2 " />
Learn more about actions in our documentation
</Link>
</div>
@ -367,7 +357,7 @@ interface ToolFormValues {
}
const ToolSchema = Yup.object().shape({
definition: Yup.string().required("Tool definition is required"),
definition: Yup.string().required("Action definition is required"),
customHeaders: Yup.array()
.of(
Yup.object().shape({

View File

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

View File

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

View File

@ -232,7 +232,7 @@ export function AssistantEditor({
enabledToolsMap[tool.id] = personaCurrentToolIds.includes(tool.id);
});
const { selectedFiles, selectedFolders } = useDocumentsContext();
const { files, folders, refreshFolders } = useDocumentsContext();
const [showVisibilityWarning, setShowVisibilityWarning] = useState(false);
@ -393,19 +393,6 @@ export function AssistantEditor({
<BackButton />
</div>
)}
{filePickerModalOpen && (
<FilePickerModal
setPresentingDocument={setPresentingDocument}
isOpen={filePickerModalOpen}
onClose={() => {
setFilePickerModalOpen(false);
}}
onSave={() => {
setFilePickerModalOpen(false);
}}
buttonContent="Add to Assistant"
/>
)}
{presentingDocument && (
<TextView
@ -556,6 +543,8 @@ export function AssistantEditor({
// don't set groups if marked as public
const groups = values.is_public ? [] : values.selectedGroups;
const teamKnowledge = values.knowledge_source === "team_knowledge";
const submissionData: PersonaUpsertParameters = {
...values,
existing_prompt_id: existingPrompt?.id ?? null,
@ -573,8 +562,9 @@ export function AssistantEditor({
? new Date(values.search_start_date)
: null,
num_chunks: numChunks,
user_file_ids: selectedFiles.map((file) => file.id),
user_folder_ids: selectedFolders.map((folder) => folder.id),
document_set_ids: teamKnowledge ? values.document_set_ids : [],
user_file_ids: teamKnowledge ? [] : values.user_file_ids,
user_folder_ids: teamKnowledge ? [] : values.user_folder_ids,
};
let personaResponse;
@ -625,6 +615,7 @@ export function AssistantEditor({
}
await refreshAssistants();
await refreshFolders();
router.push(
isAdminPage
@ -656,7 +647,38 @@ export function AssistantEditor({
values.llm_model_version_override || defaultModelName || ""
);
// TODO: memoize this / make more efficient
const selectedFiles = files.filter((file) =>
values.user_file_ids.includes(file.id)
);
const selectedFolders = folders.filter((folder) =>
values.user_folder_ids.includes(folder.id)
);
return (
<>
{filePickerModalOpen && (
<FilePickerModal
setPresentingDocument={setPresentingDocument}
isOpen={filePickerModalOpen}
onClose={() => {
setFilePickerModalOpen(false);
}}
onSave={(selectedFiles, selectedFolders) => {
setFieldValue(
"user_file_ids",
selectedFiles.map((file) => file.id)
);
setFieldValue(
"user_folder_ids",
selectedFolders.map((folder) => folder.id)
);
setFilePickerModalOpen(false);
}}
buttonContent="Add to Assistant"
/>
)}
<Form className="w-full text-text-950 assistant-editor">
{/* Refresh starter messages when name or description changes */}
<p className="text-base font-normal text-2xl">
@ -701,7 +723,10 @@ export function AssistantEditor({
className="w-12 h-12 rounded-full object-cover"
/>
) : (
generateIdenticon((values.icon_shape || 0).toString(), 36)
generateIdenticon(
(values.icon_shape || 0).toString(),
36
)
)}
</div>
@ -741,7 +766,9 @@ export function AssistantEditor({
}}
>
<TrashIcon className="h-3 w-3" />
{removePersonaImage ? "Revert to Previous " : "Remove "}
{removePersonaImage
? "Revert to Previous "
: "Remove "}
Image
</Button>
)}
@ -759,7 +786,9 @@ export function AssistantEditor({
const newShape = generateRandomIconShape();
const randomColor =
colorOptions[
Math.floor(Math.random() * colorOptions.length)
Math.floor(
Math.random() * colorOptions.length
)
];
setFieldValue("icon_shape", newShape.encodedGrid);
setFieldValue("icon_color", randomColor);
@ -876,8 +905,9 @@ export function AssistantEditor({
{ccPairs.length === 0 && (
<TooltipContent side="top" align="center">
<p className="bg-background-900 max-w-[200px] text-sm rounded-lg p-1.5 text-white">
To use the Knowledge Action, you need to
have at least one Connector configured.
To use the Knowledge Action, you need
to have at least one Connector
configured.
</p>
</TooltipContent>
)}
@ -950,8 +980,8 @@ export function AssistantEditor({
Click below to add documents or folders from the
My Document feature
</SubLabel>
{(selectedFiles.length > 0 ||
selectedFolders.length > 0) && (
{(values.user_file_ids.length > 0 ||
values.user_folder_ids.length > 0) && (
<div className="flex flex-wrap mb-2 max-w-sm gap-2">
{selectedFiles.map((file) => (
<SourceChip
@ -972,6 +1002,7 @@ export function AssistantEditor({
</div>
)}
<button
type="button"
onClick={() => setFilePickerModalOpen(true)}
className="text-primary hover:underline"
>
@ -1027,7 +1058,9 @@ export function AssistantEditor({
if (index !== -1) {
arrayHelpers.remove(index);
} else {
arrayHelpers.push(documentSet.id);
arrayHelpers.push(
documentSet.id
);
}
}}
/>
@ -1102,7 +1135,9 @@ export function AssistantEditor({
<div className="-mt-2">
<div className="flex gap-x-2 mb-2 items-center">
<div className="block font-medium text-sm">Default Model</div>
<div className="block font-medium text-sm">
Default Model
</div>
</div>
<LLMSelector
llmProviders={llmProviders}
@ -1128,7 +1163,10 @@ export function AssistantEditor({
const { modelName, provider, name } =
destructureValue(selected);
if (modelName && name) {
setFieldValue("llm_model_version_override", modelName);
setFieldValue(
"llm_model_version_override",
modelName
);
setFieldValue("llm_model_provider_override", name);
}
}
@ -1177,7 +1215,10 @@ export function AssistantEditor({
name="is_public"
size="md"
onCheckedChange={(checked) => {
if (values.is_default_persona && !checked) {
if (
values.is_default_persona &&
!checked
) {
setShowVisibilityWarning(true);
} else {
setFieldValue("is_public", checked);
@ -1217,8 +1258,8 @@ export function AssistantEditor({
<div className="flex items-center text-warning mt-2">
<InfoIcon size={16} className="mr-2" />
<span className="text-sm">
Default persona must be public. Visibility has been
automatically set to organization public.
Default persona must be public. Visibility has
been automatically set to organization public.
</span>
</div>
)}
@ -1343,8 +1384,9 @@ export function AssistantEditor({
<SubLabel>
Sample messages that help users understand what this
assistant can do and how to interact with it effectively.
New input fields will appear automatically as you type.
assistant can do and how to interact with it
effectively. New input fields will appear automatically
as you type.
</SubLabel>
<div className="w-full">
@ -1419,9 +1461,8 @@ export function AssistantEditor({
(l) => l.name === option.value
);
if (label) {
const isSelected = values.label_ids.includes(
label.id
);
const isSelected =
values.label_ids.includes(label.id);
const newLabelIds = isSelected
? values.label_ids.filter(
(id: number) => id !== label.id
@ -1480,7 +1521,9 @@ export function AssistantEditor({
<div className="flex flex-col gap-y-4">
<div className="flex flex-col gap-y-4">
<h3 className="font-medium text-sm">Knowledge Options</h3>
<h3 className="font-medium text-sm">
Knowledge Options
</h3>
<div className="flex flex-col gap-y-4 ml-4">
<TextFormField
small={true}
@ -1578,6 +1621,7 @@ export function AssistantEditor({
)}
</div>
</Form>
</>
);
}}
</Formik>

View File

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

View File

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

View File

@ -168,7 +168,7 @@ export function Explorer({
return (
<div>
{popup}
<div className="justify-center py-2">
<div className="justify-center pt-2">
<div className="flex items-center w-full border-2 border-border rounded-lg px-4 py-2 focus-within:border-accent bg-background-search dark:bg-transparent">
<MagnifyingGlass />
<textarea

View File

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

View File

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

View File

@ -14,7 +14,7 @@ const Page = () => {
Authentication Error
</h2>
<p className="text-text-700 text-center">
We encountered an issue while attempting to log you in.
There was a problem with your login attempt.
</p>
<div className="bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800 rounded-lg p-4 shadow-sm">
<h3 className="text-red-800 dark:text-red-400 font-semibold mb-2">
@ -46,8 +46,11 @@ const Page = () => {
please reach out to your system administrator for assistance.
{NEXT_PUBLIC_CLOUD_ENABLED && (
<span className="block mt-1 text-blue-600">
A member of our team has been automatically notified about this
issue.
If you continue to experience problems please reach out to the
Onyx team at{" "}
<a href="mailto:support@onyx.app" className="text-blue-600">
support@onyx.app
</a>
</span>
)}
</p>

View File

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

View File

@ -29,12 +29,15 @@ export default function LoginPage({
useSendAuthRequiredMessage();
return (
<div className="flex flex-col w-full justify-center">
{authUrl && authTypeMetadata && (
{authUrl &&
authTypeMetadata &&
authTypeMetadata.authType !== "cloud" &&
// basic auth is handled below w/ the EmailPasswordForm
authTypeMetadata.authType !== "basic" && (
<>
<h2 className="text-center text-xl text-strong font-bold">
<LoginText />
</h2>
<SignInButton
authorizeUrl={authUrl}
authType={authTypeMetadata?.authType}
@ -43,23 +46,34 @@ export default function LoginPage({
)}
{authTypeMetadata?.authType === "cloud" && (
<div className="mt-4 w-full justify-center">
<div className="w-full justify-center">
<h2 className="text-center text-xl text-strong font-bold">
<LoginText />
</h2>
<EmailPasswordForm shouldVerify={true} nextUrl={nextUrl} />
{NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && (
<div className="flex mt-4 justify-between">
<Link
href="/auth/forgot-password"
className="ml-auto text-link font-medium"
>
Reset Password
</Link>
</div>
)}
{authUrl && authTypeMetadata && (
<>
<div className="flex items-center w-full my-4">
<div className="flex-grow border-t border-background-300"></div>
<span className="px-4 text-text-500">or</span>
<div className="flex-grow border-t border-background-300"></div>
</div>
<EmailPasswordForm shouldVerify={true} nextUrl={nextUrl} />
{NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && (
<div className="flex mt-4 justify-between">
<Link
href="/auth/forgot-password"
className="text-link font-medium"
>
Reset Password
</Link>
</div>
<SignInButton
authorizeUrl={authUrl}
authType={authTypeMetadata?.authType}
/>
</>
)}
</div>
)}

View File

@ -40,10 +40,19 @@ const Page = async (props: {
// if user is already logged in, take them to the main app page
if (currentUser && currentUser.is_active && !currentUser.is_anonymous_user) {
console.log("Login page: User is logged in, redirecting to chat", {
userId: currentUser.id,
is_active: currentUser.is_active,
is_anonymous: currentUser.is_anonymous_user,
});
if (authTypeMetadata?.requiresVerification && !currentUser.is_verified) {
return redirect("/auth/waiting-on-verification");
}
return redirect("/chat");
// Add a query parameter to indicate this is a redirect from login
// This will help prevent redirect loops
return redirect("/chat?from=login");
}
// get where to send the user to authenticate

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
isSignup
shouldVerify={authTypeMetadata?.requiresVerification}
nextUrl={nextUrl}
defaultEmail={defaultEmail}
/>
{cloud && authUrl && (
<div className="w-full justify-center">
<div className="flex items-center w-full my-4">
<div className="flex-grow border-t border-background-300"></div>
<span className="px-4 text-text-500">or</span>
<div className="flex-grow border-t border-background-300"></div>
</div>
<SignInButton authorizeUrl={authUrl} authType="cloud" />
</div>
)}
</div>
</>
</AuthFlowContainer>

View File

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

View File

@ -149,7 +149,7 @@ export const FolderDropdown = forwardRef<HTMLDivElement, FolderDropdownProps>(
ref={setNodeRef}
style={style}
{...attributes}
className="overflow-visible mt-2 w-full"
className="overflow-visible pt-2 w-full"
onDragOver={handleDragOver}
onDrop={handleDrop}
>
@ -159,13 +159,13 @@ export const FolderDropdown = forwardRef<HTMLDivElement, FolderDropdownProps>(
>
<div
ref={ref}
className="flex overflow-visible items-center w-full text-text-darker rounded-md p-1 relative sticky top-0"
className="flex overflow-visible items-center w-full text-text-darker rounded-md p-1 bg-background-sidebar dark:bg-[#000] relative sticky top-0"
style={{ zIndex: 10 - index }}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<button
className="flex overflow-hidden items-center flex-grow"
className="flex overflow-hidden bg-background-sidebar dark:bg-[#000] items-center flex-grow"
onClick={() => !isEditing && setIsOpen(!isOpen)}
{...(isEditing ? {} : listeners)}
>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
"use client";
import React, { useMemo, useState, useTransition } from "react";
import React, { useEffect, useMemo, useState, useTransition } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import {
Plus,
@ -68,11 +68,23 @@ export default function MyDocuments() {
const [sortDirection, setSortDirection] = useState<SortDirection>(
SortDirection.Descending
);
const pageLimit = 10;
const searchParams = useSearchParams();
const router = useRouter();
const { popup, setPopup } = usePopup();
const [isCreateFolderOpen, setIsCreateFolderOpen] = useState(false);
useEffect(() => {
const createFolder = searchParams.get("createFolder");
if (createFolder) {
setIsCreateFolderOpen(true);
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.delete("createFolder");
router.replace(`?${newSearchParams.toString()}`);
}
}, [searchParams]);
const [isPending, startTransition] = useTransition();
const [hoveredColumn, setHoveredColumn] = useState<SortType | null>(null);
@ -118,118 +130,22 @@ export default function MyDocuments() {
};
const handleDeleteItem = async (itemId: number, isFolder: boolean) => {
if (!isFolder) {
// For files, keep the old confirmation
const confirmDelete = window.confirm(
`Are you sure you want to delete this file?`
);
if (confirmDelete) {
try {
await deleteItem(itemId, isFolder);
setPopup({
message: `File deleted successfully`,
message: isFolder
? `Folder deleted successfully`
: `File deleted successfully`,
type: "success",
});
await refreshFolders();
} catch (error) {
console.error("Error deleting item:", error);
setPopup({
message: `Failed to delete file`,
message: `Failed to delete ${isFolder ? "folder" : "file"}`,
type: "error",
});
}
}
}
// If it's a folder, the SharedFolderItem component will handle it
};
const handleMoveItem = async (
itemId: number,
currentFolderId: number | null,
isFolder: boolean
) => {
const availableFolders = folders
.filter((folder) => folder.id !== itemId)
.map((folder) => `${folder.id}: ${folder.name}`)
.join("\n");
const promptMessage = `Enter the ID of the destination folder:\n\nAvailable folders:\n${availableFolders}\n\nEnter 0 to move to the root folder.`;
const destinationFolderId = prompt(promptMessage);
if (destinationFolderId !== null) {
const newFolderId = parseInt(destinationFolderId, 10);
if (isNaN(newFolderId)) {
setPopup({
message: "Invalid folder ID",
type: "error",
});
return;
}
try {
await moveItem(
itemId,
newFolderId === 0 ? null : newFolderId,
isFolder
);
setPopup({
message: `${
isFolder ? "Knowledge Group" : "File"
} moved successfully`,
type: "success",
});
await refreshFolders();
} catch (error) {
console.error("Error moving item:", error);
setPopup({
message: "Failed to move item",
type: "error",
});
}
}
};
const handleDownloadItem = async (documentId: string) => {
try {
await downloadItem(documentId);
} catch (error) {
console.error("Error downloading file:", error);
setPopup({
message: "Failed to download file",
type: "error",
});
}
};
const onRenameItem = async (
itemId: number,
currentName: string,
isFolder: boolean
) => {
const newName = prompt(
`Enter new name for ${isFolder ? "Knowledge Group" : "File"}:`,
currentName
);
if (newName && newName !== currentName) {
try {
await renameItem(itemId, newName, isFolder);
setPopup({
message: `${
isFolder ? "Knowledge Group" : "File"
} renamed successfully`,
type: "success",
});
await refreshFolders();
} catch (error) {
console.error("Error renaming item:", error);
setPopup({
message: `Failed to rename ${isFolder ? "Knowledge Group" : "File"}`,
type: "error",
});
}
}
};
const filteredFolders = useMemo(() => {
@ -438,11 +354,7 @@ export default function MyDocuments() {
onClick={handleFolderClick}
description={folder.description}
lastUpdated={folder.created_at}
onRename={() => onRenameItem(folder.id, folder.name, true)}
onDelete={() => handleDeleteItem(folder.id, true)}
onMove={() =>
handleMoveItem(folder.id, currentFolder, true)
}
/>
))}
</div>

View File

@ -588,29 +588,6 @@ export default function UserFolderContent({ folderId }: { folderId: number }) {
{/* Invalid file message */}
{/* Add a visual overlay when dragging files */}
{isDraggingOver && (
<div className="fixed inset-0 bg-neutral-950/10 backdrop-blur-sm z-50 pointer-events-none flex items-center justify-center transition-all duration-200 ease-in-out">
<div className="bg-white dark:bg-neutral-900 rounded-lg p-8 shadow-lg text-center border border-neutral-200 dark:border-neutral-800 max-w-md mx-auto">
<div className="bg-neutral-100 dark:bg-neutral-800 p-4 rounded-full w-20 h-20 mx-auto mb-5 flex items-center justify-center">
<Upload
className="w-10 h-10 text-neutral-600 dark:text-neutral-300"
strokeWidth={1.5}
/>
</div>
<h3 className="text-xl font-medium mb-2 text-neutral-900 dark:text-neutral-50">
Drop files to upload
</h3>
<p className="text-neutral-500 dark:text-neutral-400 text-sm">
Files will be uploaded to{" "}
<span className="font-medium text-neutral-900 dark:text-neutral-200">
{folderDetails?.name || "this folder"}
</span>
</p>
</div>
</div>
)}
<DeleteEntityModal
isOpen={isDeleteModalOpen}
onClose={() => setIsDeleteModalOpen(false)}
@ -634,9 +611,9 @@ export default function UserFolderContent({ folderId }: { folderId: number }) {
<div className="flex -mt-[1px] flex-col w-full">
<div className="flex items-center mb-3">
<nav className="flex text-lg gap-x-1 items-center">
<nav className="flex text-base md:text-lg gap-x-1 items-center">
<span
className="font-medium leading-tight tracking-tight text-lg text-neutral-800 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-100 cursor-pointer flex items-center text-base"
className="font-medium leading-tight tracking-tight text-neutral-800 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-100 cursor-pointer flex items-center"
onClick={handleBack}
>
My Documents

View File

@ -255,7 +255,7 @@ export const FileListItem: React.FC<FileListItemProps> = ({
<PopoverTrigger asChild>
<Button
variant="ghost"
className="group-hover:visible invisible h-8 w-8 p-0"
className="group-hover:visible mobile:visible invisible h-8 w-8 p-0"
>
<MoreHorizontal className="h-4 w-4" />
</Button>

View File

@ -104,7 +104,7 @@ const DraggableItem: React.FC<{
<div className="w-6 flex items-center justify-center shrink-0">
<div
className={`${
isSelected ? "" : "opacity-0 group-hover:opacity-100"
isSelected ? "" : "desktop:opacity-0 group-hover:opacity-100"
} transition-opacity duration-150`}
onClick={(e) => {
e.stopPropagation();
@ -199,7 +199,7 @@ const FilePickerFolderItem: React.FC<{
className={`transition-opacity duration-150 ${
isSelected || allFilesSelected
? "opacity-100"
: "opacity-0 group-hover:opacity-100"
: "desktop:opacity-0 group-hover:opacity-100"
}`}
onClick={(e) => {
e.preventDefault();
@ -276,7 +276,10 @@ const FilePickerFolderItem: React.FC<{
export interface FilePickerModalProps {
isOpen: boolean;
onClose: () => void;
onSave: () => void;
onSave: (
selectedFiles: FileResponse[],
selectedFolders: FolderResponse[]
) => void;
buttonContent: string;
setPresentingDocument: (onyxDocument: MinimalOnyxDocument) => void;
}
@ -323,8 +326,6 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
createFileFromLink,
} = useDocumentsContext();
const router = useRouter();
const [linkUrl, setLinkUrl] = useState("");
const [isCreatingFileFromLink, setIsCreatingFileFromLink] = useState(false);
const [isUploadingFile, setIsUploadingFile] = useState(false);
@ -395,12 +396,6 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
}
}, [isOpen, selectedFiles, selectedFolders]);
useEffect(() => {
if (isOpen) {
refreshFolders();
}
}, [isOpen, refreshFolders]);
useEffect(() => {
if (currentFolder) {
if (currentFolder === -1) {
@ -1087,7 +1082,7 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
}
>
<div className="h-[calc(70vh-5rem)] flex overflow-visible flex-col">
<div className="grid overflow-x-visible h-full overflow-y-hidden flex-1 w-full divide-x divide-neutral-200 dark:divide-neutral-700 grid-cols-2">
<div className="grid overflow-x-visible h-full overflow-y-hidden flex-1 w-full divide-x divide-neutral-200 dark:divide-neutral-700 desktop:grid-cols-2">
<div className="w-full h-full pb-4 overflow-hidden ">
<div className="px-6 sticky flex flex-col gap-y-2 z-[1000] top-0 mb-2 flex gap-x-2 w-full pr-4">
<div className="w-full relative">
@ -1251,16 +1246,16 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
) : folders.length > 0 ? (
<div className="flex-grow overflow-y-auto px-4">
<p className="text-text-subtle dark:text-neutral-400">
No groups found
No folders found
</p>
</div>
) : (
<div className="flex-grow flex-col overflow-y-auto px-4 flex items-start justify-start gap-y-2">
<p className="text-sm text-muted-foreground dark:text-neutral-400">
No groups found
No folders found
</p>
<a
href="/chat/my-documents"
href="/chat/my-documents?createFolder=true"
className="inline-flex items-center text-sm justify-center text-neutral-600 dark:text-neutral-400 hover:underline"
>
<FolderIcon className="mr-2 h-4 w-4" />
@ -1270,14 +1265,20 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
)}
</div>
<div
className={`w-full h-full flex flex-col ${
className={`mobile:hidden overflow-y-auto w-full h-full flex flex-col ${
isHoveringRight ? "bg-neutral-100 dark:bg-neutral-800/30" : ""
}`}
onDragEnter={() => setIsHoveringRight(true)}
onDragLeave={() => setIsHoveringRight(false)}
>
<div className="px-5 pb-5 flex-1 flex flex-col">
<div className="shrink default-scrollbar flex h-full overflow-y-auto mb-3">
<div className="px-5 h-full flex flex-col">
{/* Top section: scrollable, takes remaining space */}
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-neutral-800 dark:text-neutral-100">
Selected Items
</h3>
</div>
<div className="flex-1 min-h-0 overflow-y-auto">
<SelectedItemsList
uploadingFiles={uploadingFiles}
setPresentingDocument={setPresentingDocument}
@ -1288,16 +1289,14 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
/>
</div>
<div className="flex flex-col space-y-3">
<div className="flex flex-col space-y-2">
{/* Bottom section: fixed height, doesn't flex */}
<div className="flex-none py-2">
<FileUploadSection
disabled={isUploadingFile || isCreatingFileFromLink}
onUpload={(files: File[]) => {
setIsUploadingFile(true);
setUploadStartTime(Date.now()); // Record start time
// Add files to uploading files state
// Start the refresh interval to simulate progress
startRefreshInterval();
@ -1326,8 +1325,10 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
startRefreshInterval();
try {
const response: FileResponse[] =
await createFileFromLink(url, -1);
const response: FileResponse[] = await createFileFromLink(
url,
-1
);
if (response.length > 0) {
// Extract domain from URL to help with detection
@ -1354,7 +1355,6 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
</div>
</div>
</div>
</div>
<div className="px-5 pt-4 border-t border-neutral-200 dark:border-neutral-700">
<div className="flex flex-col items-center justify-center py-2 space-y-4">
<div className="flex items-center gap-3">
@ -1375,7 +1375,10 @@ export const FilePickerModal: React.FC<FilePickerModalProps> = ({
<TooltipTrigger asChild>
<div>
<Button
onClick={onSave}
type="button"
onClick={() =>
onSave(selectedItems.files, selectedItems.folders)
}
className="px-8 py-2 w-48"
disabled={
isUploadingFile ||

View File

@ -26,7 +26,8 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
onRemoveFolder,
setPresentingDocument,
}) => {
const hasItems = folders.length > 0 || files.length > 0;
const hasItems =
folders.length > 0 || files.length > 0 || uploadingFiles.length > 0;
const openFile = (file: FileResponse) => {
if (file.link_url) {
window.open(file.link_url, "_blank");
@ -40,14 +41,7 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
return (
<div className="h-full w-full flex flex-col">
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold text-neutral-800 dark:text-neutral-100">
Selected Items
</h3>
</div>
<ScrollArea className="h-[200px] flex-grow pr-1">
<div className="space-y-2.5">
<div className="space-y-2.5 pb-2">
{folders.length > 0 && (
<div className="space-y-2.5">
{folders.map((folder: FolderResponse) => (
@ -207,7 +201,6 @@ export const SelectedItemsList: React.FC<SelectedItemsListProps> = ({
</div>
)}
</div>
</ScrollArea>
</div>
);
};

View File

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

View File

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

View File

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

View File

@ -144,7 +144,7 @@ export function WhitelabelingForm() {
placeholder="Custom name which will replace 'Onyx'"
disabled={isSubmitting}
/>
<div>
<Label className="mt-4">Custom Logo</Label>
{values.use_custom_logo ? (
@ -189,6 +189,7 @@ export function WhitelabelingForm() {
selectedFile={selectedLogo}
setSelectedFile={setSelectedLogo}
/>
</div>
<Separator />

View File

@ -674,3 +674,7 @@ ul > li > p {
.animate-fadeIn {
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">
{label && <Label className="text-base font-medium">{label}</Label>}
<p className="text-xs text-neutral-500 ">
<p className="text-xs text-neutral-500 dark:text-neutral-400">
All documents indexed by the selected connectors will be part of this
document set.
</p>
<div className="relative">
<div
className={`flex items-center border border-input rounded-md border border-neutral-200 ${
allConnectorsSelected ? "bg-neutral-50" : ""
} focus-within:ring-1 focus-within:ring-ring focus-within:border-neutral-400 transition-colors`}
className={`flex items-center border border-input rounded-md border-neutral-200 dark:border-neutral-700 ${
allConnectorsSelected ? "bg-neutral-50 dark:bg-neutral-800" : ""
} focus-within:ring-1 focus-within:ring-ring focus-within:border-neutral-400 dark:focus-within:border-neutral-500 transition-colors`}
>
<Search className="absolute left-3 h-4 w-4 text-neutral-500" />
<Search className="absolute left-3 h-4 w-4 text-neutral-500 dark:text-neutral-400" />
<input
ref={inputRef}
type="text"
@ -141,8 +141,10 @@ export const ConnectorMultiSelect = ({
}}
onKeyDown={handleKeyDown}
placeholder={effectivePlaceholder}
className={`h-9 w-full pl-9 pr-10 py-2 bg-transparent text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50 ${
allConnectorsSelected ? "text-neutral-500" : ""
className={`h-9 w-full pl-9 pr-10 py-2 bg-transparent dark:bg-transparent text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50 ${
allConnectorsSelected
? "text-neutral-500 dark:text-neutral-400"
: ""
}`}
disabled={isInputDisabled}
/>
@ -151,10 +153,10 @@ export const ConnectorMultiSelect = ({
{open && !allConnectorsSelected && (
<div
ref={dropdownRef}
className="absolute z-50 w-full mt-1 rounded-md border border-neutral-200 bg-white shadow-md default-scrollbar max-h-[300px] overflow-auto"
className="absolute z-50 w-full mt-1 rounded-md border border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-900 shadow-md default-scrollbar max-h-[300px] overflow-auto"
>
{filteredUnselectedConnectors.length === 0 ? (
<div className="py-4 text-center text-xs text-neutral-500">
<div className="py-4 text-center text-xs text-neutral-500 dark:text-neutral-400">
{searchQuery
? "No matching connectors found"
: "No more connectors available"}
@ -164,7 +166,7 @@ export const ConnectorMultiSelect = ({
{filteredUnselectedConnectors.map((connector) => (
<div
key={connector.cc_pair_id}
className="flex items-center justify-between py-2 px-3 cursor-pointer hover:bg-neutral-50 text-xs"
className="flex items-center justify-between py-2 px-3 cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-800 text-xs"
onClick={() => selectConnector(connector.cc_pair_id)}
>
<div className="flex items-center truncate mr-2">
@ -185,12 +187,12 @@ export const ConnectorMultiSelect = ({
</div>
{selectedConnectors.length > 0 ? (
<div className="mt-3 ">
<div className="mt-3">
<div className="flex flex-wrap gap-1.5">
{selectedConnectors.map((connector) => (
<div
key={connector.cc_pair_id}
className="flex items-center bg-white rounded-md border border-neutral-300 transition-all px-2 py-1 max-w-full group text-xs"
className="flex items-center bg-white dark:bg-neutral-800 rounded-md border border-neutral-300 dark:border-neutral-700 transition-all px-2 py-1 max-w-full group text-xs"
>
<div className="flex items-center overflow-hidden">
<div className="flex-shrink-0 text-xs">
@ -204,7 +206,7 @@ export const ConnectorMultiSelect = ({
</div>
</div>
<button
className="ml-1 flex-shrink-0 rounded-full w-4 h-4 flex items-center justify-center bg-neutral-100 text-neutral-500 hover:bg-neutral-200 hover:text-neutral-700 transition-colors group-hover:bg-neutral-200"
className="ml-1 flex-shrink-0 rounded-full w-4 h-4 flex items-center justify-center bg-neutral-100 dark:bg-neutral-700 text-neutral-500 dark:text-neutral-400 hover:bg-neutral-200 dark:hover:bg-neutral-600 hover:text-neutral-700 dark:hover:text-neutral-300 transition-colors group-hover:bg-neutral-200 dark:group-hover:bg-neutral-600"
onClick={() => removeConnector(connector.cc_pair_id)}
aria-label="Remove connector"
>
@ -215,7 +217,7 @@ export const ConnectorMultiSelect = ({
</div>
</div>
) : (
<div className="mt-3 p-3 border border-dashed border-neutral-300 rounded-md bg-neutral-50 text-neutral-500 text-xs">
<div className="mt-3 p-3 border border-dashed border-neutral-300 dark:border-neutral-700 rounded-md bg-neutral-50 dark:bg-neutral-800 text-neutral-500 dark:text-neutral-400 text-xs">
No connectors selected. Search and select connectors above.
</div>
)}
@ -224,7 +226,7 @@ export const ConnectorMultiSelect = ({
<ErrorMessage
name={name}
component="div"
className="text-red-500 text-xs mt-1"
className="text-red-500 dark:text-red-400 text-xs mt-1"
/>
)}
</div>

View File

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

View File

@ -69,7 +69,7 @@ const MultiSelectDropdown = ({
};
return (
<div className="flex flex-col space-y-4 mb-4">
<div className="flex flex-col text-white space-y-4 mb-4">
<Label>{label}</Label>
{creatable ? (
<CreatableSelect

View File

@ -27,9 +27,9 @@ export function TokenDisplay({
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center space-x-3 bg-neutral-100 dark:bg-neutral-800 rounded-full px-4 py-1.5">
<div className="relative w-36 h-2 bg-neutral-200 dark:bg-neutral-700 rounded-full overflow-hidden">
<div className="hidden sm:block relative w-24 h-2 bg-neutral-200 dark:bg-neutral-700 rounded-full overflow-hidden">
<div
className={`absolute top-0 left-0 h-full rounded-full ${
className={` absolute top-0 left-0 h-full rounded-full ${
tokenPercentage >= 100
? "bg-yellow-500 dark:bg-yellow-600"
: "bg-green-500 dark:bg-green-600"

View File

@ -379,6 +379,8 @@ export function ClientLayout({
},
]
: []),
...(!enableCloud
? [
{
name: (
<div className="flex">
@ -386,11 +388,15 @@ export function ClientLayout({
className="text-text-700"
size={18}
/>
<div className="ml-1">Custom Analytics</div>
<div className="ml-1">
Custom Analytics
</div>
</div>
),
link: "/admin/performance/custom-analytics",
},
]
: []),
],
},
]
@ -453,7 +459,7 @@ export function ClientLayout({
<div className="fixed left-0 gap-x-4 px-4 top-4 h-8 px-0 mb-auto w-full items-start flex justify-end">
<UserDropdown toggleUserSettings={toggleUserSettings} />
</div>
<div className="pt-20 flex w-full overflow-y-auto overflow-x-hidden h-full px-4 md:px-12">
<div className="pt-20 pb-4 flex w-full overflow-y-auto overflow-x-hidden h-full px-4 md:px-12">
{children}
</div>
</div>

View File

@ -201,7 +201,6 @@ export function TextFormField({
maxWidth,
removeLabel,
min,
includeForgotPassword,
onChange,
width,
vertical,
@ -229,7 +228,6 @@ export function TextFormField({
explanationLink?: string;
small?: boolean;
min?: number;
includeForgotPassword?: boolean;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
width?: string;
vertical?: boolean;
@ -339,14 +337,6 @@ export function TextFormField({
placeholder={placeholder}
autoComplete={autoCompleteDisabled ? "off" : undefined}
/>
{includeForgotPassword && (
<Link
href="/auth/forgot-password"
className="absolute right-3 top-1/2 mt-[3px] transform -translate-y-1/2 text-xs text-blue-500 cursor-pointer"
>
Forgot password?
</Link>
)}
</div>
{explanationText && (

View File

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

View File

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

View File

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

View File

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

View File

@ -437,19 +437,20 @@ export function CompactDocumentCard({
url?: string;
updatePresentingDocument: (document: OnyxDocument) => void;
}) {
console.log("document", document);
return (
<div
onClick={() => {
openDocument(document, updatePresentingDocument);
}}
className="max-w-[200px] gap-y-0 cursor-pointer pb-0 pt-0 mt-0 flex gap-y-0 flex-col content-start items-start gap-0 "
className="max-w-[250px] gap-y-1 cursor-pointer pb-0 pt-0 mt-0 flex gap-y-0 flex-col content-start items-start gap-0 "
>
<div className="text-sm !pb-0 !mb-0 font-semibold flex items-center gap-x-1 text-text-900 pt-0 mt-0 truncate w-full">
<div className="text-sm flex gap-x-2 !pb-0 !mb-0 font-semibold flex items-center gap-x-1 text-text-900 pt-0 mt-0 w-full">
{icon}
<p className="gap-0 p-0 m-0 line-clamp-2">
{(document.semantic_identifier || document.document_id).slice(0, 40)}
{(document.semantic_identifier || document.document_id).length > 40 &&
"..."}
</p>
</div>
{document.blurb && (
<div className="text-xs mb-0 text-neutral-600 dark:text-neutral-300 line-clamp-2">
@ -479,7 +480,7 @@ export function CompactQuestionCard({
return (
<div
onClick={() => openQuestion(question)}
className="max-w-[250px] gap-y-0 cursor-pointer pb-0 pt-0 mt-0 flex gap-y-0 flex-col content-start items-start gap-0"
className="max-w-[350px] gap-y-1 cursor-pointer pb-0 pt-0 mt-0 flex gap-y-0 flex-col content-start items-start gap-0"
>
<div className="text-sm !pb-0 !mb-0 font-semibold flex items-center gap-x-1 text-text-900 pt-0 mt-0 truncate w-full">
Question

View File

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

View File

@ -112,7 +112,24 @@ export async function fetchChatData(searchParams: {
? `${fullUrl}?${searchParamsString}`
: fullUrl;
if (!NEXT_PUBLIC_ENABLE_CHROME_EXTENSION) {
// Check the referrer to prevent redirect loops
const referrer = headersList.get("referer") || "";
const isComingFromLogin = referrer.includes("/auth/login");
// Also check for the from=login query parameter
const isRedirectedFromLogin = searchParams["from"] === "login";
console.log(
`Auth check: authDisabled=${authDisabled}, user=${!!user}, referrer=${referrer}, fromLogin=${isRedirectedFromLogin}`
);
// Only redirect if we're not already coming from the login page
if (
!NEXT_PUBLIC_ENABLE_CHROME_EXTENSION &&
!isComingFromLogin &&
!isRedirectedFromLogin
) {
console.log("Redirecting to login from chat page");
return {
redirect: `/auth/login?next=${encodeURIComponent(redirectUrl)}`,
};

View File

@ -77,7 +77,8 @@ export const SERVER_SIDE_ONLY__CLOUD_ENABLED =
process.env.NEXT_PUBLIC_CLOUD_ENABLED?.toLowerCase() === "true";
export const NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED =
process.env.NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED?.toLowerCase() === "true";
process.env.NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED?.toLowerCase() === "true" &&
!NEXT_PUBLIC_CLOUD_ENABLED;
export const NEXT_PUBLIC_TEST_ENV =
process.env.NEXT_PUBLIC_TEST_ENV?.toLowerCase() === "true";

View File

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