chainlit 0.7.700__py3-none-any.whl → 1.0.0rc1__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 +32 -23
- chainlit/auth.py +9 -10
- chainlit/cli/__init__.py +1 -2
- chainlit/config.py +13 -12
- chainlit/context.py +7 -3
- chainlit/data/__init__.py +375 -9
- chainlit/data/acl.py +6 -5
- chainlit/element.py +86 -123
- chainlit/emitter.py +117 -50
- chainlit/frontend/dist/assets/{index-71698725.js → index-6aee009a.js} +118 -292
- chainlit/frontend/dist/assets/{react-plotly-2c0acdf0.js → react-plotly-2f07c02a.js} +1 -1
- chainlit/frontend/dist/index.html +1 -1
- chainlit/haystack/callbacks.py +45 -43
- chainlit/hello.py +1 -1
- chainlit/langchain/callbacks.py +132 -120
- chainlit/llama_index/callbacks.py +68 -48
- chainlit/message.py +179 -207
- chainlit/oauth_providers.py +39 -34
- chainlit/playground/provider.py +44 -30
- chainlit/playground/providers/anthropic.py +4 -4
- chainlit/playground/providers/huggingface.py +2 -2
- chainlit/playground/providers/langchain.py +8 -10
- chainlit/playground/providers/openai.py +19 -13
- chainlit/server.py +155 -99
- chainlit/session.py +109 -40
- chainlit/socket.py +47 -36
- chainlit/step.py +393 -0
- chainlit/types.py +78 -21
- chainlit/user.py +32 -0
- chainlit/user_session.py +1 -5
- {chainlit-0.7.700.dist-info → chainlit-1.0.0rc1.dist-info}/METADATA +12 -31
- chainlit-1.0.0rc1.dist-info/RECORD +60 -0
- chainlit/client/base.py +0 -169
- chainlit/client/cloud.py +0 -502
- chainlit/prompt.py +0 -40
- chainlit-0.7.700.dist-info/RECORD +0 -61
- {chainlit-0.7.700.dist-info → chainlit-1.0.0rc1.dist-info}/WHEEL +0 -0
- {chainlit-0.7.700.dist-info → chainlit-1.0.0rc1.dist-info}/entry_points.txt +0 -0
chainlit/element.py
CHANGED
|
@@ -2,16 +2,14 @@ import json
|
|
|
2
2
|
import uuid
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from io import BytesIO
|
|
5
|
-
from typing import Any, ClassVar,
|
|
5
|
+
from typing import Any, ClassVar, List, Literal, Optional, TypedDict, TypeVar, Union
|
|
6
6
|
|
|
7
|
-
import aiofiles
|
|
8
7
|
import filetype
|
|
9
|
-
from chainlit.client.base import ElementDict, ElementDisplay, ElementSize, ElementType
|
|
10
|
-
from chainlit.client.cloud import ChainlitCloudClient
|
|
11
8
|
from chainlit.context import context
|
|
12
|
-
from chainlit.data import
|
|
9
|
+
from chainlit.data import get_data_layer
|
|
13
10
|
from chainlit.logger import logger
|
|
14
11
|
from chainlit.telemetry import trace_event
|
|
12
|
+
from chainlit.types import FileDict
|
|
15
13
|
from pydantic.dataclasses import Field, dataclass
|
|
16
14
|
from syncer import asyncio
|
|
17
15
|
|
|
@@ -21,16 +19,38 @@ mime_types = {
|
|
|
21
19
|
"plotly": "application/json",
|
|
22
20
|
}
|
|
23
21
|
|
|
22
|
+
ElementType = Literal[
|
|
23
|
+
"image", "avatar", "text", "pdf", "tasklist", "audio", "video", "file", "plotly"
|
|
24
|
+
]
|
|
25
|
+
ElementDisplay = Literal["inline", "side", "page"]
|
|
26
|
+
ElementSize = Literal["small", "medium", "large"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ElementDict(TypedDict):
|
|
30
|
+
id: str
|
|
31
|
+
threadId: Optional[str]
|
|
32
|
+
type: ElementType
|
|
33
|
+
chainlitKey: Optional[str]
|
|
34
|
+
url: Optional[str]
|
|
35
|
+
objectKey: Optional[str]
|
|
36
|
+
name: str
|
|
37
|
+
display: ElementDisplay
|
|
38
|
+
size: Optional[ElementSize]
|
|
39
|
+
language: Optional[str]
|
|
40
|
+
forId: Optional[str]
|
|
41
|
+
mime: Optional[str]
|
|
42
|
+
|
|
24
43
|
|
|
25
44
|
@dataclass
|
|
26
45
|
class Element:
|
|
27
46
|
# The type of the element. This will be used to determine how to display the element in the UI.
|
|
28
47
|
type: ClassVar[ElementType]
|
|
29
|
-
|
|
48
|
+
# Name of the element, this will be used to reference the element in the UI.
|
|
49
|
+
name: str
|
|
30
50
|
# The ID of the element. This is set automatically when the element is sent to the UI.
|
|
31
51
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
32
|
-
#
|
|
33
|
-
|
|
52
|
+
# The key of the element hosted on Chainlit.
|
|
53
|
+
chainlit_key: Optional[str] = None
|
|
34
54
|
# The URL of the element if already hosted somehwere else.
|
|
35
55
|
url: Optional[str] = None
|
|
36
56
|
# The S3 object key.
|
|
@@ -44,7 +64,7 @@ class Element:
|
|
|
44
64
|
# Controls element size
|
|
45
65
|
size: Optional[ElementSize] = None
|
|
46
66
|
# The ID of the message this element is associated with.
|
|
47
|
-
|
|
67
|
+
for_id: Optional[str] = None
|
|
48
68
|
# The language, if relevant
|
|
49
69
|
language: Optional[str] = None
|
|
50
70
|
# Mime type, infered based on content if not provided
|
|
@@ -53,6 +73,8 @@ class Element:
|
|
|
53
73
|
def __post_init__(self) -> None:
|
|
54
74
|
trace_event(f"init {self.__class__.__name__}")
|
|
55
75
|
self.persisted = False
|
|
76
|
+
self.updatable = False
|
|
77
|
+
self.thread_id = context.session.thread_id
|
|
56
78
|
|
|
57
79
|
if not self.url and not self.path and not self.content:
|
|
58
80
|
raise ValueError("Must provide url, path or content to instantiate element")
|
|
@@ -61,136 +83,92 @@ class Element:
|
|
|
61
83
|
_dict = ElementDict(
|
|
62
84
|
{
|
|
63
85
|
"id": self.id,
|
|
86
|
+
"threadId": self.thread_id,
|
|
64
87
|
"type": self.type,
|
|
65
|
-
"url": self.url
|
|
66
|
-
"
|
|
88
|
+
"url": self.url,
|
|
89
|
+
"chainlitKey": self.chainlit_key,
|
|
90
|
+
"name": self.name,
|
|
67
91
|
"display": self.display,
|
|
68
92
|
"objectKey": getattr(self, "object_key", None),
|
|
69
93
|
"size": getattr(self, "size", None),
|
|
70
94
|
"language": getattr(self, "language", None),
|
|
71
|
-
"
|
|
95
|
+
"forId": getattr(self, "for_id", None),
|
|
72
96
|
"mime": getattr(self, "mime", None),
|
|
73
|
-
"conversationId": None,
|
|
74
97
|
}
|
|
75
98
|
)
|
|
76
99
|
return _dict
|
|
77
100
|
|
|
78
101
|
@classmethod
|
|
79
|
-
def from_dict(self, _dict:
|
|
80
|
-
|
|
102
|
+
def from_dict(self, _dict: FileDict):
|
|
103
|
+
type = _dict.get("type", "")
|
|
104
|
+
if "image" in type and "svg" not in type:
|
|
81
105
|
return Image(
|
|
82
106
|
id=_dict.get("id", str(uuid.uuid4())),
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
display=
|
|
87
|
-
mime=
|
|
107
|
+
name=_dict.get("name", ""),
|
|
108
|
+
path=str(_dict.get("path")),
|
|
109
|
+
chainlit_key=_dict.get("id"),
|
|
110
|
+
display="inline",
|
|
111
|
+
mime=type,
|
|
88
112
|
)
|
|
89
113
|
else:
|
|
90
114
|
return File(
|
|
91
115
|
id=_dict.get("id", str(uuid.uuid4())),
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
size=_dict.get("size"),
|
|
98
|
-
mime=_dict.get("mime"),
|
|
116
|
+
name=_dict.get("name", ""),
|
|
117
|
+
path=str(_dict.get("path")),
|
|
118
|
+
chainlit_key=_dict.get("id"),
|
|
119
|
+
display="inline",
|
|
120
|
+
mime=type,
|
|
99
121
|
)
|
|
100
122
|
|
|
101
|
-
async def
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
self.
|
|
113
|
-
else:
|
|
114
|
-
raise ValueError("Must provide path or content to load element")
|
|
115
|
-
|
|
116
|
-
async def persist(self, client: ChainlitCloudClient) -> Optional[ElementDict]:
|
|
117
|
-
if not self.url and self.content and not self.persisted:
|
|
118
|
-
conversation_id = await context.session.get_conversation_id()
|
|
119
|
-
upload_res = await client.upload_element(
|
|
123
|
+
async def _create(self) -> bool:
|
|
124
|
+
if (self.persisted or self.url) and not self.updatable:
|
|
125
|
+
return True
|
|
126
|
+
if data_layer := get_data_layer():
|
|
127
|
+
try:
|
|
128
|
+
asyncio.create_task(data_layer.create_element(self))
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"Failed to create element: {str(e)}")
|
|
131
|
+
if not self.chainlit_key or self.updatable:
|
|
132
|
+
file_dict = await context.session.persist_file(
|
|
133
|
+
name=self.name,
|
|
134
|
+
path=self.path,
|
|
120
135
|
content=self.content,
|
|
121
136
|
mime=self.mime or "",
|
|
122
|
-
conversation_id=conversation_id,
|
|
123
137
|
)
|
|
124
|
-
self.
|
|
125
|
-
self.object_key = upload_res["object_key"]
|
|
126
|
-
element_dict = await self.with_conversation_id()
|
|
138
|
+
self.chainlit_key = file_dict["id"]
|
|
127
139
|
|
|
128
|
-
|
|
140
|
+
self.persisted = True
|
|
129
141
|
|
|
130
|
-
return
|
|
131
|
-
|
|
132
|
-
async def _persist(self, element: ElementDict):
|
|
133
|
-
if not chainlit_client:
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
try:
|
|
137
|
-
if self.persisted:
|
|
138
|
-
await chainlit_client.update_element(element)
|
|
139
|
-
else:
|
|
140
|
-
await chainlit_client.create_element(element)
|
|
141
|
-
self.persisted = True
|
|
142
|
-
except Exception as e:
|
|
143
|
-
logger.error(f"Failed to persist element: {str(e)}")
|
|
144
|
-
|
|
145
|
-
async def before_emit(self, element: Dict) -> Dict:
|
|
146
|
-
return element
|
|
142
|
+
return True
|
|
147
143
|
|
|
148
144
|
async def remove(self):
|
|
149
145
|
trace_event(f"remove {self.__class__.__name__}")
|
|
146
|
+
data_layer = get_data_layer()
|
|
147
|
+
if data_layer and self.persisted:
|
|
148
|
+
await data_layer.delete_element(self.id)
|
|
150
149
|
await context.emitter.emit("remove_element", {"id": self.id})
|
|
151
150
|
|
|
152
|
-
async def send(self, for_id:
|
|
153
|
-
if
|
|
154
|
-
|
|
151
|
+
async def send(self, for_id: str):
|
|
152
|
+
if self.persisted and not self.updatable:
|
|
153
|
+
return
|
|
155
154
|
|
|
156
|
-
|
|
155
|
+
self.for_id = for_id
|
|
157
156
|
|
|
158
157
|
if not self.mime:
|
|
159
158
|
# Only guess the mime type when the content is binary
|
|
160
159
|
self.mime = (
|
|
161
160
|
mime_types[self.type]
|
|
162
161
|
if self.type in mime_types
|
|
163
|
-
else filetype.guess_mime(self.content)
|
|
162
|
+
else filetype.guess_mime(self.path or self.content)
|
|
164
163
|
)
|
|
165
164
|
|
|
166
|
-
|
|
167
|
-
self.for_ids.append(for_id)
|
|
165
|
+
await self._create()
|
|
168
166
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
element_dict = await self.persist(chainlit_client)
|
|
172
|
-
if element_dict:
|
|
173
|
-
self.id = element_dict["id"]
|
|
167
|
+
if not self.url and not self.chainlit_key:
|
|
168
|
+
raise ValueError("Must provide url or chainlit key to send element")
|
|
174
169
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
emit_dict = cast(Dict, self.to_dict())
|
|
179
|
-
|
|
180
|
-
# Adding this out of to_dict since the dict will be persisted in the DB
|
|
181
|
-
emit_dict["content"] = self.content
|
|
182
|
-
|
|
183
|
-
# Element was already sent
|
|
184
|
-
if len(self.for_ids) > 1:
|
|
185
|
-
trace_event(f"update {self.__class__.__name__}")
|
|
186
|
-
await context.emitter.emit(
|
|
187
|
-
"update_element",
|
|
188
|
-
{"id": self.id, "forIds": self.for_ids},
|
|
189
|
-
)
|
|
190
|
-
else:
|
|
191
|
-
trace_event(f"send {self.__class__.__name__}")
|
|
192
|
-
emit_dict = await self.before_emit(emit_dict)
|
|
193
|
-
await context.emitter.emit("element", emit_dict)
|
|
170
|
+
trace_event(f"send {self.__class__.__name__}")
|
|
171
|
+
await context.emitter.emit("element", self.to_dict())
|
|
194
172
|
|
|
195
173
|
|
|
196
174
|
ElementBased = TypeVar("ElementBased", bound=Element)
|
|
@@ -208,23 +186,7 @@ class Avatar(Element):
|
|
|
208
186
|
type: ClassVar[ElementType] = "avatar"
|
|
209
187
|
|
|
210
188
|
async def send(self):
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if not self.content and not self.url and self.path:
|
|
214
|
-
await self.load()
|
|
215
|
-
|
|
216
|
-
if not self.url and not self.content:
|
|
217
|
-
raise ValueError("Must provide url or content to send element")
|
|
218
|
-
|
|
219
|
-
element = self.to_dict()
|
|
220
|
-
|
|
221
|
-
# Adding this out of to_dict since the dict will be persisted in the DB
|
|
222
|
-
element["content"] = self.content
|
|
223
|
-
|
|
224
|
-
if element:
|
|
225
|
-
trace_event(f"send {self.__class__.__name__}")
|
|
226
|
-
element = await self.before_emit(element)
|
|
227
|
-
await context.emitter.emit("element", element)
|
|
189
|
+
await super().send(for_id="")
|
|
228
190
|
|
|
229
191
|
|
|
230
192
|
@dataclass
|
|
@@ -232,15 +194,8 @@ class Text(Element):
|
|
|
232
194
|
"""Useful to send a text (not a message) to the UI."""
|
|
233
195
|
|
|
234
196
|
type: ClassVar[ElementType] = "text"
|
|
235
|
-
|
|
236
|
-
content: bytes = b""
|
|
237
197
|
language: Optional[str] = None
|
|
238
198
|
|
|
239
|
-
async def before_emit(self, text_element):
|
|
240
|
-
if "content" in text_element and isinstance(text_element["content"], bytes):
|
|
241
|
-
text_element["content"] = text_element["content"].decode("utf-8")
|
|
242
|
-
return text_element
|
|
243
|
-
|
|
244
199
|
|
|
245
200
|
@dataclass
|
|
246
201
|
class Pdf(Element):
|
|
@@ -312,12 +267,20 @@ class TaskList(Element):
|
|
|
312
267
|
name: str = "tasklist"
|
|
313
268
|
content: str = "dummy content to pass validation"
|
|
314
269
|
|
|
270
|
+
def __post_init__(self) -> None:
|
|
271
|
+
super().__post_init__()
|
|
272
|
+
self.updatable = True
|
|
273
|
+
|
|
315
274
|
async def add_task(self, task: Task):
|
|
316
275
|
self.tasks.append(task)
|
|
317
276
|
|
|
318
277
|
async def update(self):
|
|
319
278
|
await self.send()
|
|
320
279
|
|
|
280
|
+
async def send(self):
|
|
281
|
+
await self.preprocess_content()
|
|
282
|
+
await super().send(for_id="")
|
|
283
|
+
|
|
321
284
|
async def preprocess_content(self):
|
|
322
285
|
# serialize enum
|
|
323
286
|
tasks = [
|
chainlit/emitter.py
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import uuid
|
|
3
|
-
from
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, Dict, List, Optional, Union, cast
|
|
4
5
|
|
|
5
|
-
from chainlit.
|
|
6
|
-
from chainlit.element import Element
|
|
6
|
+
from chainlit.data import get_data_layer
|
|
7
|
+
from chainlit.element import Element, File
|
|
7
8
|
from chainlit.message import Message
|
|
8
9
|
from chainlit.session import BaseSession, WebsocketSession
|
|
9
|
-
from chainlit.
|
|
10
|
+
from chainlit.step import StepDict
|
|
11
|
+
from chainlit.types import (
|
|
12
|
+
AskActionResponse,
|
|
13
|
+
AskSpec,
|
|
14
|
+
FileDict,
|
|
15
|
+
FileReference,
|
|
16
|
+
ThreadDict,
|
|
17
|
+
UIMessagePayload,
|
|
18
|
+
)
|
|
19
|
+
from chainlit.user import PersistedUser
|
|
10
20
|
from socketio.exceptions import TimeoutError
|
|
11
21
|
|
|
12
22
|
|
|
@@ -30,19 +40,19 @@ class BaseChainlitEmitter:
|
|
|
30
40
|
"""Stub method to get the 'ask_user' property from the session."""
|
|
31
41
|
pass
|
|
32
42
|
|
|
33
|
-
async def
|
|
34
|
-
"""Stub method to resume a
|
|
43
|
+
async def resume_thread(self, thread_dict: ThreadDict):
|
|
44
|
+
"""Stub method to resume a thread."""
|
|
35
45
|
pass
|
|
36
46
|
|
|
37
|
-
async def
|
|
47
|
+
async def send_step(self, step_dict: StepDict):
|
|
38
48
|
"""Stub method to send a message to the UI."""
|
|
39
49
|
pass
|
|
40
50
|
|
|
41
|
-
async def
|
|
51
|
+
async def update_step(self, step_dict: StepDict):
|
|
42
52
|
"""Stub method to update a message in the UI."""
|
|
43
53
|
pass
|
|
44
54
|
|
|
45
|
-
async def
|
|
55
|
+
async def delete_step(self, step_dict: StepDict):
|
|
46
56
|
"""Stub method to delete a message in the UI."""
|
|
47
57
|
pass
|
|
48
58
|
|
|
@@ -54,15 +64,17 @@ class BaseChainlitEmitter:
|
|
|
54
64
|
"""Stub method to clear the prompt from the UI."""
|
|
55
65
|
pass
|
|
56
66
|
|
|
57
|
-
async def
|
|
58
|
-
"""Signal the UI that a new
|
|
67
|
+
async def init_thread(self, step_dict: StepDict):
|
|
68
|
+
"""Signal the UI that a new thread (with a user message) exists"""
|
|
59
69
|
pass
|
|
60
70
|
|
|
61
71
|
async def process_user_message(self, payload: UIMessagePayload) -> Message:
|
|
62
72
|
"""Stub method to process user message."""
|
|
63
73
|
return Message(content="")
|
|
64
74
|
|
|
65
|
-
async def send_ask_user(
|
|
75
|
+
async def send_ask_user(
|
|
76
|
+
self, step_dict: StepDict, spec: AskSpec, raise_on_timeout=False
|
|
77
|
+
) -> Optional[Union["StepDict", "AskActionResponse", List["FileDict"]]]:
|
|
66
78
|
"""Stub method to send a prompt to the UI and wait for a response."""
|
|
67
79
|
pass
|
|
68
80
|
|
|
@@ -78,7 +90,7 @@ class BaseChainlitEmitter:
|
|
|
78
90
|
"""Stub method to send a task end signal to the UI."""
|
|
79
91
|
pass
|
|
80
92
|
|
|
81
|
-
async def stream_start(self,
|
|
93
|
+
async def stream_start(self, step_dict: StepDict):
|
|
82
94
|
"""Stub method to send a stream start signal to the UI."""
|
|
83
95
|
pass
|
|
84
96
|
|
|
@@ -90,6 +102,12 @@ class BaseChainlitEmitter:
|
|
|
90
102
|
"""Stub method to set chat settings."""
|
|
91
103
|
pass
|
|
92
104
|
|
|
105
|
+
async def send_action_response(
|
|
106
|
+
self, id: str, status: bool, response: Optional[str] = None
|
|
107
|
+
):
|
|
108
|
+
"""Send an action response to the UI."""
|
|
109
|
+
pass
|
|
110
|
+
|
|
93
111
|
|
|
94
112
|
class ChainlitEmitter(BaseChainlitEmitter):
|
|
95
113
|
"""
|
|
@@ -123,23 +141,21 @@ class ChainlitEmitter(BaseChainlitEmitter):
|
|
|
123
141
|
"""Get the 'ask_user' property from the session."""
|
|
124
142
|
return self._get_session_property("ask_user")
|
|
125
143
|
|
|
126
|
-
def
|
|
127
|
-
"""Send a
|
|
128
|
-
return self.emit("
|
|
144
|
+
def resume_thread(self, thread_dict: ThreadDict):
|
|
145
|
+
"""Send a thread to the UI to resume it"""
|
|
146
|
+
return self.emit("resume_thread", thread_dict)
|
|
129
147
|
|
|
130
|
-
def
|
|
148
|
+
def send_step(self, step_dict: StepDict):
|
|
131
149
|
"""Send a message to the UI."""
|
|
132
|
-
return self.emit("new_message",
|
|
150
|
+
return self.emit("new_message", step_dict)
|
|
133
151
|
|
|
134
|
-
def
|
|
152
|
+
def update_step(self, step_dict: StepDict):
|
|
135
153
|
"""Update a message in the UI."""
|
|
154
|
+
return self.emit("update_message", step_dict)
|
|
136
155
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def delete_message(self, msg_dict):
|
|
156
|
+
def delete_step(self, step_dict: StepDict):
|
|
140
157
|
"""Delete a message in the UI."""
|
|
141
|
-
|
|
142
|
-
return self.emit("delete_message", msg_dict)
|
|
158
|
+
return self.emit("delete_message", step_dict)
|
|
143
159
|
|
|
144
160
|
def send_ask_timeout(self):
|
|
145
161
|
"""Send a prompt timeout message to the UI."""
|
|
@@ -151,22 +167,44 @@ class ChainlitEmitter(BaseChainlitEmitter):
|
|
|
151
167
|
|
|
152
168
|
return self.emit("clear_ask", {})
|
|
153
169
|
|
|
154
|
-
def
|
|
155
|
-
"""Signal the UI that a new
|
|
170
|
+
async def init_thread(self, step: StepDict):
|
|
171
|
+
"""Signal the UI that a new thread (with a user message) exists"""
|
|
172
|
+
if data_layer := get_data_layer():
|
|
173
|
+
if isinstance(self.session.user, PersistedUser):
|
|
174
|
+
user_id = self.session.user.id
|
|
175
|
+
else:
|
|
176
|
+
user_id = None
|
|
177
|
+
await data_layer.update_thread(
|
|
178
|
+
thread_id=self.session.thread_id,
|
|
179
|
+
user_id=user_id,
|
|
180
|
+
metadata={"name": step["output"]},
|
|
181
|
+
)
|
|
182
|
+
await self.session.flush_method_queue()
|
|
156
183
|
|
|
157
|
-
|
|
184
|
+
await self.emit("init_thread", step)
|
|
158
185
|
|
|
159
186
|
async def process_user_message(self, payload: UIMessagePayload):
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
#
|
|
163
|
-
assert uuid.UUID(
|
|
187
|
+
step_dict = payload["message"]
|
|
188
|
+
file_refs = payload["fileReferences"]
|
|
189
|
+
# UUID generated by the frontend should use v4
|
|
190
|
+
assert uuid.UUID(step_dict["id"]).version == 4
|
|
164
191
|
|
|
165
|
-
message = Message.from_dict(
|
|
192
|
+
message = Message.from_dict(step_dict)
|
|
193
|
+
# Overwrite the created_at timestamp with the current time
|
|
194
|
+
message.created_at = datetime.utcnow().isoformat()
|
|
166
195
|
|
|
167
196
|
asyncio.create_task(message._create())
|
|
168
197
|
|
|
169
|
-
if
|
|
198
|
+
if not self.session.has_user_message:
|
|
199
|
+
self.session.has_user_message = True
|
|
200
|
+
asyncio.create_task(self.init_thread(message.to_dict()))
|
|
201
|
+
|
|
202
|
+
if file_refs:
|
|
203
|
+
files = [
|
|
204
|
+
self.session.files[file["id"]]
|
|
205
|
+
for file in file_refs
|
|
206
|
+
if file["id"] in self.session.files
|
|
207
|
+
]
|
|
170
208
|
file_elements = [Element.from_dict(file) for file in files]
|
|
171
209
|
message.elements = file_elements
|
|
172
210
|
|
|
@@ -176,38 +214,60 @@ class ChainlitEmitter(BaseChainlitEmitter):
|
|
|
176
214
|
|
|
177
215
|
asyncio.create_task(send_elements())
|
|
178
216
|
|
|
179
|
-
if not self.session.has_user_message:
|
|
180
|
-
self.session.has_user_message = True
|
|
181
|
-
await self.init_conversation(await message.with_conversation_id())
|
|
182
|
-
|
|
183
217
|
self.session.root_message = message
|
|
184
218
|
|
|
185
219
|
return message
|
|
186
220
|
|
|
187
221
|
async def send_ask_user(
|
|
188
|
-
self,
|
|
222
|
+
self, step_dict: StepDict, spec: AskSpec, raise_on_timeout=False
|
|
189
223
|
):
|
|
190
224
|
"""Send a prompt to the UI and wait for a response."""
|
|
191
225
|
|
|
192
226
|
try:
|
|
193
227
|
# Send the prompt to the UI
|
|
194
|
-
|
|
195
|
-
{"msg":
|
|
196
|
-
) # type: Optional["
|
|
228
|
+
user_res = await self.ask_user(
|
|
229
|
+
{"msg": step_dict, "spec": spec.to_dict()}, spec.timeout
|
|
230
|
+
) # type: Optional[Union["StepDict", "AskActionResponse", List["FileReference"]]]
|
|
197
231
|
|
|
198
232
|
# End the task temporarily so that the User can answer the prompt
|
|
199
233
|
await self.task_end()
|
|
200
234
|
|
|
201
|
-
|
|
202
|
-
|
|
235
|
+
final_res: Optional[
|
|
236
|
+
Union["StepDict", "AskActionResponse", List["FileDict"]]
|
|
237
|
+
] = None
|
|
238
|
+
|
|
239
|
+
if user_res:
|
|
203
240
|
if spec.type == "text":
|
|
204
|
-
|
|
241
|
+
message_dict_res = cast(StepDict, user_res)
|
|
242
|
+
await self.process_user_message(
|
|
243
|
+
{"message": message_dict_res, "fileReferences": None}
|
|
244
|
+
)
|
|
245
|
+
final_res = message_dict_res
|
|
205
246
|
elif spec.type == "file":
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
247
|
+
file_refs = cast(List[FileReference], user_res)
|
|
248
|
+
files = [
|
|
249
|
+
self.session.files[file["id"]]
|
|
250
|
+
for file in file_refs
|
|
251
|
+
if file["id"] in self.session.files
|
|
252
|
+
]
|
|
253
|
+
final_res = files
|
|
254
|
+
if get_data_layer():
|
|
255
|
+
coros = [
|
|
256
|
+
File(
|
|
257
|
+
name=file["name"],
|
|
258
|
+
path=str(file["path"]),
|
|
259
|
+
mime=file["type"],
|
|
260
|
+
chainlit_key=file["id"],
|
|
261
|
+
for_id=step_dict["id"],
|
|
262
|
+
)._create()
|
|
263
|
+
for file in files
|
|
264
|
+
]
|
|
265
|
+
await asyncio.gather(*coros)
|
|
266
|
+
elif spec.type == "action":
|
|
267
|
+
action_res = cast(AskActionResponse, user_res)
|
|
268
|
+
final_res = action_res
|
|
209
269
|
await self.clear_ask()
|
|
210
|
-
return
|
|
270
|
+
return final_res
|
|
211
271
|
except TimeoutError as e:
|
|
212
272
|
await self.send_ask_timeout()
|
|
213
273
|
|
|
@@ -231,11 +291,11 @@ class ChainlitEmitter(BaseChainlitEmitter):
|
|
|
231
291
|
"""Send a task end signal to the UI."""
|
|
232
292
|
return self.emit("task_end", {})
|
|
233
293
|
|
|
234
|
-
def stream_start(self,
|
|
294
|
+
def stream_start(self, step_dict: StepDict):
|
|
235
295
|
"""Send a stream start signal to the UI."""
|
|
236
296
|
return self.emit(
|
|
237
297
|
"stream_start",
|
|
238
|
-
|
|
298
|
+
step_dict,
|
|
239
299
|
)
|
|
240
300
|
|
|
241
301
|
def send_token(self, id: str, token: str, is_sequence=False):
|
|
@@ -246,3 +306,10 @@ class ChainlitEmitter(BaseChainlitEmitter):
|
|
|
246
306
|
|
|
247
307
|
def set_chat_settings(self, settings: Dict[str, Any]):
|
|
248
308
|
self.session.chat_settings = settings
|
|
309
|
+
|
|
310
|
+
def send_action_response(
|
|
311
|
+
self, id: str, status: bool, response: Optional[str] = None
|
|
312
|
+
):
|
|
313
|
+
return self.emit(
|
|
314
|
+
"action_response", {"id": id, "status": status, "response": response}
|
|
315
|
+
)
|