diff --git a/backend/ee/onyx/db/persona.py b/backend/ee/onyx/db/persona.py index 9fab07a58..40e7bb9aa 100644 --- a/backend/ee/onyx/db/persona.py +++ b/backend/ee/onyx/db/persona.py @@ -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,15 +30,15 @@ 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)) - - create_notification( - user_id=user_id, - notif_type=NotificationType.PERSONA_SHARED, - db_session=db_session, - additional_data=PersonaSharedNotificationData( - persona_id=persona_id, - ).model_dump(), - ) + if user_id != creator_user_id: + create_notification( + user_id=user_id, + notif_type=NotificationType.PERSONA_SHARED, + db_session=db_session, + additional_data=PersonaSharedNotificationData( + persona_id=persona_id, + ).model_dump(), + ) if group_ids: group_ids_set = set(group_ids) diff --git a/backend/onyx/agents/agent_search/basic/utils.py b/backend/onyx/agents/agent_search/basic/utils.py index 51641adbf..cc0af4a95 100644 --- a/backend/onyx/agents/agent_search/basic/utils.py +++ b/backend/onyx/agents/agent_search/basic/utils.py @@ -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 diff --git a/backend/onyx/agents/agent_search/run_graph.py b/backend/onyx/agents/agent_search/run_graph.py index 5dea5924e..add734e1e 100644 --- a/backend/onyx/agents/agent_search/run_graph.py +++ b/backend/onyx/agents/agent_search/run_graph.py @@ -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: diff --git a/backend/onyx/background/celery/tasks/tenant_provisioning/tasks.py b/backend/onyx/background/celery/tasks/tenant_provisioning/tasks.py index cb8605b80..e182f96f6 100644 --- a/backend/onyx/background/celery/tasks/tenant_provisioning/tasks.py +++ b/backend/onyx/background/celery/tasks/tenant_provisioning/tasks.py @@ -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, ) diff --git a/backend/onyx/chat/answer.py b/backend/onyx/chat/answer.py index c624b9b0e..bf3e72d59 100644 --- a/backend/onyx/chat/answer.py +++ b/backend/onyx/chat/answer.py @@ -167,7 +167,6 @@ class Answer: break processed_stream.append(packet) yield packet - self._processed_stream = processed_stream @property diff --git a/backend/onyx/configs/constants.py b/backend/onyx/configs/constants.py index d21a51881..c76710d09 100644 --- a/backend/onyx/configs/constants.py +++ b/backend/onyx/configs/constants.py @@ -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" diff --git a/backend/onyx/db/persona.py b/backend/onyx/db/persona.py index bf54ad09b..d0d7b6da8 100644 --- a/backend/onyx/db/persona.py +++ b/backend/onyx/db/persona.py @@ -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,15 +179,15 @@ def make_persona_private( for user_uuid in user_ids: db_session.add(Persona__User(persona_id=persona_id, user_id=user_uuid)) - - create_notification( - user_id=user_uuid, - notif_type=NotificationType.PERSONA_SHARED, - db_session=db_session, - additional_data=PersonaSharedNotificationData( - persona_id=persona_id, - ).model_dump(), - ) + if user_uuid != creator_user_id: + create_notification( + user_id=user_uuid, + notif_type=NotificationType.PERSONA_SHARED, + db_session=db_session, + additional_data=PersonaSharedNotificationData( + persona_id=persona_id, + ).model_dump(), + ) db_session.commit() @@ -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) diff --git a/backend/onyx/server/manage/llm/api.py b/backend/onyx/server/manage/llm/api.py index 5ce350839..91373aa52 100644 --- a/backend/onyx/server/manage/llm/api.py +++ b/backend/onyx/server/manage/llm/api.py @@ -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`. diff --git a/backend/onyx/server/user_documents/api.py b/backend/onyx/server/user_documents/api.py index 809510c07..731ae954c 100644 --- a/backend/onyx/server/user_documents/api.py +++ b/backend/onyx/server/user_documents/api.py @@ -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 diff --git a/backend/shared_configs/contextvars.py b/backend/shared_configs/contextvars.py index 69eb8d33e..efd9f5174 100644 --- a/backend/shared_configs/contextvars.py +++ b/backend/shared_configs/contextvars.py @@ -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 diff --git a/web/package-lock.json b/web/package-lock.json index 87b6573fb..abc2e8a53 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -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", diff --git a/web/src/app/admin/actions/ActionEditor.tsx b/web/src/app/admin/actions/ActionEditor.tsx index ccc6b199f..9d896f7b9 100644 --- a/web/src/app/admin/actions/ActionEditor.tsx +++ b/web/src/app/admin/actions/ActionEditor.tsx @@ -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" /> -
+
- - - + Learn more about actions in our documentation
@@ -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({ diff --git a/web/src/app/admin/actions/edit/[toolId]/page.tsx b/web/src/app/admin/actions/edit/[toolId]/page.tsx index 1b992555a..46e5cf7b8 100644 --- a/web/src/app/admin/actions/edit/[toolId]/page.tsx +++ b/web/src/app/admin/actions/edit/[toolId]/page.tsx @@ -27,15 +27,17 @@ export default async function Page(props: { ); } else { body = ( -
+
- Delete Tool - Click the button below to permanently delete this tool. + Delete Action + + Click the button below to permanently delete this action. +
@@ -50,7 +52,7 @@ export default async function Page(props: { } /> diff --git a/web/src/app/admin/api-key/page.tsx b/web/src/app/admin/api-key/page.tsx index e173c4bcc..8f0be94c0 100644 --- a/web/src/app/admin/api-key/page.tsx +++ b/web/src/app/admin/api-key/page.tsx @@ -41,11 +41,8 @@ function NewApiKeyModal({ const [copyClicked, setCopyClicked] = useState(false); return ( - +
-
- New API Key -
Make sure you copy your new API key. You won’t be able to see this diff --git a/web/src/app/admin/assistants/AssistantEditor.tsx b/web/src/app/admin/assistants/AssistantEditor.tsx index ab3a8452f..b5b389722 100644 --- a/web/src/app/admin/assistants/AssistantEditor.tsx +++ b/web/src/app/admin/assistants/AssistantEditor.tsx @@ -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({
)} - {filePickerModalOpen && ( - { - setFilePickerModalOpen(false); - }} - onSave={() => { - setFilePickerModalOpen(false); - }} - buttonContent="Add to Assistant" - /> - )} {presentingDocument && ( 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,928 +647,981 @@ export function AssistantEditor({ values.llm_model_version_override || defaultModelName || "" ); - return ( -
- {/* Refresh starter messages when name or description changes */} -

- {existingPersona ? ( - <> - Edit assistant {existingPersona.name} - - ) : ( - "Create an Assistant" - )} -

-
- -
-
- Assistant Icon -
-
- - The icon that will visually represent your Assistant - -
-
- {values.uploaded_image ? ( - Uploaded assistant icon - ) : existingPersona?.uploaded_image_id && - !removePersonaImage ? ( - Uploaded assistant icon - ) : ( - generateIdenticon((values.icon_shape || 0).toString(), 36) - )} -
+ // TODO: memoize this / make more efficient + const selectedFiles = files.filter((file) => + values.user_file_ids.includes(file.id) + ); -
- + {values.uploaded_image ? ( + Uploaded assistant icon + ) : existingPersona?.uploaded_image_id && + !removePersonaImage ? ( + Uploaded assistant icon + ) : ( + generateIdenticon( + (values.icon_shape || 0).toString(), + 36 + ) + )} +
- {values.uploaded_image && ( +
- )} - {!values.uploaded_image && - (!existingPersona?.uploaded_image_id || - removePersonaImage) && ( - - )} - - {existingPersona?.uploaded_image_id && - removePersonaImage && - !values.uploaded_image && ( + {values.uploaded_image && ( - )} - - {existingPersona?.uploaded_image_id && - !removePersonaImage && - !values.uploaded_image && ( - )} -
-
-
- - - - - - - - -
-
- {searchTool && ( - <> - -
-
-
-

- Knowledge -

-
- - - -
- { - setFieldValue("num_chunks", null); - toggleToolInValues(searchTool.id); - }} - name={`enabled_tools_map.${searchTool.id}`} - disabled={ccPairs.length === 0} - /> -
-
- - {ccPairs.length === 0 && ( - -

- To use the Knowledge Action, you need to - have at least one Connector configured. -

-
- )} -
-
-
-
-
-
- - )} - - {searchTool && values.enabled_tools_map[searchTool.id] && ( -
- {canShowKnowledgeSource && ( - <> -
-
-
- setFieldValue( - "knowledge_source", - "team_knowledge" + {!values.uploaded_image && + (!existingPersona?.uploaded_image_id || + removePersonaImage) && ( +
- -
- setFieldValue( - "knowledge_source", - "user_files" - ) - } - > -
- -
-

- User Knowledge -

-
-
-
- - )} - - {values.knowledge_source === "user_files" && - !existingPersona?.is_default_persona && - !admin && ( -
- - Click below to add documents or folders from the - My Document feature - - {(selectedFiles.length > 0 || - selectedFolders.length > 0) && ( -
- {selectedFiles.map((file) => ( - {}} - title={file.name} - icon={} - /> - ))} - {selectedFolders.map((folder) => ( - {}} - title={folder.name} - icon={} - /> - ))} -
- )} - -
+ ]; + setFieldValue("icon_shape", newShape.encodedGrid); + setFieldValue("icon_color", randomColor); + }} + > + + Generate Icon + )} - {values.knowledge_source === "team_knowledge" && - ccPairs.length > 0 && ( -
-
- - <> - Select which{" "} - {!user || user.role === "admin" ? ( - - Document Sets - - ) : ( - "Team Document Sets" - )}{" "} - this Assistant should use to inform its - responses. If none are specified, the - Assistant will reference all available - documents. - - -
+ {existingPersona?.uploaded_image_id && + removePersonaImage && + !values.uploaded_image && ( + + )} - {documentSets.length > 0 ? ( - ( -
-
- {documentSets.map((documentSet) => ( - { - const index = - values.document_set_ids.indexOf( - documentSet.id - ); - if (index !== -1) { - arrayHelpers.remove(index); - } else { - arrayHelpers.push(documentSet.id); - } - }} - /> - ))} -
-
- )} - /> - ) : ( -

- - + Create Document Set - -

- )} -
+ {existingPersona?.uploaded_image_id && + !removePersonaImage && + !values.uploaded_image && ( + )}
- )} +
+
- -
-

Actions

+ - {imageGenerationTool && ( + + + + + + +
+
+ {searchTool && ( <> -
- + +
+
+
+

+ Knowledge +

+
+ + + +
+ { + setFieldValue("num_chunks", null); + toggleToolInValues(searchTool.id); + }} + name={`enabled_tools_map.${searchTool.id}`} + disabled={ccPairs.length === 0} + /> +
+
+ + {ccPairs.length === 0 && ( + +

+ To use the Knowledge Action, you need + to have at least one Connector + configured. +

+
+ )} +
+
+
+
+
)} - {internetSearchTool && ( - <> - - - )} + {searchTool && values.enabled_tools_map[searchTool.id] && ( +
+ {canShowKnowledgeSource && ( + <> +
+
+
+ setFieldValue( + "knowledge_source", + "team_knowledge" + ) + } + > +
+ +
+

+ Team Knowledge +

+
- {customTools.length > 0 && - customTools.map((tool) => ( - - ))} -
-
-
- +
+ setFieldValue( + "knowledge_source", + "user_files" + ) + } + > +
+ +
+

+ User Knowledge +

+
+
+
+ + )} -
-
-
Default Model
-
- { - if (selected === null) { - setFieldValue("llm_model_version_override", null); - setFieldValue("llm_model_provider_override", null); - } else { - const { modelName, provider, name } = - destructureValue(selected); - if (modelName && name) { - setFieldValue("llm_model_version_override", modelName); - setFieldValue("llm_model_provider_override", name); - } - } - }} - /> -
+ {values.knowledge_source === "user_files" && + !existingPersona?.is_default_persona && + !admin && ( +
+ + Click below to add documents or folders from the + My Document feature + + {(values.user_file_ids.length > 0 || + values.user_folder_ids.length > 0) && ( +
+ {selectedFiles.map((file) => ( + {}} + title={file.name} + icon={} + /> + ))} + {selectedFolders.map((folder) => ( + {}} + title={folder.name} + icon={} + /> + ))} +
+ )} + +
+ )} - - - {showAdvancedOptions && ( - <> -
- {user?.role == UserRole.ADMIN && ( - { - if (checked) { - setFieldValue("is_public", true); - setFieldValue("is_default_persona", true); - } - }} - name="is_default_persona" - label="Featured Assistant" - subtext="If set, this assistant will be pinned for all new users and appear in the Featured list in the assistant explorer. This also makes the assistant public." - /> + {values.knowledge_source === "team_knowledge" && + ccPairs.length > 0 && ( +
+
+ + <> + Select which{" "} + {!user || user.role === "admin" ? ( + + Document Sets + + ) : ( + "Team Document Sets" + )}{" "} + this Assistant should use to inform its + responses. If none are specified, the + Assistant will reference all available + documents. + + +
+ + {documentSets.length > 0 ? ( + ( +
+
+ {documentSets.map((documentSet) => ( + { + const index = + values.document_set_ids.indexOf( + documentSet.id + ); + if (index !== -1) { + arrayHelpers.remove(index); + } else { + arrayHelpers.push( + documentSet.id + ); + } + }} + /> + ))} +
+
+ )} + /> + ) : ( +

+ + + Create Document Set + +

+ )} +
+ )} +
)} +
+

Actions

-
-
Access
-
- - Control who can access and use this assistant - - -
-
- - - -
- { - if (values.is_default_persona && !checked) { - setShowVisibilityWarning(true); - } else { - setFieldValue("is_public", checked); - if (!checked) { - // Even though this code path should not be possible, - // we set the default persona to false to be safe - setFieldValue( - "is_default_persona", - false - ); - } - if (checked) { - setFieldValue("selectedUsers", []); - setFieldValue("selectedGroups", []); - } - } - }} - disabled={values.is_default_persona} - /> -
-
- {values.is_default_persona && ( - - Default persona must be public. Set - "Default Persona" to false to change - visibility. - - )} -
-
- - Organization Public - -
- - {showVisibilityWarning && ( -
- - - Default persona must be public. Visibility has been - automatically set to organization public. - -
- )} - - {values.is_public ? ( -

- This assistant will be available to everyone in your - organization -

- ) : ( + {imageGenerationTool && ( <> -

- This assistant will only be available to specific - users and groups -

-
- - - - !values.selectedUsers.some( - (su: MinimalUserSnapshot) => - su.id === u.id - ) && u.id !== user?.id - ) - .map((u: MinimalUserSnapshot) => ({ - name: u.email, - value: u.id, - type: "user", - })), - ...(userGroups || []) - .filter( - (g: UserGroup) => - !values.selectedGroups.includes(g.id) - ) - .map((g: UserGroup) => ({ - name: g.name, - value: g.id, - type: "group", - })), - ]} - onSelect={( - selected: DropdownOption - ) => { - const option = selected as { - name: string; - value: string | number; - type: "user" | "group"; - }; - if (option.type === "user") { - setFieldValue("selectedUsers", [ - ...values.selectedUsers, - { id: option.value, email: option.name }, - ]); - } else { - setFieldValue("selectedGroups", [ - ...values.selectedGroups, - option.value, - ]); - } - }} +
+
-
- {values.selectedUsers.map( - (user: MinimalUserSnapshot) => ( - { - setFieldValue( - "selectedUsers", - values.selectedUsers.filter( - (u: MinimalUserSnapshot) => - u.id !== user.id - ) - ); - }} - title={user.email} - icon={} - /> - ) - )} - {values.selectedGroups.map((groupId: number) => { - const group = (userGroups || []).find( - (g: UserGroup) => g.id === groupId - ); - return group ? ( - { - setFieldValue( - "selectedGroups", - values.selectedGroups.filter( - (id: number) => id !== group.id - ) - ); - }} - icon={} - /> - ) : null; - })} -
)} + + {internetSearchTool && ( + <> + + + )} + + {customTools.length > 0 && + customTools.map((tool) => ( + + ))}
+
+ - +
+
+
+ Default Model +
+
+ { + if (selected === null) { + setFieldValue("llm_model_version_override", null); + setFieldValue("llm_model_provider_override", null); + } else { + const { modelName, provider, name } = + destructureValue(selected); + if (modelName && name) { + setFieldValue( + "llm_model_version_override", + modelName + ); + setFieldValue("llm_model_provider_override", name); + } + } + }} + /> +
-
-
-
- [Optional] Starter Messages + + + {showAdvancedOptions && ( + <> +
+ {user?.role == UserRole.ADMIN && ( + { + if (checked) { + setFieldValue("is_public", true); + setFieldValue("is_default_persona", true); + } + }} + name="is_default_persona" + label="Featured Assistant" + subtext="If set, this assistant will be pinned for all new users and appear in the Featured list in the assistant explorer. This also makes the assistant public." + /> + )} + + + +
+
Access
+
+ + Control who can access and use this assistant + + +
+
+ + + +
+ { + if ( + values.is_default_persona && + !checked + ) { + setShowVisibilityWarning(true); + } else { + setFieldValue("is_public", checked); + if (!checked) { + // Even though this code path should not be possible, + // we set the default persona to false to be safe + setFieldValue( + "is_default_persona", + false + ); + } + if (checked) { + setFieldValue("selectedUsers", []); + setFieldValue("selectedGroups", []); + } + } + }} + disabled={values.is_default_persona} + /> +
+
+ {values.is_default_persona && ( + + Default persona must be public. Set + "Default Persona" to false to change + visibility. + + )} +
+
+ + Organization Public + +
+ + {showVisibilityWarning && ( +
+ + + Default persona must be public. Visibility has + been automatically set to organization public. + +
+ )} + + {values.is_public ? ( +

+ This assistant will be available to everyone in your + organization +

+ ) : ( + <> +

+ This assistant will only be available to specific + users and groups +

+
+ + + + !values.selectedUsers.some( + (su: MinimalUserSnapshot) => + su.id === u.id + ) && u.id !== user?.id + ) + .map((u: MinimalUserSnapshot) => ({ + name: u.email, + value: u.id, + type: "user", + })), + ...(userGroups || []) + .filter( + (g: UserGroup) => + !values.selectedGroups.includes(g.id) + ) + .map((g: UserGroup) => ({ + name: g.name, + value: g.id, + type: "group", + })), + ]} + onSelect={( + selected: DropdownOption + ) => { + const option = selected as { + name: string; + value: string | number; + type: "user" | "group"; + }; + if (option.type === "user") { + setFieldValue("selectedUsers", [ + ...values.selectedUsers, + { id: option.value, email: option.name }, + ]); + } else { + setFieldValue("selectedGroups", [ + ...values.selectedGroups, + option.value, + ]); + } + }} + /> +
+
+ {values.selectedUsers.map( + (user: MinimalUserSnapshot) => ( + { + setFieldValue( + "selectedUsers", + values.selectedUsers.filter( + (u: MinimalUserSnapshot) => + u.id !== user.id + ) + ); + }} + title={user.email} + icon={} + /> + ) + )} + {values.selectedGroups.map((groupId: number) => { + const group = (userGroups || []).find( + (g: UserGroup) => g.id === groupId + ); + return group ? ( + { + setFieldValue( + "selectedGroups", + values.selectedGroups.filter( + (id: number) => id !== group.id + ) + ); + }} + icon={} + /> + ) : null; + })} +
+ + )}
- - 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. - - -
- ( - - debouncedRefreshPrompts(values, setFieldValue) - } - autoStarterMessageEnabled={ - autoStarterMessageEnabled - } - isRefreshing={isRefreshing} - values={values.starter_messages} - arrayHelpers={arrayHelpers} - setFieldValue={setFieldValue} - /> - )} - /> -
-
- -
-
-
Labels
-
-

- Select labels to categorize this assistant -

-
- { - await createLabel(name); - const currentLabels = await refreshLabels(); - setTimeout(() => { - const newLabelId = currentLabels.find( - (l: { name: string }) => l.name === name - )?.id; - const updatedLabelIds = [ +
+
+
+ [Optional] Starter Messages +
+
+ + + 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. + + +
+ ( + + debouncedRefreshPrompts(values, setFieldValue) + } + autoStarterMessageEnabled={ + autoStarterMessageEnabled + } + isRefreshing={isRefreshing} + values={values.starter_messages} + arrayHelpers={arrayHelpers} + setFieldValue={setFieldValue} + /> + )} + /> +
+
+ +
+ +
+
Labels
+
+

+ Select labels to categorize this assistant +

+
+ { + await createLabel(name); + const currentLabels = await refreshLabels(); + + setTimeout(() => { + const newLabelId = currentLabels.find( + (l: { name: string }) => l.name === name + )?.id; + const updatedLabelIds = [ + ...values.label_ids, + newLabelId as number, + ]; + setFieldValue("label_ids", updatedLabelIds); + }, 300); + }} + options={Array.from( + new Set(labels.map((label) => label.name)) + ).map((name) => ({ + name, + value: name, + }))} + onSelect={(selected) => { + const newLabelIds = [ ...values.label_ids, - newLabelId as number, + labels.find((l) => l.name === selected.value) + ?.id as number, ]; - setFieldValue("label_ids", updatedLabelIds); - }, 300); - }} - options={Array.from( - new Set(labels.map((label) => label.name)) - ).map((name) => ({ - name, - value: name, - }))} - onSelect={(selected) => { - const newLabelIds = [ - ...values.label_ids, - labels.find((l) => l.name === selected.value) - ?.id as number, - ]; - setFieldValue("label_ids", newLabelIds); - }} - itemComponent={({ option }) => ( -
-
{ - const label = labels.find( - (l) => l.name === option.value - ); - if (label) { - const isSelected = values.label_ids.includes( - label.id - ); - const newLabelIds = isSelected - ? values.label_ids.filter( - (id: number) => id !== label.id - ) - : [...values.label_ids, label.id]; - setFieldValue("label_ids", newLabelIds); - } - }} - > - - {option.name} - -
- {admin && ( - - )} -
- )} - /> -
- {values.label_ids.map((labelId: number) => { - const label = labels.find((l) => l.id === labelId); - return label ? ( - { - setFieldValue( - "label_ids", - values.label_ids.filter( - (id: number) => id !== label.id - ) - ); - }} - title={label.name} - icon={} - /> - ) : null; - })} + + {option.name} + +
+ {admin && ( + + )} +
+ )} + /> +
+ {values.label_ids.map((labelId: number) => { + const label = labels.find((l) => l.id === labelId); + return label ? ( + { + setFieldValue( + "label_ids", + values.label_ids.filter( + (id: number) => id !== label.id + ) + ); + }} + title={label.name} + icon={} + /> + ) : null; + })} +
-
- + -
-

