From 1ead94e0db0225b000dbd72984aa52e8e2ebd7bb Mon Sep 17 00:00:00 2001 From: mauanga Date: Sun, 21 Jul 2024 02:53:17 +0200 Subject: [PATCH 01/13] Add window.postMessage support. --- backend/chainlit/callbacks.py | 16 ++++++++++++++++ backend/chainlit/config.py | 1 + backend/chainlit/socket.py | 11 +++++++++++ frontend/src/AppWrapper.tsx | 11 ++++++++++- libs/react-client/src/useChatInteract.ts | 8 ++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) diff --git a/backend/chainlit/callbacks.py b/backend/chainlit/callbacks.py index 02c6feb124..53f2b0c72a 100644 --- a/backend/chainlit/callbacks.py +++ b/backend/chainlit/callbacks.py @@ -123,6 +123,22 @@ async def with_parent_id(message: Message): return func +@trace +def on_window_message(func: Callable[[str], Any]) -> Callable: + """ + Hook to react to javascript postMessage events coming from the UI. + + Args: + func (Callable[[str], Any]): The function to be called when a window message is received. + Takes the message content as a string parameter. + + Returns: + Callable[[str], Any]: The decorated on_window_message function. + """ + config.code.on_window_message = wrap_user_function(func) + return func + + @trace def on_chat_start(func: Callable) -> Callable: """ diff --git a/backend/chainlit/config.py b/backend/chainlit/config.py index ad9c1aaf88..3f8d359d0a 100644 --- a/backend/chainlit/config.py +++ b/backend/chainlit/config.py @@ -284,6 +284,7 @@ class CodeSettings: on_chat_end: Optional[Callable[[], Any]] = None on_chat_resume: Optional[Callable[["ThreadDict"], Any]] = None on_message: Optional[Callable[["Message"], Any]] = None + on_window_message: Optional[Callable[[str], Any]] = None on_audio_start: Optional[Callable[[], Any]] = None on_audio_chunk: Optional[Callable[["InputAudioChunk"], Any]] = None on_audio_end: Optional[Callable[[], Any]] = None diff --git a/backend/chainlit/socket.py b/backend/chainlit/socket.py index 4cfc42fa9a..02d1c23395 100644 --- a/backend/chainlit/socket.py +++ b/backend/chainlit/socket.py @@ -313,6 +313,17 @@ async def message(sid, payload: MessagePayload): session.current_task = task +@sio.on("window_message") +async def window_message(sid, data): + """Handle a message send by the host window.""" + session = WebsocketSession.require(sid) + + init_ws_context(session) + + if config.code.on_window_message: + await config.code.on_window_message(data) + + @sio.on("audio_start") async def audio_start(sid): """Handle audio init.""" diff --git a/frontend/src/AppWrapper.tsx b/frontend/src/AppWrapper.tsx index b32f10abea..d7d249cbaa 100644 --- a/frontend/src/AppWrapper.tsx +++ b/frontend/src/AppWrapper.tsx @@ -3,12 +3,13 @@ import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import getRouterBasename from 'utils/router'; -import { useApi, useAuth, useConfig } from '@chainlit/react-client'; +import { useApi, useAuth, useChatInteract, useConfig } from '@chainlit/react-client'; export default function AppWrapper() { const { isAuthenticated, isReady } = useAuth(); const { language: languageInUse } = useConfig(); const { i18n } = useTranslation(); + const { windowMessage } = useChatInteract(); function handleChangeLanguage(languageBundle: any): void { i18n.addResourceBundle(languageInUse, 'translation', languageBundle); @@ -33,6 +34,14 @@ export default function AppWrapper() { handleChangeLanguage(translations.translation); }, [translations]); + useEffect(() => { + const handleWindowMessage = (event: MessageEvent) => { + windowMessage(event.data); + } + window.addEventListener('message', handleWindowMessage); + return () => window.removeEventListener('message', handleWindowMessage); + }, [windowMessage]); + if (!isReady) { return null; } diff --git a/libs/react-client/src/useChatInteract.ts b/libs/react-client/src/useChatInteract.ts index ab646de970..d7fe4bee33 100644 --- a/libs/react-client/src/useChatInteract.ts +++ b/libs/react-client/src/useChatInteract.ts @@ -90,6 +90,13 @@ const useChatInteract = () => { [session?.socket] ); + const windowMessage = useCallback( + (data: any) => { + session?.socket.emit('window_message', data); + }, + [session?.socket] + ); + const startAudioStream = useCallback(() => { session?.socket.emit('audio_start'); }, [session?.socket]); @@ -185,6 +192,7 @@ const useChatInteract = () => { replyMessage, sendMessage, editMessage, + windowMessage, startAudioStream, sendAudioChunk, endAudioStream, From 3b9665069727e49a22d69de0e469d5da812263a7 Mon Sep 17 00:00:00 2001 From: mauanga Date: Fri, 6 Sep 2024 02:02:54 +0200 Subject: [PATCH 02/13] Add support for 2-way messaging --- backend/chainlit/__init__.py | 3 +++ backend/chainlit/callbacks.py | 13 +++++++++++++ backend/chainlit/emitter.py | 4 ++++ backend/chainlit/socket.py | 4 ---- libs/react-client/src/useChatSession.ts | 6 ++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/backend/chainlit/__init__.py b/backend/chainlit/__init__.py index 635a2d2cd9..0e264fe5a4 100644 --- a/backend/chainlit/__init__.py +++ b/backend/chainlit/__init__.py @@ -65,6 +65,7 @@ on_chat_start, on_logout, on_message, + on_window_message, on_settings_update, on_stop, password_auth_callback, @@ -149,6 +150,8 @@ def acall(self): "CompletionGeneration", "GenerationMessage", "on_logout", + "on_window_message", + "send_window_message", "on_chat_start", "on_chat_end", "on_chat_resume", diff --git a/backend/chainlit/callbacks.py b/backend/chainlit/callbacks.py index 53f2b0c72a..83bdc3e201 100644 --- a/backend/chainlit/callbacks.py +++ b/backend/chainlit/callbacks.py @@ -1,8 +1,10 @@ +import asyncio import inspect from typing import Any, Awaitable, Callable, Dict, List, Optional from chainlit.action import Action from chainlit.config import config +from chainlit.context import context from chainlit.message import Message from chainlit.oauth_providers import get_configured_oauth_providers from chainlit.step import Step, step @@ -123,6 +125,17 @@ async def with_parent_id(message: Message): return func +@trace +def send_window_message(data: Any): + """ + Send custom data to the host window via a window.postMessage event. + + Args: + data (Any): The data to send with the event. + """ + asyncio.create_task(context.emitter.send_window_message(data)) + + @trace def on_window_message(func: Callable[[str], Any]) -> Callable: """ diff --git a/backend/chainlit/emitter.py b/backend/chainlit/emitter.py index df8a78e9f4..ba96c63537 100644 --- a/backend/chainlit/emitter.py +++ b/backend/chainlit/emitter.py @@ -392,3 +392,7 @@ def send_action_response( return self.emit( "action_response", {"id": id, "status": status, "response": response} ) + + def send_window_message(self, data: Any): + """Send custom data to the host window.""" + return self.emit("window_message", data) diff --git a/backend/chainlit/socket.py b/backend/chainlit/socket.py index 02d1c23395..ef01b48490 100644 --- a/backend/chainlit/socket.py +++ b/backend/chainlit/socket.py @@ -316,10 +316,6 @@ async def message(sid, payload: MessagePayload): @sio.on("window_message") async def window_message(sid, data): """Handle a message send by the host window.""" - session = WebsocketSession.require(sid) - - init_ws_context(session) - if config.code.on_window_message: await config.code.on_window_message(data) diff --git a/libs/react-client/src/useChatSession.ts b/libs/react-client/src/useChatSession.ts index 94f8d74eb5..6aa05e1964 100644 --- a/libs/react-client/src/useChatSession.ts +++ b/libs/react-client/src/useChatSession.ts @@ -359,6 +359,12 @@ const useChatSession = () => { socket.on('token_usage', (count: number) => { setTokenCount((old) => old + count); }); + + socket.on('window_message', (data: any) => { + if (window.parent && window.parent !== window) { + window.parent.postMessage(data, '*'); + } + }); }, [setSession, sessionId, chatProfile] ); From d21f7fcdca26a411b2a45c4141a8898d3d97fd9a Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Mon, 21 Oct 2024 14:41:13 -0400 Subject: [PATCH 03/13] Add missing function to BaseChainlitEmitter --- backend/chainlit/emitter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/chainlit/emitter.py b/backend/chainlit/emitter.py index ba96c63537..1be0f245ad 100644 --- a/backend/chainlit/emitter.py +++ b/backend/chainlit/emitter.py @@ -133,6 +133,9 @@ async def send_action_response( """Send an action response to the UI.""" pass + async def send_window_message(self, data: Any): + """Stub method to send custom data to the host window.""" + pass class ChainlitEmitter(BaseChainlitEmitter): """ From 909a8358ca2e798433430fab0693f912d222db7f Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Mon, 21 Oct 2024 14:41:53 -0400 Subject: [PATCH 04/13] Initialize session context for window messages --- backend/chainlit/socket.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/chainlit/socket.py b/backend/chainlit/socket.py index ef01b48490..2a1e46cc5f 100644 --- a/backend/chainlit/socket.py +++ b/backend/chainlit/socket.py @@ -316,9 +316,18 @@ async def message(sid, payload: MessagePayload): @sio.on("window_message") async def window_message(sid, data): """Handle a message send by the host window.""" - if config.code.on_window_message: - await config.code.on_window_message(data) + session = WebsocketSession.require(sid) + context = init_ws_context(session) + await context.emitter.task_start() + + if config.code.on_window_message: + try: + await config.code.on_window_message(data) + except asyncio.CancelledError: + pass + finally: + await context.emitter.task_end() @sio.on("audio_start") async def audio_start(sid): From 2c3e0ce6f78742e60003ebf94cfb5684e340727a Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Tue, 22 Oct 2024 11:54:08 -0400 Subject: [PATCH 05/13] Always postMessage to the parent window if valid --- libs/react-client/src/useChatSession.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/react-client/src/useChatSession.ts b/libs/react-client/src/useChatSession.ts index 6aa05e1964..35e2289c9e 100644 --- a/libs/react-client/src/useChatSession.ts +++ b/libs/react-client/src/useChatSession.ts @@ -361,7 +361,7 @@ const useChatSession = () => { }); socket.on('window_message', (data: any) => { - if (window.parent && window.parent !== window) { + if (window.parent) { window.parent.postMessage(data, '*'); } }); From 2315ff20cffd13108f427f5321f6361fd7c192f9 Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Tue, 22 Oct 2024 12:06:16 -0400 Subject: [PATCH 06/13] Add test for sending and receiving window messages --- cypress/e2e/window_message/main.py | 12 ++++++++ cypress/e2e/window_message/public/iframe.html | 18 ++++++++++++ cypress/e2e/window_message/spec.cy.ts | 28 +++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 cypress/e2e/window_message/main.py create mode 100644 cypress/e2e/window_message/public/iframe.html create mode 100644 cypress/e2e/window_message/spec.cy.ts diff --git a/cypress/e2e/window_message/main.py b/cypress/e2e/window_message/main.py new file mode 100644 index 0000000000..3aac183ada --- /dev/null +++ b/cypress/e2e/window_message/main.py @@ -0,0 +1,12 @@ +import chainlit as cl + + +@cl.on_window_message +async def window_message(message: str): + if message.startswith("Client: "): + cl.send_window_message("Server: World") + + +@cl.on_message +async def message(message: str): + await cl.Message(content="ok").send() diff --git a/cypress/e2e/window_message/public/iframe.html b/cypress/e2e/window_message/public/iframe.html new file mode 100644 index 0000000000..9272f2c1fd --- /dev/null +++ b/cypress/e2e/window_message/public/iframe.html @@ -0,0 +1,18 @@ + + + + Chainlit iframe + + +

Chainlit iframe

+ +
No message received
+ + + diff --git a/cypress/e2e/window_message/spec.cy.ts b/cypress/e2e/window_message/spec.cy.ts new file mode 100644 index 0000000000..6a1643d4cd --- /dev/null +++ b/cypress/e2e/window_message/spec.cy.ts @@ -0,0 +1,28 @@ +import { runTestServer } from '../../support/testUtils'; + +const getIframeWindow = () => { + return cy + .get('iframe[data-cy="the-frame"]') + .its('0.contentWindow') + .should('exist'); +}; + +describe('Window Message', () => { + before(() => { + runTestServer(); + }); + + it('should be able to send and receive window messages', () => { + cy.visit('/public/iframe.html'); + + cy.get('div#message').should('contain', 'No message received'); + + getIframeWindow().then((win) => { + cy.wait(1000).then(() => { + win.postMessage('Client: Hello', '*'); + }); + }); + + cy.get('div#message').should('contain', 'Server: World'); + }); +}); From 060b9fd635f84014e14bd0748bbbebbe4cf32aea Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Tue, 22 Oct 2024 16:16:38 -0400 Subject: [PATCH 07/13] Add missing import of send_window_message --- backend/chainlit/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/chainlit/__init__.py b/backend/chainlit/__init__.py index 0e264fe5a4..77a2152fdf 100644 --- a/backend/chainlit/__init__.py +++ b/backend/chainlit/__init__.py @@ -44,7 +44,7 @@ ) 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 @@ -57,18 +57,19 @@ author_rename, 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, on_logout, on_message, - on_window_message, on_settings_update, on_stop, + on_window_message, password_auth_callback, + send_window_message, set_chat_profiles, set_starters, ) From d69ea3ee8d24abb606eec5df0a0bac75f7dd3ea2 Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Tue, 12 Nov 2024 16:32:18 -0500 Subject: [PATCH 08/13] Fix lint issue I001 in emitter.py --- backend/chainlit/emitter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/chainlit/emitter.py b/backend/chainlit/emitter.py index 1be0f245ad..34049815f4 100644 --- a/backend/chainlit/emitter.py +++ b/backend/chainlit/emitter.py @@ -2,6 +2,9 @@ import uuid from typing import Any, Dict, List, Literal, Optional, Union, cast +from literalai.helper import utc_now +from socketio.exceptions import TimeoutError + from chainlit.chat_context import chat_context from chainlit.config import config from chainlit.data import get_data_layer @@ -16,12 +19,10 @@ FileDict, FileReference, MessagePayload, + OutputAudioChunk, ThreadDict, - OutputAudioChunk ) from chainlit.user import PersistedUser -from literalai.helper import utc_now -from socketio.exceptions import TimeoutError class BaseChainlitEmitter: From 0361ebcad0c53558608c6b800d7a0c37de890288 Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Tue, 12 Nov 2024 16:38:08 -0500 Subject: [PATCH 09/13] Fix formatting --- backend/chainlit/emitter.py | 11 ++++++----- backend/chainlit/socket.py | 17 +++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/backend/chainlit/emitter.py b/backend/chainlit/emitter.py index 1be0f245ad..8b8af90ad9 100644 --- a/backend/chainlit/emitter.py +++ b/backend/chainlit/emitter.py @@ -16,8 +16,8 @@ FileDict, FileReference, MessagePayload, + OutputAudioChunk, ThreadDict, - OutputAudioChunk ) from chainlit.user import PersistedUser from literalai.helper import utc_now @@ -52,15 +52,15 @@ async def resume_thread(self, thread_dict: ThreadDict): async def send_element(self, element_dict: ElementDict): """Stub method to send an element to the UI.""" pass - + async def update_audio_connection(self, state: Literal["on", "off"]): """Audio connection signaling.""" pass - + async def send_audio_chunk(self, chunk: OutputAudioChunk): """Stub method to send an audio chunk to the UI.""" pass - + async def send_audio_interrupt(self): """Stub method to interrupt the current audio response.""" pass @@ -137,6 +137,7 @@ async def send_window_message(self, data: Any): """Stub method to send custom data to the host window.""" pass + class ChainlitEmitter(BaseChainlitEmitter): """ Chainlit Emitter class. The Emitter is not directly exposed to the developer. @@ -180,7 +181,7 @@ async def update_audio_connection(self, state: Literal["on", "off"]): async def send_audio_chunk(self, chunk: OutputAudioChunk): """Send an audio chunk to the UI.""" await self.emit("audio_chunk", chunk) - + async def send_audio_interrupt(self): """Method to interrupt the current audio response.""" await self.emit("audio_interrupt", {}) diff --git a/backend/chainlit/socket.py b/backend/chainlit/socket.py index 2a1e46cc5f..064c4c2189 100644 --- a/backend/chainlit/socket.py +++ b/backend/chainlit/socket.py @@ -17,11 +17,7 @@ from chainlit.server import sio from chainlit.session import WebsocketSession from chainlit.telemetry import trace_event -from chainlit.types import ( - InputAudioChunk, - InputAudioChunkPayload, - MessagePayload, -) +from chainlit.types import InputAudioChunk, InputAudioChunkPayload, MessagePayload from chainlit.user_session import user_sessions @@ -329,6 +325,7 @@ async def window_message(sid, data): finally: await context.emitter.task_end() + @sio.on("audio_start") async def audio_start(sid): """Handle audio init.""" @@ -336,10 +333,10 @@ async def audio_start(sid): context = init_ws_context(session) if config.code.on_audio_start: - connected = bool(await config.code.on_audio_start()) - connection_state = "on" if connected else "off" - await context.emitter.update_audio_connection(connection_state) - + connected = bool(await config.code.on_audio_start()) + connection_state = "on" if connected else "off" + await context.emitter.update_audio_connection(connection_state) + @sio.on("audio_chunk") async def audio_chunk(sid, payload: InputAudioChunkPayload): @@ -366,7 +363,7 @@ async def audio_end(sid): if config.code.on_audio_end: await config.code.on_audio_end() - + except asyncio.CancelledError: pass except Exception as e: From 2d6a1897a4240ae01cd83c041f73f8eb51ac5f8d Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Tue, 12 Nov 2024 16:43:38 -0500 Subject: [PATCH 10/13] Format emitter.py Trying to commit this locally fails because the pre-commit trigger changes to imports that then fail I001. --- backend/chainlit/emitter.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/chainlit/emitter.py b/backend/chainlit/emitter.py index 34049815f4..5cc6a905b6 100644 --- a/backend/chainlit/emitter.py +++ b/backend/chainlit/emitter.py @@ -53,15 +53,15 @@ async def resume_thread(self, thread_dict: ThreadDict): async def send_element(self, element_dict: ElementDict): """Stub method to send an element to the UI.""" pass - + async def update_audio_connection(self, state: Literal["on", "off"]): """Audio connection signaling.""" pass - + async def send_audio_chunk(self, chunk: OutputAudioChunk): """Stub method to send an audio chunk to the UI.""" pass - + async def send_audio_interrupt(self): """Stub method to interrupt the current audio response.""" pass @@ -138,6 +138,7 @@ async def send_window_message(self, data: Any): """Stub method to send custom data to the host window.""" pass + class ChainlitEmitter(BaseChainlitEmitter): """ Chainlit Emitter class. The Emitter is not directly exposed to the developer. @@ -181,7 +182,7 @@ async def update_audio_connection(self, state: Literal["on", "off"]): async def send_audio_chunk(self, chunk: OutputAudioChunk): """Send an audio chunk to the UI.""" await self.emit("audio_chunk", chunk) - + async def send_audio_interrupt(self): """Method to interrupt the current audio response.""" await self.emit("audio_interrupt", {}) From 1626e7dde94c28ddacb3369281cd71e983f12b62 Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Tue, 12 Nov 2024 16:44:00 -0500 Subject: [PATCH 11/13] Format socket.py --- backend/chainlit/socket.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/backend/chainlit/socket.py b/backend/chainlit/socket.py index 2a1e46cc5f..064c4c2189 100644 --- a/backend/chainlit/socket.py +++ b/backend/chainlit/socket.py @@ -17,11 +17,7 @@ from chainlit.server import sio from chainlit.session import WebsocketSession from chainlit.telemetry import trace_event -from chainlit.types import ( - InputAudioChunk, - InputAudioChunkPayload, - MessagePayload, -) +from chainlit.types import InputAudioChunk, InputAudioChunkPayload, MessagePayload from chainlit.user_session import user_sessions @@ -329,6 +325,7 @@ async def window_message(sid, data): finally: await context.emitter.task_end() + @sio.on("audio_start") async def audio_start(sid): """Handle audio init.""" @@ -336,10 +333,10 @@ async def audio_start(sid): context = init_ws_context(session) if config.code.on_audio_start: - connected = bool(await config.code.on_audio_start()) - connection_state = "on" if connected else "off" - await context.emitter.update_audio_connection(connection_state) - + connected = bool(await config.code.on_audio_start()) + connection_state = "on" if connected else "off" + await context.emitter.update_audio_connection(connection_state) + @sio.on("audio_chunk") async def audio_chunk(sid, payload: InputAudioChunkPayload): @@ -366,7 +363,7 @@ async def audio_end(sid): if config.code.on_audio_end: await config.code.on_audio_end() - + except asyncio.CancelledError: pass except Exception as e: From 02e604898b95cecd2f0ccf2c86a90467493f65fc Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Thu, 14 Nov 2024 09:06:41 -0500 Subject: [PATCH 12/13] Make cl.send_window_message async --- backend/chainlit/callbacks.py | 4 ++-- cypress/e2e/window_message/main.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/chainlit/callbacks.py b/backend/chainlit/callbacks.py index c242e15d4a..3e6c5f3667 100644 --- a/backend/chainlit/callbacks.py +++ b/backend/chainlit/callbacks.py @@ -128,14 +128,14 @@ async def with_parent_id(message: Message): @trace -def send_window_message(data: Any): +async def send_window_message(data: Any): """ Send custom data to the host window via a window.postMessage event. Args: data (Any): The data to send with the event. """ - asyncio.create_task(context.emitter.send_window_message(data)) + await context.emitter.send_window_message(data) @trace diff --git a/cypress/e2e/window_message/main.py b/cypress/e2e/window_message/main.py index 3aac183ada..8843e7cb09 100644 --- a/cypress/e2e/window_message/main.py +++ b/cypress/e2e/window_message/main.py @@ -4,7 +4,7 @@ @cl.on_window_message async def window_message(message: str): if message.startswith("Client: "): - cl.send_window_message("Server: World") + await cl.send_window_message("Server: World") @cl.on_message From 58e78766deff5e2d8689fe11aef16c48356d54bd Mon Sep 17 00:00:00 2001 From: jfperusse-bhvr Date: Thu, 14 Nov 2024 09:08:18 -0500 Subject: [PATCH 13/13] Remove unused asyncio import --- backend/chainlit/callbacks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/chainlit/callbacks.py b/backend/chainlit/callbacks.py index 3e6c5f3667..106904e3d1 100644 --- a/backend/chainlit/callbacks.py +++ b/backend/chainlit/callbacks.py @@ -1,4 +1,3 @@ -import asyncio import inspect from typing import Any, Awaitable, Callable, Dict, List, Optional