chainlit 1.0.0rc2__py3-none-any.whl → 1.0.100__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of chainlit might be problematic. Click here for more details.
- chainlit/__init__.py +14 -1
- chainlit/auth.py +4 -3
- chainlit/config.py +2 -6
- chainlit/data/__init__.py +103 -25
- chainlit/element.py +4 -1
- chainlit/emitter.py +26 -21
- chainlit/frontend/dist/assets/index-c4f40824.js +723 -0
- chainlit/frontend/dist/assets/{react-plotly-ae26362f.js → react-plotly-259d6961.js} +1 -1
- chainlit/frontend/dist/index.html +1 -1
- chainlit/haystack/callbacks.py +32 -6
- chainlit/langchain/callbacks.py +8 -6
- chainlit/llama_index/callbacks.py +13 -5
- chainlit/message.py +1 -1
- chainlit/oauth_providers.py +67 -0
- chainlit/playground/config.py +2 -0
- chainlit/playground/provider.py +1 -1
- chainlit/playground/providers/__init__.py +1 -0
- chainlit/playground/providers/anthropic.py +1 -1
- chainlit/playground/providers/langchain.py +8 -7
- chainlit/playground/providers/openai.py +0 -2
- chainlit/playground/providers/vertexai.py +51 -6
- chainlit/server.py +35 -15
- chainlit/session.py +6 -2
- chainlit/socket.py +59 -20
- chainlit/step.py +4 -4
- chainlit/telemetry.py +2 -6
- chainlit/types.py +1 -1
- {chainlit-1.0.0rc2.dist-info → chainlit-1.0.100.dist-info}/METADATA +3 -3
- chainlit-1.0.100.dist-info/RECORD +60 -0
- chainlit/frontend/dist/assets/index-aac0232b.js +0 -697
- chainlit-1.0.0rc2.dist-info/RECORD +0 -60
- {chainlit-1.0.0rc2.dist-info → chainlit-1.0.100.dist-info}/WHEEL +0 -0
- {chainlit-1.0.0rc2.dist-info → chainlit-1.0.100.dist-info}/entry_points.txt +0 -0
chainlit/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@ env_found = load_dotenv(dotenv_path=os.path.join(os.getcwd(), ".env"))
|
|
|
7
7
|
import asyncio
|
|
8
8
|
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional
|
|
9
9
|
|
|
10
|
+
from fastapi import Request, Response
|
|
10
11
|
from starlette.datastructures import Headers
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
@@ -54,7 +55,7 @@ from chainlit.user import PersistedUser, User
|
|
|
54
55
|
from chainlit.user_session import user_session
|
|
55
56
|
from chainlit.utils import make_module_getattr, wrap_user_function
|
|
56
57
|
from chainlit.version import __version__
|
|
57
|
-
from
|
|
58
|
+
from literalai import ChatGeneration, CompletionGeneration, GenerationMessage
|
|
58
59
|
|
|
59
60
|
if env_found:
|
|
60
61
|
logger.info("Loaded .env file")
|
|
@@ -127,6 +128,17 @@ def oauth_callback(
|
|
|
127
128
|
return func
|
|
128
129
|
|
|
129
130
|
|
|
131
|
+
@trace
|
|
132
|
+
def on_logout(func: Callable[[Request, Response], Any]) -> Callable:
|
|
133
|
+
"""
|
|
134
|
+
Function called when the user logs out.
|
|
135
|
+
Takes the FastAPI request and response as parameters.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
config.code.on_logout = wrap_user_function(func)
|
|
139
|
+
return func
|
|
140
|
+
|
|
141
|
+
|
|
130
142
|
@trace
|
|
131
143
|
def on_message(func: Callable) -> Callable:
|
|
132
144
|
"""
|
|
@@ -320,6 +332,7 @@ __all__ = [
|
|
|
320
332
|
"ChatGeneration",
|
|
321
333
|
"CompletionGeneration",
|
|
322
334
|
"GenerationMessage",
|
|
335
|
+
"on_logout",
|
|
323
336
|
"on_chat_start",
|
|
324
337
|
"on_chat_end",
|
|
325
338
|
"on_chat_resume",
|
chainlit/auth.py
CHANGED
|
@@ -20,7 +20,7 @@ def get_jwt_secret():
|
|
|
20
20
|
def ensure_jwt_secret():
|
|
21
21
|
if require_login() and get_jwt_secret() is None:
|
|
22
22
|
raise ValueError(
|
|
23
|
-
"You must provide a JWT secret in the environment to use
|
|
23
|
+
"You must provide a JWT secret in the environment to use authentication. Run `chainlit create-secret` to generate one."
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
|
|
@@ -30,7 +30,8 @@ def is_oauth_enabled():
|
|
|
30
30
|
|
|
31
31
|
def require_login():
|
|
32
32
|
return (
|
|
33
|
-
|
|
33
|
+
bool(os.environ.get("CHAINLIT_CUSTOM_AUTH"))
|
|
34
|
+
or config.code.password_auth_callback is not None
|
|
34
35
|
or config.code.header_auth_callback is not None
|
|
35
36
|
or is_oauth_enabled()
|
|
36
37
|
)
|
|
@@ -74,7 +75,7 @@ async def authenticate_user(token: str = Depends(reuseable_oauth)):
|
|
|
74
75
|
try:
|
|
75
76
|
persisted_user = await data_layer.get_user(user.identifier)
|
|
76
77
|
except Exception as e:
|
|
77
|
-
|
|
78
|
+
return user
|
|
78
79
|
if persisted_user == None:
|
|
79
80
|
raise HTTPException(status_code=401, detail="User does not exist")
|
|
80
81
|
|
chainlit/config.py
CHANGED
|
@@ -15,6 +15,7 @@ if TYPE_CHECKING:
|
|
|
15
15
|
from chainlit.action import Action
|
|
16
16
|
from chainlit.types import ChatProfile, ThreadDict
|
|
17
17
|
from chainlit.user import User
|
|
18
|
+
from fastapi import Request, Response
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
BACKEND_ROOT = os.path.dirname(__file__)
|
|
@@ -118,8 +119,6 @@ hide_cot = false
|
|
|
118
119
|
generated_by = "{__version__}"
|
|
119
120
|
"""
|
|
120
121
|
|
|
121
|
-
chainlit_prod_url = os.environ.get("CHAINLIT_PROD_URL")
|
|
122
|
-
|
|
123
122
|
|
|
124
123
|
DEFAULT_HOST = "0.0.0.0"
|
|
125
124
|
DEFAULT_PORT = 8000
|
|
@@ -200,6 +199,7 @@ class CodeSettings:
|
|
|
200
199
|
oauth_callback: Optional[
|
|
201
200
|
Callable[[str, str, Dict[str, str], "User"], Optional["User"]]
|
|
202
201
|
] = None
|
|
202
|
+
on_logout: Optional[Callable[["Request", "Response"], Any]] = None
|
|
203
203
|
on_stop: Optional[Callable[[], Any]] = None
|
|
204
204
|
on_chat_start: Optional[Callable[[], Any]] = None
|
|
205
205
|
on_chat_end: Optional[Callable[[], Any]] = None
|
|
@@ -234,9 +234,6 @@ class ChainlitConfig:
|
|
|
234
234
|
root = APP_ROOT
|
|
235
235
|
# Chainlit server URL. Used only for cloud features
|
|
236
236
|
chainlit_server: str
|
|
237
|
-
# The url of the deployed app. Only set if the app is deployed.
|
|
238
|
-
chainlit_prod_url = chainlit_prod_url
|
|
239
|
-
|
|
240
237
|
run: RunSettings
|
|
241
238
|
features: FeaturesSettings
|
|
242
239
|
ui: UISettings
|
|
@@ -347,7 +344,6 @@ def load_config():
|
|
|
347
344
|
|
|
348
345
|
config = ChainlitConfig(
|
|
349
346
|
chainlit_server=chainlit_server,
|
|
350
|
-
chainlit_prod_url=chainlit_prod_url,
|
|
351
347
|
run=RunSettings(),
|
|
352
348
|
**settings,
|
|
353
349
|
)
|
chainlit/data/__init__.py
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import os
|
|
3
3
|
from collections import deque
|
|
4
|
-
from typing import TYPE_CHECKING, Dict, List, Optional
|
|
4
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
|
5
5
|
|
|
6
|
+
import aiofiles
|
|
6
7
|
from chainlit.config import config
|
|
7
8
|
from chainlit.context import context
|
|
8
9
|
from chainlit.logger import logger
|
|
9
10
|
from chainlit.session import WebsocketSession
|
|
10
11
|
from chainlit.types import Feedback, Pagination, ThreadDict, ThreadFilter
|
|
11
12
|
from chainlit.user import PersistedUser, User, UserDict
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
13
|
+
from literalai import Attachment
|
|
14
|
+
from literalai import Feedback as ClientFeedback
|
|
15
|
+
from literalai import PageInfo, PaginatedResponse
|
|
16
|
+
from literalai import Step as ClientStep
|
|
17
|
+
from literalai.thread import NumberListFilter, StringFilter, StringListFilter
|
|
18
|
+
from literalai.thread import ThreadFilter as ClientThreadFilter
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
21
|
from chainlit.element import Element, ElementDict
|
|
@@ -29,7 +30,7 @@ def queue_until_user_message():
|
|
|
29
30
|
async def wrapper(self, *args, **kwargs):
|
|
30
31
|
if (
|
|
31
32
|
isinstance(context.session, WebsocketSession)
|
|
32
|
-
and not context.session.
|
|
33
|
+
and not context.session.has_first_interaction
|
|
33
34
|
):
|
|
34
35
|
# Queue the method invocation waiting for the first user message
|
|
35
36
|
queues = context.session.thread_queues
|
|
@@ -112,14 +113,29 @@ class BaseDataLayer:
|
|
|
112
113
|
):
|
|
113
114
|
pass
|
|
114
115
|
|
|
116
|
+
async def create_user_session(
|
|
117
|
+
self,
|
|
118
|
+
id: str,
|
|
119
|
+
started_at: str,
|
|
120
|
+
anon_user_id: str,
|
|
121
|
+
user_id: Optional[str],
|
|
122
|
+
) -> Dict:
|
|
123
|
+
return {}
|
|
124
|
+
|
|
125
|
+
async def update_user_session(
|
|
126
|
+
self, id: str, is_interactive: bool, ended_at: Optional[str]
|
|
127
|
+
) -> Dict:
|
|
128
|
+
return {}
|
|
129
|
+
|
|
130
|
+
async def delete_user_session(self, id: str) -> bool:
|
|
131
|
+
return True
|
|
132
|
+
|
|
115
133
|
|
|
116
134
|
class ChainlitDataLayer:
|
|
117
|
-
def __init__(
|
|
118
|
-
|
|
119
|
-
):
|
|
120
|
-
from chainlit_client import ChainlitClient
|
|
135
|
+
def __init__(self, api_key: str, server: Optional[str]):
|
|
136
|
+
from literalai import LiteralClient
|
|
121
137
|
|
|
122
|
-
self.client =
|
|
138
|
+
self.client = LiteralClient(api_key=api_key, url=server)
|
|
123
139
|
logger.info("Chainlit data layer initialized")
|
|
124
140
|
|
|
125
141
|
def attachment_to_element_dict(self, attachment: Attachment) -> "ElementDict":
|
|
@@ -128,6 +144,7 @@ class ChainlitDataLayer:
|
|
|
128
144
|
"chainlitKey": None,
|
|
129
145
|
"display": metadata.get("display", "side"),
|
|
130
146
|
"language": metadata.get("language"),
|
|
147
|
+
"page": metadata.get("page"),
|
|
131
148
|
"size": metadata.get("size"),
|
|
132
149
|
"type": metadata.get("type", "file"),
|
|
133
150
|
"forId": attachment.step_id,
|
|
@@ -193,6 +210,8 @@ class ChainlitDataLayer:
|
|
|
193
210
|
_user = await self.client.api.create_user(
|
|
194
211
|
identifier=user.identifier, metadata=user.metadata
|
|
195
212
|
)
|
|
213
|
+
elif _user.id:
|
|
214
|
+
await self.client.api.update_user(id=_user.id, metadata=user.metadata)
|
|
196
215
|
return PersistedUser(
|
|
197
216
|
id=_user.id or "",
|
|
198
217
|
identifier=_user.identifier or "",
|
|
@@ -230,17 +249,44 @@ class ChainlitDataLayer:
|
|
|
230
249
|
"language": element.language,
|
|
231
250
|
"display": element.display,
|
|
232
251
|
"type": element.type,
|
|
252
|
+
"page": getattr(element, "page", None),
|
|
233
253
|
}
|
|
234
254
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
255
|
+
if not element.for_id:
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
object_key = None
|
|
259
|
+
|
|
260
|
+
if not element.url:
|
|
261
|
+
if element.path:
|
|
262
|
+
async with aiofiles.open(element.path, "rb") as f:
|
|
263
|
+
content = await f.read() # type: Union[bytes, str]
|
|
264
|
+
elif element.content:
|
|
265
|
+
content = element.content
|
|
266
|
+
else:
|
|
267
|
+
raise ValueError("Either path or content must be provided")
|
|
268
|
+
uploaded = await self.client.api.upload_file(
|
|
269
|
+
content=content, mime=element.mime, thread_id=element.thread_id
|
|
270
|
+
)
|
|
271
|
+
object_key = uploaded["object_key"]
|
|
272
|
+
|
|
273
|
+
await self.client.api.send_steps(
|
|
274
|
+
[
|
|
275
|
+
{
|
|
276
|
+
"id": element.for_id,
|
|
277
|
+
"threadId": element.thread_id,
|
|
278
|
+
"attachments": [
|
|
279
|
+
{
|
|
280
|
+
"id": element.id,
|
|
281
|
+
"name": element.name,
|
|
282
|
+
"metadata": metadata,
|
|
283
|
+
"mime": element.mime,
|
|
284
|
+
"url": element.url,
|
|
285
|
+
"objectKey": object_key,
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
}
|
|
289
|
+
]
|
|
244
290
|
)
|
|
245
291
|
|
|
246
292
|
async def get_element(
|
|
@@ -337,6 +383,8 @@ class ChainlitDataLayer:
|
|
|
337
383
|
continue
|
|
338
384
|
for attachment in step.attachments:
|
|
339
385
|
elements.append(self.attachment_to_element_dict(attachment))
|
|
386
|
+
if not config.features.prompt_playground and step.generation:
|
|
387
|
+
step.generation = None
|
|
340
388
|
steps.append(self.step_to_step_dict(step))
|
|
341
389
|
|
|
342
390
|
user = None # type: Optional["UserDict"]
|
|
@@ -372,10 +420,40 @@ class ChainlitDataLayer:
|
|
|
372
420
|
tags=tags,
|
|
373
421
|
)
|
|
374
422
|
|
|
423
|
+
async def create_user_session(
|
|
424
|
+
self,
|
|
425
|
+
id: str,
|
|
426
|
+
started_at: str,
|
|
427
|
+
anon_user_id: str,
|
|
428
|
+
user_id: Optional[str],
|
|
429
|
+
) -> Dict:
|
|
430
|
+
existing_session = await self.client.api.get_user_session(id=id)
|
|
431
|
+
if existing_session:
|
|
432
|
+
return existing_session
|
|
433
|
+
session = await self.client.api.create_user_session(
|
|
434
|
+
id=id,
|
|
435
|
+
started_at=started_at,
|
|
436
|
+
participant_identifier=user_id,
|
|
437
|
+
anon_participant_identifier=anon_user_id,
|
|
438
|
+
)
|
|
439
|
+
return session
|
|
440
|
+
|
|
441
|
+
async def update_user_session(
|
|
442
|
+
self, id: str, is_interactive: bool, ended_at: Optional[str]
|
|
443
|
+
) -> Dict:
|
|
444
|
+
session = await self.client.api.update_user_session(
|
|
445
|
+
id=id, is_interactive=is_interactive, ended_at=ended_at
|
|
446
|
+
)
|
|
447
|
+
return session
|
|
448
|
+
|
|
449
|
+
async def delete_user_session(self, id: str) -> bool:
|
|
450
|
+
await self.client.api.delete_user_session(id=id)
|
|
451
|
+
return True
|
|
452
|
+
|
|
375
453
|
|
|
376
|
-
if api_key := os.environ.get("
|
|
377
|
-
|
|
378
|
-
_data_layer = ChainlitDataLayer(api_key=api_key,
|
|
454
|
+
if api_key := os.environ.get("LITERAL_API_KEY"):
|
|
455
|
+
server = os.environ.get("LITERAL_SERVER")
|
|
456
|
+
_data_layer = ChainlitDataLayer(api_key=api_key, server=server)
|
|
379
457
|
|
|
380
458
|
|
|
381
459
|
def get_data_layer():
|
chainlit/element.py
CHANGED
|
@@ -37,6 +37,7 @@ class ElementDict(TypedDict):
|
|
|
37
37
|
display: ElementDisplay
|
|
38
38
|
size: Optional[ElementSize]
|
|
39
39
|
language: Optional[str]
|
|
40
|
+
page: Optional[int]
|
|
40
41
|
forId: Optional[str]
|
|
41
42
|
mime: Optional[str]
|
|
42
43
|
|
|
@@ -46,7 +47,7 @@ class Element:
|
|
|
46
47
|
# The type of the element. This will be used to determine how to display the element in the UI.
|
|
47
48
|
type: ClassVar[ElementType]
|
|
48
49
|
# Name of the element, this will be used to reference the element in the UI.
|
|
49
|
-
name: str
|
|
50
|
+
name: str = ""
|
|
50
51
|
# The ID of the element. This is set automatically when the element is sent to the UI.
|
|
51
52
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
52
53
|
# The key of the element hosted on Chainlit.
|
|
@@ -91,6 +92,7 @@ class Element:
|
|
|
91
92
|
"display": self.display,
|
|
92
93
|
"objectKey": getattr(self, "object_key", None),
|
|
93
94
|
"size": getattr(self, "size", None),
|
|
95
|
+
"page": getattr(self, "page", None),
|
|
94
96
|
"language": getattr(self, "language", None),
|
|
95
97
|
"forId": getattr(self, "for_id", None),
|
|
96
98
|
"mime": getattr(self, "mime", None),
|
|
@@ -201,6 +203,7 @@ class Text(Element):
|
|
|
201
203
|
class Pdf(Element):
|
|
202
204
|
"""Useful to send a pdf to the UI."""
|
|
203
205
|
|
|
206
|
+
page: Optional[int] = None
|
|
204
207
|
type: ClassVar[ElementType] = "pdf"
|
|
205
208
|
|
|
206
209
|
|
chainlit/emitter.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional, Union, cast
|
|
|
5
5
|
|
|
6
6
|
from chainlit.data import get_data_layer
|
|
7
7
|
from chainlit.element import Element, File
|
|
8
|
+
from chainlit.logger import logger
|
|
8
9
|
from chainlit.message import Message
|
|
9
10
|
from chainlit.session import BaseSession, WebsocketSession
|
|
10
11
|
from chainlit.step import StepDict
|
|
@@ -64,8 +65,7 @@ class BaseChainlitEmitter:
|
|
|
64
65
|
"""Stub method to clear the prompt from the UI."""
|
|
65
66
|
pass
|
|
66
67
|
|
|
67
|
-
async def init_thread(self,
|
|
68
|
-
"""Signal the UI that a new thread (with a user message) exists"""
|
|
68
|
+
async def init_thread(self, interaction: str):
|
|
69
69
|
pass
|
|
70
70
|
|
|
71
71
|
async def process_user_message(self, payload: UIMessagePayload) -> Message:
|
|
@@ -167,23 +167,25 @@ class ChainlitEmitter(BaseChainlitEmitter):
|
|
|
167
167
|
|
|
168
168
|
return self.emit("clear_ask", {})
|
|
169
169
|
|
|
170
|
-
async def flush_thread_queues(self,
|
|
170
|
+
async def flush_thread_queues(self, interaction: str):
|
|
171
171
|
if data_layer := get_data_layer():
|
|
172
172
|
if isinstance(self.session.user, PersistedUser):
|
|
173
173
|
user_id = self.session.user.id
|
|
174
174
|
else:
|
|
175
175
|
user_id = None
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
try:
|
|
177
|
+
await data_layer.update_thread(
|
|
178
|
+
thread_id=self.session.thread_id,
|
|
179
|
+
user_id=user_id,
|
|
180
|
+
metadata={"name": interaction},
|
|
181
|
+
)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"Error updating thread: {e}")
|
|
181
184
|
await self.session.flush_method_queue()
|
|
182
185
|
|
|
183
|
-
async def init_thread(self,
|
|
184
|
-
|
|
185
|
-
await self.
|
|
186
|
-
await self.emit("init_thread", step)
|
|
186
|
+
async def init_thread(self, interaction: str):
|
|
187
|
+
await self.flush_thread_queues(interaction)
|
|
188
|
+
await self.emit("first_interaction", interaction)
|
|
187
189
|
|
|
188
190
|
async def process_user_message(self, payload: UIMessagePayload):
|
|
189
191
|
step_dict = payload["message"]
|
|
@@ -197,9 +199,9 @@ class ChainlitEmitter(BaseChainlitEmitter):
|
|
|
197
199
|
|
|
198
200
|
asyncio.create_task(message._create())
|
|
199
201
|
|
|
200
|
-
if not self.session.
|
|
201
|
-
self.session.
|
|
202
|
-
asyncio.create_task(self.init_thread(message.
|
|
202
|
+
if not self.session.has_first_interaction:
|
|
203
|
+
self.session.has_first_interaction = True
|
|
204
|
+
asyncio.create_task(self.init_thread(message.content))
|
|
203
205
|
|
|
204
206
|
if file_refs:
|
|
205
207
|
files = [
|
|
@@ -239,11 +241,13 @@ class ChainlitEmitter(BaseChainlitEmitter):
|
|
|
239
241
|
] = None
|
|
240
242
|
|
|
241
243
|
if user_res:
|
|
244
|
+
interaction = None
|
|
242
245
|
if spec.type == "text":
|
|
243
246
|
message_dict_res = cast(StepDict, user_res)
|
|
244
247
|
await self.process_user_message(
|
|
245
248
|
{"message": message_dict_res, "fileReferences": None}
|
|
246
249
|
)
|
|
250
|
+
interaction = message_dict_res["output"]
|
|
247
251
|
final_res = message_dict_res
|
|
248
252
|
elif spec.type == "file":
|
|
249
253
|
file_refs = cast(List[FileReference], user_res)
|
|
@@ -253,12 +257,7 @@ class ChainlitEmitter(BaseChainlitEmitter):
|
|
|
253
257
|
if file["id"] in self.session.files
|
|
254
258
|
]
|
|
255
259
|
final_res = files
|
|
256
|
-
|
|
257
|
-
self.session.has_user_message = True
|
|
258
|
-
await self.flush_thread_queues(
|
|
259
|
-
name=",".join([file["name"] for file in files])
|
|
260
|
-
)
|
|
261
|
-
|
|
260
|
+
interaction = ",".join([file["name"] for file in files])
|
|
262
261
|
if get_data_layer():
|
|
263
262
|
coros = [
|
|
264
263
|
File(
|
|
@@ -274,6 +273,12 @@ class ChainlitEmitter(BaseChainlitEmitter):
|
|
|
274
273
|
elif spec.type == "action":
|
|
275
274
|
action_res = cast(AskActionResponse, user_res)
|
|
276
275
|
final_res = action_res
|
|
276
|
+
interaction = action_res["value"]
|
|
277
|
+
|
|
278
|
+
if not self.session.has_first_interaction and interaction:
|
|
279
|
+
self.session.has_first_interaction = True
|
|
280
|
+
await self.init_thread(interaction=interaction)
|
|
281
|
+
|
|
277
282
|
await self.clear_ask()
|
|
278
283
|
return final_res
|
|
279
284
|
except TimeoutError as e:
|