Knowledge Options

-
- { - const value = e.target.value; - if (value === "" || /^[0-9]+$/.test(value)) { - setFieldValue("num_chunks", value); - } - }} - /> +
+

+ Knowledge Options +

+
+ { + const value = e.target.value; + if (value === "" || /^[0-9]+$/.test(value)) { + setFieldValue("num_chunks", value); + } + }} + /> - + - + - + +
-
- + - + - + - { - setFieldValue("task_prompt", e.target.value); - }} - explanationText="Learn about prompting in our docs!" - explanationLink="https://docs.onyx.app/guides/assistants" - className="[&_textarea]:placeholder:text-text-muted/50" - /> - - )} - -
- - -
- -
- {existingPersona && ( - + { + setFieldValue("task_prompt", e.target.value); + }} + explanationText="Learn about prompting in our docs!" + explanationLink="https://docs.onyx.app/guides/assistants" + className="[&_textarea]:placeholder:text-text-muted/50" + /> + )} -
- + +
+ + +
+ +
+ {existingPersona && ( + + )} +
+ + ); }} diff --git a/web/src/app/admin/bots/SlackBotTable.tsx b/web/src/app/admin/bots/SlackBotTable.tsx index c896bef34..ddab71ce0 100644 --- a/web/src/app/admin/bots/SlackBotTable.tsx +++ b/web/src/app/admin/bots/SlackBotTable.tsx @@ -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[] }) => { >
- + {slackBot.name}
diff --git a/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx b/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx index 3a2a9de84..590d3b875 100644 --- a/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx +++ b/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx @@ -424,7 +424,6 @@ export function LLMProviderUpdateForm({ />
)} - {popup} -
+