From aa146db7621a90ade33571d6451d2cbfd35a5db5 Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Thu, 7 Nov 2024 06:27:10 -0500 Subject: [PATCH 1/5] Wait for chat-input to toggle in chat_profile E2E test (#1468) Increase reliability of test while increasing performance. --------- Co-authored-by: Mathijs de Bruin --- cypress/e2e/chat_profiles/spec.cy.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/chat_profiles/spec.cy.ts b/cypress/e2e/chat_profiles/spec.cy.ts index 0558691fe9..befcd8285f 100644 --- a/cypress/e2e/chat_profiles/spec.cy.ts +++ b/cypress/e2e/chat_profiles/spec.cy.ts @@ -37,7 +37,8 @@ describe('Chat profiles', () => { cy.get('[data-test="select-item:GPT-4"]').click(); cy.get('#confirm').click(); - cy.wait(1000); + cy.get('#chat-input').should('have.attr', 'disabled'); + cy.get('#chat-input').should('not.have.attr', 'disabled'); cy.get('#starter-ask-for-help').should('exist').click(); cy.get('.step') @@ -78,7 +79,9 @@ describe('Chat profiles', () => { cy.get('#chat-profile-selector').parent().click(); // Force hover over GPT-4 profile to show description - cy.get('[data-test="select-item:GPT-4"]').trigger('mouseover', { force: true }); + cy.get('[data-test="select-item:GPT-4"]').trigger('mouseover', { + force: true + }); // Wait for the popover to appear and check its content cy.get('#chat-profile-description').within(() => { @@ -111,6 +114,8 @@ describe('Chat profiles', () => { // Select GPT-4 profile cy.get('[data-test="select-item:GPT-4"]').click(); + cy.get('#chat-input').should('have.attr', 'disabled'); + cy.get('#chat-input').should('not.have.attr', 'disabled'); // Verify the profile has been changed submitMessage('hello'); @@ -121,6 +126,5 @@ describe('Chat profiles', () => { 'contain', 'starting chat with admin using the GPT-4 chat profile' ); - }); }); From fc5acdee4a23132a3d31d89121aeab58ddc0896e Mon Sep 17 00:00:00 2001 From: Mathijs de Bruin Date: Thu, 7 Nov 2024 17:56:35 +0000 Subject: [PATCH 2/5] Allow running commit hooks outside of poetry/venv. (#1503) --- lint-staged.config.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lint-staged.config.js b/lint-staged.config.js index 9fb04678a6..8befb50e38 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -2,6 +2,10 @@ module.exports = { '**/*.{js,jsx,ts,tsx}': ['npx prettier --write', 'npx eslint --fix'], '**/*.{ts,tsx}': [() => 'tsc --skipLibCheck --noEmit'], - '**/*.py': ['ruff check --fix', 'ruff format', () => 'pnpm run lintPython'], + '**/*.py': [ + 'cd backend && poetry run ruff check --fix', + 'cd backend && poetry run ruff format', + () => 'pnpm run lintPython' + ], '.github/{workflows,actions}/**': ['actionlint'] }; From 82f08bf671dc92419e3741997aa55bde0deac2f8 Mon Sep 17 00:00:00 2001 From: Mathijs de Bruin Date: Thu, 7 Nov 2024 17:57:28 +0000 Subject: [PATCH 3/5] @data_layer API hook to configure data layer. (#1463) * @data_layer API hook to configure data layer. * Use @data_layer in e2e test. * Unit tests for get_data_layer() and @data_layer. * Test cleanup should never fail. --- backend/chainlit/__init__.py | 11 +-- backend/chainlit/callbacks.py | 29 ++++++-- backend/chainlit/config.py | 26 ++++--- backend/chainlit/data/__init__.py | 9 ++- backend/tests/conftest.py | 34 +++++++++ backend/tests/data/test_get_data_layer.py | 18 +++++ backend/tests/test_callbacks.py | 73 +++++++++++++------- backend/tests/test_server.py | 22 ++---- cypress/e2e/custom_data_layer/sql_alchemy.py | 10 +-- cypress/e2e/data_layer/main.py | 4 +- cypress/e2e/data_layer/spec.cy.ts | 2 +- 11 files changed, 169 insertions(+), 69 deletions(-) create mode 100644 backend/tests/data/test_get_data_layer.py diff --git a/backend/chainlit/__init__.py b/backend/chainlit/__init__.py index 635a2d2cd9..279762d3eb 100644 --- a/backend/chainlit/__init__.py +++ b/backend/chainlit/__init__.py @@ -14,6 +14,9 @@ import asyncio from typing import TYPE_CHECKING, Any, Dict +from literalai import ChatGeneration, CompletionGeneration, GenerationMessage +from pydantic.dataclasses import dataclass + import chainlit.input_widget as input_widget from chainlit.action import Action from chainlit.cache import cache @@ -44,22 +47,21 @@ ) from chainlit.step import Step, step from chainlit.sync import make_async, run_sync -from chainlit.types import InputAudioChunk, OutputAudioChunk, ChatProfile, Starter +from chainlit.types import ChatProfile, InputAudioChunk, OutputAudioChunk, Starter from chainlit.user import PersistedUser, User from chainlit.user_session import user_session from chainlit.utils import make_module_getattr from chainlit.version import __version__ -from literalai import ChatGeneration, CompletionGeneration, GenerationMessage -from pydantic.dataclasses import dataclass from .callbacks import ( action_callback, author_rename, + data_layer, header_auth_callback, oauth_callback, - on_audio_start, on_audio_chunk, on_audio_end, + on_audio_start, on_chat_end, on_chat_resume, on_chat_start, @@ -186,6 +188,7 @@ def acall(self): "on_stop", "action_callback", "on_settings_update", + "data_layer", ] diff --git a/backend/chainlit/callbacks.py b/backend/chainlit/callbacks.py index 02c6feb124..f03625da64 100644 --- a/backend/chainlit/callbacks.py +++ b/backend/chainlit/callbacks.py @@ -1,8 +1,12 @@ import inspect from typing import Any, Awaitable, Callable, Dict, List, Optional +from fastapi import Request, Response +from starlette.datastructures import Headers + from chainlit.action import Action from chainlit.config import config +from chainlit.data.base import BaseDataLayer from chainlit.message import Message from chainlit.oauth_providers import get_configured_oauth_providers from chainlit.step import Step, step @@ -10,13 +14,11 @@ from chainlit.types import ChatProfile, Starter, ThreadDict from chainlit.user import User from chainlit.utils import wrap_user_function -from fastapi import Request, Response -from starlette.datastructures import Headers @trace def password_auth_callback( - func: Callable[[str, str], Awaitable[Optional[User]]] + func: Callable[[str, str], Awaitable[Optional[User]]], ) -> Callable: """ Framework agnostic decorator to authenticate the user. @@ -38,7 +40,7 @@ async def password_auth_callback(username: str, password: str) -> Optional[User] @trace def header_auth_callback( - func: Callable[[Headers], Awaitable[Optional[User]]] + func: Callable[[Headers], Awaitable[Optional[User]]], ) -> Callable: """ Framework agnostic decorator to authenticate the user via a header @@ -177,7 +179,7 @@ def set_chat_profiles( @trace def set_starters( - func: Callable[[Optional["User"]], Awaitable[List["Starter"]]] + func: Callable[[Optional["User"]], Awaitable[List["Starter"]]], ) -> Callable: """ Programmatic declaration of the available starter (can depend on the User from the session if authentication is setup). @@ -221,6 +223,7 @@ def on_audio_start(func: Callable) -> Callable: config.code.on_audio_start = wrap_user_function(func, with_task=False) return func + @trace def on_audio_chunk(func: Callable) -> Callable: """ @@ -254,7 +257,7 @@ def on_audio_end(func: Callable) -> Callable: @trace def author_rename( - func: Callable[[str], Awaitable[str]] + func: Callable[[str], Awaitable[str]], ) -> Callable[[str], Awaitable[str]]: """ Useful to rename the author of message to display more friendly author names in the UI. @@ -315,3 +318,17 @@ def on_settings_update( config.code.on_settings_update = wrap_user_function(func, with_task=True) return func + + +def data_layer( + func: Callable[[], BaseDataLayer], +) -> Callable[[], BaseDataLayer]: + """ + Hook to configure custom data layer. + """ + + # We don't use wrap_user_function here because: + # 1. We don't need to support async here and; + # 2. We don't want to change the API for get_data_layer() to be async, everywhere (at this point). + config.code.data_layer = func + return func diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index ad9c1aaf88..71fa8fca1f 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -17,21 +17,24 @@ ) import tomli -from chainlit.logger import logger -from chainlit.translations import lint_translation_json -from chainlit.version import __version__ from dataclasses_json import DataClassJsonMixin from pydantic.dataclasses import Field, dataclass from starlette.datastructures import Headers +from chainlit.data.base import BaseDataLayer +from chainlit.logger import logger +from chainlit.translations import lint_translation_json +from chainlit.version import __version__ + from ._utils import is_path_inside if TYPE_CHECKING: + from fastapi import Request, Response + from chainlit.action import Action from chainlit.message import Message - from chainlit.types import InputAudioChunk, ChatProfile, Starter, ThreadDict + from chainlit.types import ChatProfile, InputAudioChunk, Starter, ThreadDict from chainlit.user import User - from fastapi import Request, Response BACKEND_ROOT = os.path.dirname(__file__) @@ -272,9 +275,9 @@ class CodeSettings: password_auth_callback: Optional[ Callable[[str, str], Awaitable[Optional["User"]]] ] = None - header_auth_callback: Optional[ - Callable[[Headers], Awaitable[Optional["User"]]] - ] = None + header_auth_callback: Optional[Callable[[Headers], Awaitable[Optional["User"]]]] = ( + None + ) oauth_callback: Optional[ Callable[[str, str, Dict[str, str], "User"], Awaitable[Optional["User"]]] ] = None @@ -293,9 +296,10 @@ class CodeSettings: set_chat_profiles: Optional[ Callable[[Optional["User"]], Awaitable[List["ChatProfile"]]] ] = None - set_starters: Optional[ - Callable[[Optional["User"]], Awaitable[List["Starter"]]] - ] = None + set_starters: Optional[Callable[[Optional["User"]], Awaitable[List["Starter"]]]] = ( + None + ) + data_layer: Optional[Callable[[], BaseDataLayer]] = None @dataclass() diff --git a/backend/chainlit/data/__init__.py b/backend/chainlit/data/__init__.py index 3082f91744..dfc359c24b 100644 --- a/backend/chainlit/data/__init__.py +++ b/backend/chainlit/data/__init__.py @@ -11,9 +11,16 @@ def get_data_layer(): global _data_layer + print("Getting data layer", _data_layer) if not _data_layer: - if api_key := os.environ.get("LITERAL_API_KEY"): + from chainlit.config import config + + if config.code.data_layer: + # When @data_layer is configured, call it to get data layer. + _data_layer = config.code.data_layer() + elif api_key := os.environ.get("LITERAL_API_KEY"): + # When LITERAL_API_KEY is defined, use LiteralAI data layer from .literalai import LiteralDataLayer # support legacy LITERAL_SERVER variable as fallback diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 7cc4266371..5eda37063b 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -1,11 +1,16 @@ import datetime from contextlib import asynccontextmanager +from pathlib import Path from typing import Callable from unittest.mock import AsyncMock, Mock import pytest import pytest_asyncio + +from chainlit import config +from chainlit.callbacks import data_layer from chainlit.context import ChainlitContext, context_var +from chainlit.data.base import BaseDataLayer from chainlit.session import HTTPSession, WebsocketSession from chainlit.user import PersistedUser from chainlit.user_session import UserSession @@ -79,3 +84,32 @@ def mock_websocket_session(): @pytest.fixture def mock_http_session(): return Mock(spec=HTTPSession) + + +@pytest.fixture +def mock_data_layer(monkeypatch: pytest.MonkeyPatch) -> AsyncMock: + mock_data_layer = AsyncMock(spec=BaseDataLayer) + + return mock_data_layer + + +@pytest.fixture +def mock_get_data_layer(mock_data_layer: AsyncMock, test_config: config.ChainlitConfig): + # Instantiate mock data layer + mock_get_data_layer = Mock(return_value=mock_data_layer) + + # Configure it using @data_layer decorator + return data_layer(mock_get_data_layer) + + +@pytest.fixture +def test_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path): + monkeypatch.setenv("CHAINLIT_ROOT_PATH", str(tmp_path)) + + test_config = config.load_config() + + monkeypatch.setattr("chainlit.callbacks.config", test_config) + monkeypatch.setattr("chainlit.server.config", test_config) + monkeypatch.setattr("chainlit.config.config", test_config) + + return test_config diff --git a/backend/tests/data/test_get_data_layer.py b/backend/tests/data/test_get_data_layer.py new file mode 100644 index 0000000000..403e86ae9a --- /dev/null +++ b/backend/tests/data/test_get_data_layer.py @@ -0,0 +1,18 @@ +from unittest.mock import AsyncMock, Mock + +from chainlit.data import get_data_layer + + +async def test_get_data_layer( + mock_data_layer: AsyncMock, + mock_get_data_layer: Mock, +): + # Check whether the data layer is properly set + assert mock_data_layer == get_data_layer() + + mock_get_data_layer.assert_called_once() + + # Getting the data layer again, should not result in additional call + assert mock_data_layer == get_data_layer() + + mock_get_data_layer.assert_called_once() diff --git a/backend/tests/test_callbacks.py b/backend/tests/test_callbacks.py index 3c41000137..c00674ba9b 100644 --- a/backend/tests/test_callbacks.py +++ b/backend/tests/test_callbacks.py @@ -1,22 +1,17 @@ from __future__ import annotations +from unittest.mock import AsyncMock, Mock + import pytest -from chainlit.callbacks import password_auth_callback -from chainlit.user import User from chainlit import config +from chainlit.callbacks import data_layer, password_auth_callback +from chainlit.data import get_data_layer +from chainlit.data.base import BaseDataLayer +from chainlit.user import User -@pytest.fixture -def test_config(monkeypatch: pytest.MonkeyPatch): - test_config = config.load_config() - - monkeypatch.setattr("chainlit.callbacks.config", test_config) - - return test_config - - -async def test_password_auth_callback(test_config): +async def test_password_auth_callback(test_config: config.ChainlitConfig): @password_auth_callback async def auth_func(username: str, password: str) -> User | None: if username == "testuser" and password == "testpass": # nosec B105 @@ -36,10 +31,11 @@ async def auth_func(username: str, password: str) -> User | None: assert result is None -async def test_header_auth_callback(test_config): - from chainlit.callbacks import header_auth_callback +async def test_header_auth_callback(test_config: config.ChainlitConfig): from starlette.datastructures import Headers + from chainlit.callbacks import header_auth_callback + @header_auth_callback async def auth_func(headers: Headers) -> User | None: if headers.get("Authorization") == "Bearer valid_token": @@ -66,7 +62,7 @@ async def auth_func(headers: Headers) -> User | None: assert result is None -async def test_oauth_callback(test_config): +async def test_oauth_callback(test_config: config.ChainlitConfig): from unittest.mock import patch from chainlit.callbacks import oauth_callback @@ -107,7 +103,7 @@ async def auth_func( assert result is None -async def test_on_message(mock_chainlit_context, test_config): +async def test_on_message(mock_chainlit_context, test_config: config.ChainlitConfig): from chainlit.callbacks import on_message from chainlit.message import Message @@ -137,7 +133,7 @@ async def handle_message(message: Message): context.session.emit.assert_called() -async def test_on_stop(mock_chainlit_context, test_config): +async def test_on_stop(mock_chainlit_context, test_config: config.ChainlitConfig): from chainlit.callbacks import on_stop from chainlit.config import config @@ -159,7 +155,9 @@ async def handle_stop(): assert stop_called -async def test_action_callback(mock_chainlit_context, test_config): +async def test_action_callback( + mock_chainlit_context, test_config: config.ChainlitConfig +): from chainlit.action import Action from chainlit.callbacks import action_callback from chainlit.config import config @@ -184,7 +182,9 @@ async def handle_action(action: Action): assert action_handled -async def test_on_settings_update(mock_chainlit_context, test_config): +async def test_on_settings_update( + mock_chainlit_context, test_config: config.ChainlitConfig +): from chainlit.callbacks import on_settings_update from chainlit.config import config @@ -207,7 +207,7 @@ async def handle_settings_update(settings: dict): assert settings_updated -async def test_author_rename(test_config): +async def test_author_rename(test_config: config.ChainlitConfig): from chainlit.callbacks import author_rename from chainlit.config import config @@ -238,7 +238,7 @@ async def rename_author(author: str) -> str: assert result == "Human" -async def test_on_chat_start(mock_chainlit_context, test_config): +async def test_on_chat_start(mock_chainlit_context, test_config: config.ChainlitConfig): from chainlit.callbacks import on_chat_start from chainlit.config import config @@ -263,7 +263,9 @@ async def handle_chat_start(): context.session.emit.assert_called() -async def test_on_chat_resume(mock_chainlit_context, test_config): +async def test_on_chat_resume( + mock_chainlit_context, test_config: config.ChainlitConfig +): from chainlit.callbacks import on_chat_resume from chainlit.config import config from chainlit.types import ThreadDict @@ -299,7 +301,9 @@ async def handle_chat_resume(thread: ThreadDict): assert chat_resumed -async def test_set_chat_profiles(mock_chainlit_context, test_config): +async def test_set_chat_profiles( + mock_chainlit_context, test_config: config.ChainlitConfig +): from chainlit.callbacks import set_chat_profiles from chainlit.config import config from chainlit.types import ChatProfile @@ -327,7 +331,7 @@ async def get_chat_profiles(user): assert result[0].markdown_description == "A test profile" -async def test_set_starters(mock_chainlit_context, test_config): +async def test_set_starters(mock_chainlit_context, test_config: config.ChainlitConfig): from chainlit.callbacks import set_starters from chainlit.config import config from chainlit.types import Starter @@ -358,7 +362,7 @@ async def get_starters(user): assert result[0].message == "Test Message" -async def test_on_chat_end(mock_chainlit_context, test_config): +async def test_on_chat_end(mock_chainlit_context, test_config: config.ChainlitConfig): from chainlit.callbacks import on_chat_end from chainlit.config import config @@ -381,3 +385,22 @@ async def handle_chat_end(): # Check that the emit method was called context.session.emit.assert_called() + + +async def test_data_layer_config( + mock_data_layer: AsyncMock, + test_config: config.ChainlitConfig, + mock_get_data_layer: Mock, +): + """Test whether we can properly configure a data layer.""" + + # Test that the callback is properly registered + assert test_config.code.data_layer is not None + + # Call the registered callback + result = test_config.code.data_layer() + + # Check that the result is an instance of MockDataLayer + assert isinstance(result, BaseDataLayer) + + mock_get_data_layer.assert_called_once() diff --git a/backend/tests/test_server.py b/backend/tests/test_server.py index 36c65124d6..c61827c8a3 100644 --- a/backend/tests/test_server.py +++ b/backend/tests/test_server.py @@ -1,17 +1,18 @@ +import datetime # Added import for datetime import os -from pathlib import Path import pathlib +import tempfile +from pathlib import Path from typing import Callable from unittest.mock import AsyncMock, Mock, create_autospec, mock_open -import datetime # Added import for datetime import pytest -import tempfile -from chainlit.session import WebsocketSession +from fastapi.testclient import TestClient + from chainlit.auth import get_current_user from chainlit.config import APP_ROOT, ChainlitConfig, load_config from chainlit.server import app -from fastapi.testclient import TestClient +from chainlit.session import WebsocketSession from chainlit.types import FileReference from chainlit.user import PersistedUser # Added import for PersistedUser @@ -21,17 +22,6 @@ def test_client(): return TestClient(app) -@pytest.fixture -def test_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path): - monkeypatch.setenv("CHAINLIT_ROOT_PATH", str(tmp_path)) - - config = load_config() - - monkeypatch.setattr("chainlit.server.config", config) - - return config - - @pytest.fixture def mock_load_translation(test_config: ChainlitConfig, monkeypatch: pytest.MonkeyPatch): monkeypatch.setattr( diff --git a/cypress/e2e/custom_data_layer/sql_alchemy.py b/cypress/e2e/custom_data_layer/sql_alchemy.py index 430b02a58e..809a109e86 100644 --- a/cypress/e2e/custom_data_layer/sql_alchemy.py +++ b/cypress/e2e/custom_data_layer/sql_alchemy.py @@ -1,6 +1,5 @@ from typing import Optional -import chainlit.data as cl_data from chainlit.data.sql_alchemy import SQLAlchemyDataLayer from chainlit.data.storage_clients.azure import AzureStorageClient @@ -10,9 +9,12 @@ account_url="", container="" ) -cl_data._data_layer = SQLAlchemyDataLayer( - conninfo="", storage_provider=storage_client -) + +@cl.data_layer +def data_layer(): + return SQLAlchemyDataLayer( + conninfo="", storage_provider=storage_client + ) @cl.on_chat_start diff --git a/cypress/e2e/data_layer/main.py b/cypress/e2e/data_layer/main.py index 2fc6181ffa..e03adea92c 100644 --- a/cypress/e2e/data_layer/main.py +++ b/cypress/e2e/data_layer/main.py @@ -194,7 +194,9 @@ async def build_debug_url(self) -> str: return "" -cl_data._data_layer = TestDataLayer() +@cl.data_layer +def data_layer(): + return TestDataLayer() async def send_count(): diff --git a/cypress/e2e/data_layer/spec.cy.ts b/cypress/e2e/data_layer/spec.cy.ts index 0320e89e98..394bb6e745 100644 --- a/cypress/e2e/data_layer/spec.cy.ts +++ b/cypress/e2e/data_layer/spec.cy.ts @@ -116,7 +116,7 @@ describe('Data Layer', () => { afterEach(() => { cy.get('@threadHistoryFile').then((threadHistoryFile) => { // Clean up the thread history file - cy.exec(`rm ${threadHistoryFile}`); + cy.exec(`rm -f ${threadHistoryFile}`); }); }); From e8d3b5d57a1d592a7d8fe2d2b15e0721bd4f1d93 Mon Sep 17 00:00:00 2001 From: Mathijs de Bruin Date: Thu, 7 Nov 2024 13:09:01 +0000 Subject: [PATCH 4/5] Changelog for 1.3.2 and 2.0rc0. --- CHANGELOG.md | 147 +++++++++++++++++++++------------------------------ 1 file changed, 61 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb58059a0d..02510caecc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,72 @@ All notable changes to Chainlit will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [1.3.2] - 2024-11-08 + +### Security Advisory +**IMPORTANT**: +- This release drops support for FastAPI versions before 0.115.3 and Starlette versions before 0.41.2 due to a severe security vulnerability (CVE-2024-47874). We strongly encourage all downstream dependencies to upgrade as well. +- This release still contains a known security vulnerability in the element feature that could allow unauthorized file access. We strongly recommend against using elements in production environments until a comprehensive fix is implemented in an upcoming release. + +### Security +- **[breaking]** Updated dependencies to address critical issues (#1493): + - Upgraded fastapi to 0.115.3 to address CVE-2024-47874 in Starlette + - Upgraded starlette to 0.41.2 (required for security fix) + - Upgraded werkzeug to 3.0.6 + +Note: This is a breaking change as older FastAPI versions are no longer supported. +To prioritize security, we opted to break with semver on this particular occasion. + +### Fixed +- Resolved incorrect message ordering in UI (#1501) + +## [2.0rc0] - 2024-11-08 + +### Security Advisory +**IMPORTANT**: +- The element feature currently contains a known security vulnerability that could allow unauthorized file access. We strongly recommend against using elements in production environments until a comprehensive fix is implemented in an upcoming release. + +### Changed +- **[breaking]**: Completely revamped audio implementation (#1401, #1410): + - Replaced `AudioChunk` with `InputAudioChunk` and `OutputAudioChunk` + - Changed default audio sampling rate from 44100 to 24000 + - Removed several audio configuration options (`min_decibels`, `initial_silence_timeout`, `silence_timeout`, `chunk_duration`, `max_duration`) + - Removed `RecordScreen` component +- Factored storage clients into separate modules (#1363) + +### Added +- Realtime audio streaming and processing (#1401, #1406, #1410): + - New `AudioPresence` component for visual representation + - Implemented `WavRecorder` and `WavStreamPlayer` classes + - Introduced new `on_audio_start` callback + - Added audio interruption functionality + - New audio connection signaling with `on` and `off` states +- Interactive DataFrame display with auto-fit content using MUI Data Grid (#1373, #1467) +- Optional websocket connection in react-client (#1379) +- Enhanced image interaction with popup view and download option (#1402) +- Current URL included in message payload (#1403) +- Allow empty chat input when submitting attachments (#1261) + +### Fixes +- Various backend fixes and cleanup (#1432): + - Use importlib.util.find_spec to check if a package is installed + - Use `raise... from` to wrap exceptions + - Fix error message in Discord integration + - Several minor fixups/cleanup + +### Development +- Implemented ruff for linting and formatting (#1495) +- Added mypy daemon for faster type-checking (#1495) +- Added GitHub Actions linting (#1445) +- Enabled direct installation from GitHub (#1423) +- Various build script improvements (#1462) + ## [1.3.1] - 2024-10-25 ### Security Advisory - **IMPORTANT**: This release temporarily reverts the file access security improvements from 1.3.0 to restore element functionality. The element feature currently has a known security vulnerability that could allow unauthorized access to files. We strongly recommend against using elements in production environments until the next release. -- A comprehensive security fix using HTTP-only cookie authentication will be implemented in an upcoming release. +- A comprehensive security fix will be implemented in an upcoming release. ### Changed @@ -59,91 +119,6 @@ override oauth prompt parameter. Enabling users to explicitly enable login/conse - Improved Python code style and linting (#1353) - Resolved various small text and documentation issues (#1347, #1348) -## [2.0.dev2] - 2024-10-25 - -### Security Advisory - -- **IMPORTANT**: This release temporarily reverts the file access security improvements from 2.0.dev1 to restore element functionality. The element feature currently has a known security vulnerability that could allow unauthorized access to files. We strongly recommend against using elements in production environments until the next release. -- A comprehensive security fix using HTTP-only cookie authentication will be implemented in an upcoming release. - -### Changed - -- Reverted authentication requirements for file access endpoints to restore element functionality (#1474) - -### Development - -- Work in progress on implementing HTTP-only cookie authentication for proper security (#1472) - -## [2.0.dev1] - 2024-10-22 - -### Added - -- Interactive DataFrame display component using MUI Data Grid (#1373) -- Optional websocket connection in react-client (#1379) -- Current URL in message payload (#1403) -- Improved image interaction - clicking opens popup with download option (#1402) -- Configurable user session timeout (#1032) - -### Security - -- Fixed file access vulnerability in get_file and upload_file endpoints (#1441) -- Added authentication to /project/file endpoint (#1441) -- Addressed security vulnerabilities in frontend dependencies (#1431, #1414) - -### Fixed - -- Dialog boxes extending beyond window (#1446) -- Allow empty chat input when submitting attachments (#1261) -- Tasklist when Chainlit is submounted (#1433) -- Spaces in avatar filenames (#1418) -- Step argument input and concurrency issues (#1409) -- Display_name copying to PersistentUser during authentication (#1425) - -### Development - -- Refactored storage clients into separate modules (#1363) -- Support for IETF BCP 47 language tags (#1399) -- Improved GitHub Actions workflows and build process (#1445) -- Direct installation from GitHub support (#1423) -- Extended package metadata with homepage and documentation links (#1413) -- Various backend fixes and code cleanup (#1432) - -## [2.0.dev0] - 2024-10-08 - -### Breaking Changes - -- Completely revamped audio implementation: - - Removed `AudioChunk` type, replaced with `InputAudioChunk` and `OutputAudioChunk` - - Changed audio sampling rate from 44100 to 24000 - - Removed several audio configuration options (`min_decibels`, `initial_silence_timeout`, `silence_timeout`, `chunk_duration`, `max_duration`) - - Introduced new `on_audio_start` callback - - Modified `on_audio_end` callback to no longer accept file elements as arguments - -### Added - -- New audio connection signaling with `on` and `off` states -- Introduced `AudioPresence` component for visual representation of audio state -- Added `WavRecorder` and `WavStreamPlayer` classes for improved audio handling -- New `startConversation` and `endConversation` methods in `useAudio` hook -- Implemented audio interruption functionality - -### Changed - -- Updated `useChatInteract` hook to include `startAudioStream` method -- Modified `useChatSession` to handle new audio streaming functionality -- Updated UI components to reflect new audio implementation, including new microphone icons and audio presence indicators -- Refactored `InputBoxFooter` to display audio presence when active - -### Removed - -- Eliminated `RecordScreen` component -- Removed several audio-related configuration options from `config.toml` - -### Development - -- Added new wavtools directory with various audio processing utilities -- Implemented new AudioWorklet processors for more efficient audio handling - ## [1.2.0] - 2024-09-16 ### Security From 45866b2149241079974fc74ffaac764a77bc698c Mon Sep 17 00:00:00 2001 From: Mathijs de Bruin Date: Thu, 7 Nov 2024 13:09:14 +0000 Subject: [PATCH 5/5] Bump version to 2.0rc0. --- backend/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 8b57573159..ed25c3828c 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "chainlit" -version = "2.0.dev2" +version = "2.0rc0" keywords = [ 'LLM', 'Agents',