chainlit 2.0rc0__py3-none-any.whl → 2.0.1__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.

Files changed (76) hide show
  1. chainlit/__init__.py +49 -54
  2. chainlit/action.py +12 -10
  3. chainlit/{auth.py → auth/__init__.py} +20 -34
  4. chainlit/auth/cookie.py +123 -0
  5. chainlit/auth/jwt.py +37 -0
  6. chainlit/callbacks.py +29 -1
  7. chainlit/chat_context.py +2 -2
  8. chainlit/chat_settings.py +3 -1
  9. chainlit/cli/__init__.py +14 -1
  10. chainlit/config.py +21 -59
  11. chainlit/context.py +3 -2
  12. chainlit/copilot/dist/index.js +8319 -1019
  13. chainlit/data/__init__.py +98 -17
  14. chainlit/data/acl.py +3 -2
  15. chainlit/data/base.py +1 -1
  16. chainlit/data/chainlit_data_layer.py +584 -0
  17. chainlit/data/dynamodb.py +5 -3
  18. chainlit/data/literalai.py +4 -6
  19. chainlit/data/sql_alchemy.py +8 -7
  20. chainlit/data/storage_clients/azure.py +1 -0
  21. chainlit/data/storage_clients/azure_blob.py +80 -0
  22. chainlit/data/storage_clients/base.py +6 -0
  23. chainlit/data/storage_clients/gcs.py +78 -0
  24. chainlit/data/storage_clients/s3.py +16 -3
  25. chainlit/discord/app.py +2 -1
  26. chainlit/element.py +13 -9
  27. chainlit/emitter.py +17 -21
  28. chainlit/frontend/dist/assets/{DailyMotion-CleI-8Dh.js → DailyMotion-B8XgmoRm.js} +1 -1
  29. chainlit/frontend/dist/assets/Dataframe-VU4lXMbv.js +22 -0
  30. chainlit/frontend/dist/assets/{Facebook-C4PuTowX.js → Facebook-fVIMi9h_.js} +1 -1
  31. chainlit/frontend/dist/assets/{FilePlayer-D49YToZz.js → FilePlayer-DlXvvaZa.js} +1 -1
  32. chainlit/frontend/dist/assets/{Kaltura-BkZcQEIs.js → Kaltura-C48Ui_4V.js} +1 -1
  33. chainlit/frontend/dist/assets/{Mixcloud-DzvBFYsm.js → Mixcloud-Dmjz7RrS.js} +1 -1
  34. chainlit/frontend/dist/assets/{Mux-UXPyWWYv.js → Mux-Bqaa3ZzG.js} +1 -1
  35. chainlit/frontend/dist/assets/{Preview-0YXzpiVm.js → Preview-B2d1Ugq4.js} +1 -1
  36. chainlit/frontend/dist/assets/{SoundCloud-CS54COex.js → SoundCloud-BGuk87T3.js} +1 -1
  37. chainlit/frontend/dist/assets/{Streamable-DYYShO6Q.js → Streamable-DOe4rXrG.js} +1 -1
  38. chainlit/frontend/dist/assets/{Twitch-DG7403Hm.js → Twitch-TA7I2UEi.js} +1 -1
  39. chainlit/frontend/dist/assets/{Vidyard-C5JbOHIQ.js → Vidyard-B5F6Dk_y.js} +1 -1
  40. chainlit/frontend/dist/assets/{Vimeo-dFLZbhqH.js → Vimeo-DP_Y98tQ.js} +1 -1
  41. chainlit/frontend/dist/assets/Wistia-DB26BTg8.js +1 -0
  42. chainlit/frontend/dist/assets/{YouTube-Dct4gpfH.js → YouTube-CMwwf2TN.js} +1 -1
  43. chainlit/frontend/dist/assets/index-88S3ZtD5.css +1 -0
  44. chainlit/frontend/dist/assets/index-D7lZEN9m.js +8665 -0
  45. chainlit/frontend/dist/assets/{react-plotly-CFHBSMgg.js → react-plotly-28_xImPF.js} +1 -1
  46. chainlit/frontend/dist/index.html +2 -2
  47. chainlit/haystack/callbacks.py +5 -4
  48. chainlit/input_widget.py +6 -4
  49. chainlit/langchain/callbacks.py +56 -47
  50. chainlit/langflow/__init__.py +1 -0
  51. chainlit/llama_index/callbacks.py +7 -7
  52. chainlit/message.py +6 -7
  53. chainlit/mistralai/__init__.py +3 -2
  54. chainlit/oauth_providers.py +70 -3
  55. chainlit/openai/__init__.py +3 -2
  56. chainlit/secret.py +1 -1
  57. chainlit/server.py +483 -199
  58. chainlit/session.py +7 -5
  59. chainlit/slack/app.py +3 -2
  60. chainlit/socket.py +79 -106
  61. chainlit/step.py +11 -11
  62. chainlit/sync.py +2 -1
  63. chainlit/teams/app.py +1 -0
  64. chainlit/translations/en-US.json +1 -1
  65. chainlit/translations/nl-NL.json +229 -0
  66. chainlit/types.py +20 -4
  67. chainlit/user.py +2 -1
  68. chainlit/utils.py +3 -2
  69. {chainlit-2.0rc0.dist-info → chainlit-2.0.1.dist-info}/METADATA +3 -34
  70. chainlit-2.0.1.dist-info/RECORD +106 -0
  71. chainlit/frontend/dist/assets/Wistia-143Q9V9c.js +0 -1
  72. chainlit/frontend/dist/assets/index-2yAiK0R5.js +0 -1091
  73. chainlit/frontend/dist/assets/index-CwmincdQ.css +0 -1
  74. chainlit-2.0rc0.dist-info/RECORD +0 -99
  75. {chainlit-2.0rc0.dist-info → chainlit-2.0.1.dist-info}/WHEEL +0 -0
  76. {chainlit-2.0rc0.dist-info → chainlit-2.0.1.dist-info}/entry_points.txt +0 -0
chainlit/session.py CHANGED
@@ -6,6 +6,7 @@ import uuid
6
6
  from typing import TYPE_CHECKING, Any, Callable, Deque, Dict, Literal, Optional, Union
7
7
 
8
8
  import aiofiles
9
+
9
10
  from chainlit.logger import logger
10
11
  from chainlit.types import FileReference
11
12
 
@@ -17,9 +18,9 @@ ClientType = Literal["webapp", "copilot", "teams", "slack", "discord"]
17
18
 
18
19
 
19
20
  class JSONEncoderIgnoreNonSerializable(json.JSONEncoder):
20
- def default(self, obj):
21
+ def default(self, o):
21
22
  try:
22
- return super(JSONEncoderIgnoreNonSerializable, self).default(obj)
23
+ return super().default(o)
23
24
  except TypeError:
24
25
  return None
25
26
 
@@ -112,9 +113,10 @@ class BaseSession:
112
113
 
113
114
  if path:
114
115
  # Copy the file from the given path
115
- async with aiofiles.open(path, "rb") as src, aiofiles.open(
116
- file_path, "wb"
117
- ) as dst:
116
+ async with (
117
+ aiofiles.open(path, "rb") as src,
118
+ aiofiles.open(file_path, "wb") as dst,
119
+ ):
118
120
  await dst.write(await src.read())
119
121
  elif content:
120
122
  # Write the provided content to the file
chainlit/slack/app.py CHANGED
@@ -7,6 +7,9 @@ from functools import partial
7
7
  from typing import Dict, List, Optional, Union
8
8
 
9
9
  import httpx
10
+ from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
11
+ from slack_bolt.async_app import AsyncApp
12
+
10
13
  from chainlit.config import config
11
14
  from chainlit.context import ChainlitContext, HTTPSession, context, context_var
12
15
  from chainlit.data import get_data_layer
@@ -18,8 +21,6 @@ from chainlit.telemetry import trace
18
21
  from chainlit.types import Feedback
19
22
  from chainlit.user import PersistedUser, User
20
23
  from chainlit.user_session import user_session
21
- from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
22
- from slack_bolt.async_app import AsyncApp
23
24
 
24
25
 
25
26
  class SlackEmitter(BaseChainlitEmitter):
chainlit/socket.py CHANGED
@@ -1,29 +1,27 @@
1
1
  import asyncio
2
2
  import json
3
- import time
4
- import uuid
5
- from typing import Any, Dict, Literal
3
+ from typing import Any, Dict, Literal, Optional, Tuple, Union
6
4
  from urllib.parse import unquote
7
5
 
8
- from chainlit.action import Action
6
+ from starlette.requests import cookie_parser
7
+ from typing_extensions import TypeAlias
8
+
9
9
  from chainlit.auth import get_current_user, require_login
10
10
  from chainlit.chat_context import chat_context
11
11
  from chainlit.config import config
12
12
  from chainlit.context import init_ws_context
13
13
  from chainlit.data import get_data_layer
14
- from chainlit.element import Element
15
14
  from chainlit.logger import logger
16
15
  from chainlit.message import ErrorMessage, Message
17
16
  from chainlit.server import sio
18
17
  from chainlit.session import WebsocketSession
19
18
  from chainlit.telemetry import trace_event
20
- from chainlit.types import (
21
- InputAudioChunk,
22
- InputAudioChunkPayload,
23
- MessagePayload,
24
- )
19
+ from chainlit.types import InputAudioChunk, InputAudioChunkPayload, MessagePayload
20
+ from chainlit.user import PersistedUser, User
25
21
  from chainlit.user_session import user_sessions
26
22
 
23
+ WSGIEnvironment: TypeAlias = dict[str, Any]
24
+
27
25
 
28
26
  def restore_existing_session(sid, session_id, emit_fn, emit_call_fn):
29
27
  """Restore a session from the sessionId provided by the client."""
@@ -82,45 +80,44 @@ def load_user_env(user_env):
82
80
  return user_env
83
81
 
84
82
 
85
- def build_anon_user_identifier(environ):
86
- scope = environ.get("asgi.scope", {})
87
- client_ip, _ = scope.get("client")
88
- ip = environ.get("HTTP_X_FORWARDED_FOR", client_ip)
83
+ def _get_token_from_cookie(environ: WSGIEnvironment) -> Optional[str]:
84
+ if cookie_header := environ.get("HTTP_COOKIE", None):
85
+ cookies = cookie_parser(cookie_header)
86
+ return cookies.get("access_token", None)
89
87
 
90
- try:
91
- headers = scope.get("headers", {})
92
- user_agent = next(
93
- (v.decode("utf-8") for k, v in headers if k.decode("utf-8") == "user-agent")
94
- )
95
- return str(uuid.uuid5(uuid.NAMESPACE_DNS, user_agent + ip))
96
-
97
- except StopIteration:
98
- return str(uuid.uuid5(uuid.NAMESPACE_DNS, ip))
99
-
100
-
101
- @sio.on("connect")
102
- async def connect(sid, environ):
103
- if (
104
- not config.code.on_chat_start
105
- and not config.code.on_message
106
- and not config.code.on_audio_chunk
107
- ):
108
- logger.warning(
109
- "You need to configure at least one of on_chat_start, on_message or on_audio_chunk callback"
110
- )
111
- return False
112
- user = None
113
- token = None
114
- login_required = require_login()
115
- try:
116
- # Check if the authentication is required
117
- if login_required:
118
- authorization_header = environ.get("HTTP_AUTHORIZATION")
119
- token = authorization_header.split(" ")[1] if authorization_header else None
120
- user = await get_current_user(token=token)
121
- except Exception:
122
- logger.info("Authentication failed")
123
- return False
88
+ return None
89
+
90
+
91
+ def _get_token(environ: WSGIEnvironment, auth: dict) -> Optional[str]:
92
+ """Take WSGI environ, return access token."""
93
+ return _get_token_from_cookie(environ)
94
+
95
+
96
+ async def _authenticate_connection(
97
+ environ,
98
+ auth,
99
+ ) -> Union[Tuple[Union[User, PersistedUser], str], Tuple[None, None]]:
100
+ if token := _get_token(environ, auth):
101
+ user = await get_current_user(token=token)
102
+ if user:
103
+ return user, token
104
+
105
+ return None, None
106
+
107
+
108
+ @sio.on("connect") # pyright: ignore [reportOptionalCall]
109
+ async def connect(sid, environ, auth):
110
+ user = token = None
111
+
112
+ if require_login():
113
+ try:
114
+ user, token = await _authenticate_connection(environ, auth)
115
+ except Exception as e:
116
+ logger.exception("Exception authenticating connection: %s", e)
117
+
118
+ if not user:
119
+ logger.error("Authentication failed in websocket connect.")
120
+ raise ConnectionRefusedError("authentication failed")
124
121
 
125
122
  # Session scoped function to emit to the client
126
123
  def emit_fn(event, data):
@@ -130,16 +127,16 @@ async def connect(sid, environ):
130
127
  def emit_call_fn(event: Literal["ask", "call_fn"], data, timeout):
131
128
  return sio.call(event, data, timeout=timeout, to=sid)
132
129
 
133
- session_id = environ.get("HTTP_X_CHAINLIT_SESSION_ID")
130
+ session_id = auth.get("sessionId")
134
131
  if restore_existing_session(sid, session_id, emit_fn, emit_call_fn):
135
132
  return True
136
133
 
137
- user_env_string = environ.get("HTTP_USER_ENV")
134
+ user_env_string = auth.get("userEnv")
138
135
  user_env = load_user_env(user_env_string)
139
136
 
140
- client_type = environ.get("HTTP_X_CHAINLIT_CLIENT_TYPE")
137
+ client_type = auth.get("clientType")
141
138
  http_referer = environ.get("HTTP_REFERER")
142
- url_encoded_chat_profile = environ.get("HTTP_X_CHAINLIT_CHAT_PROFILE")
139
+ url_encoded_chat_profile = auth.get("chatProfile")
143
140
  chat_profile = (
144
141
  unquote(url_encoded_chat_profile) if url_encoded_chat_profile else None
145
142
  )
@@ -154,7 +151,7 @@ async def connect(sid, environ):
154
151
  user=user,
155
152
  token=token,
156
153
  chat_profile=chat_profile,
157
- thread_id=environ.get("HTTP_X_CHAINLIT_THREAD_ID"),
154
+ thread_id=auth.get("threadId"),
158
155
  languages=environ.get("HTTP_ACCEPT_LANGUAGE"),
159
156
  http_referer=http_referer,
160
157
  )
@@ -163,17 +160,17 @@ async def connect(sid, environ):
163
160
  return True
164
161
 
165
162
 
166
- @sio.on("connection_successful")
163
+ @sio.on("connection_successful") # pyright: ignore [reportOptionalCall]
167
164
  async def connection_successful(sid):
168
165
  context = init_ws_context(sid)
169
166
 
170
- if context.session.restored:
171
- return
172
-
173
167
  await context.emitter.task_end()
174
168
  await context.emitter.clear("clear_ask")
175
169
  await context.emitter.clear("clear_call_fn")
176
170
 
171
+ if context.session.restored:
172
+ return
173
+
177
174
  if context.session.thread_id_to_resume and config.code.on_chat_resume:
178
175
  thread = await resume_thread(context.session)
179
176
  if thread:
@@ -196,14 +193,14 @@ async def connection_successful(sid):
196
193
  context.session.current_task = task
197
194
 
198
195
 
199
- @sio.on("clear_session")
196
+ @sio.on("clear_session") # pyright: ignore [reportOptionalCall]
200
197
  async def clean_session(sid):
201
198
  session = WebsocketSession.get(sid)
202
199
  if session:
203
200
  session.to_clear = True
204
201
 
205
202
 
206
- @sio.on("disconnect")
203
+ @sio.on("disconnect") # pyright: ignore [reportOptionalCall]
207
204
  async def disconnect(sid):
208
205
  session = WebsocketSession.get(sid)
209
206
 
@@ -237,7 +234,7 @@ async def disconnect(sid):
237
234
  asyncio.ensure_future(clear_on_timeout(sid))
238
235
 
239
236
 
240
- @sio.on("stop")
237
+ @sio.on("stop") # pyright: ignore [reportOptionalCall]
241
238
  async def stop(sid):
242
239
  if session := WebsocketSession.get(sid):
243
240
  trace_event("stop_task")
@@ -260,8 +257,7 @@ async def process_message(session: WebsocketSession, payload: MessagePayload):
260
257
  message = await context.emitter.process_message(payload)
261
258
 
262
259
  if config.code.on_message:
263
- # Sleep 1ms to make sure any children step starts after the message step start
264
- time.sleep(0.001)
260
+ await asyncio.sleep(0.001)
265
261
  await config.code.on_message(message)
266
262
  except asyncio.CancelledError:
267
263
  pass
@@ -274,7 +270,7 @@ async def process_message(session: WebsocketSession, payload: MessagePayload):
274
270
  await context.emitter.task_end()
275
271
 
276
272
 
277
- @sio.on("edit_message")
273
+ @sio.on("edit_message") # pyright: ignore [reportOptionalCall]
278
274
  async def edit_message(sid, payload: MessagePayload):
279
275
  """Handle a message sent by the User."""
280
276
  session = WebsocketSession.require(sid)
@@ -304,7 +300,7 @@ async def edit_message(sid, payload: MessagePayload):
304
300
  await context.emitter.task_end()
305
301
 
306
302
 
307
- @sio.on("client_message")
303
+ @sio.on("client_message") # pyright: ignore [reportOptionalCall]
308
304
  async def message(sid, payload: MessagePayload):
309
305
  """Handle a message sent by the User."""
310
306
  session = WebsocketSession.require(sid)
@@ -313,17 +309,30 @@ async def message(sid, payload: MessagePayload):
313
309
  session.current_task = task
314
310
 
315
311
 
316
- @sio.on("audio_start")
312
+ @sio.on("window_message") # pyright: ignore [reportOptionalCall]
313
+ async def window_message(sid, data):
314
+ """Handle a message send by the host window."""
315
+ session = WebsocketSession.require(sid)
316
+ init_ws_context(session)
317
+
318
+ if config.code.on_window_message:
319
+ try:
320
+ await config.code.on_window_message(data)
321
+ except asyncio.CancelledError:
322
+ pass
323
+
324
+
325
+ @sio.on("audio_start") # pyright: ignore [reportOptionalCall]
317
326
  async def audio_start(sid):
318
327
  """Handle audio init."""
319
328
  session = WebsocketSession.require(sid)
320
329
 
321
330
  context = init_ws_context(session)
322
331
  if config.code.on_audio_start:
323
- connected = bool(await config.code.on_audio_start())
324
- connection_state = "on" if connected else "off"
325
- await context.emitter.update_audio_connection(connection_state)
326
-
332
+ connected = bool(await config.code.on_audio_start())
333
+ connection_state = "on" if connected else "off"
334
+ await context.emitter.update_audio_connection(connection_state)
335
+
327
336
 
328
337
  @sio.on("audio_chunk")
329
338
  async def audio_chunk(sid, payload: InputAudioChunkPayload):
@@ -350,7 +359,7 @@ async def audio_end(sid):
350
359
 
351
360
  if config.code.on_audio_end:
352
361
  await config.code.on_audio_end()
353
-
362
+
354
363
  except asyncio.CancelledError:
355
364
  pass
356
365
  except Exception as e:
@@ -362,42 +371,6 @@ async def audio_end(sid):
362
371
  await context.emitter.task_end()
363
372
 
364
373
 
365
- async def process_action(action: Action):
366
- callback = config.code.action_callbacks.get(action.name)
367
- if callback:
368
- res = await callback(action)
369
- return res
370
- else:
371
- logger.warning("No callback found for action %s", action.name)
372
-
373
-
374
- @sio.on("action_call")
375
- async def call_action(sid, action):
376
- """Handle an action call from the UI."""
377
- context = init_ws_context(sid)
378
-
379
- action = Action(**action)
380
-
381
- try:
382
- if not context.session.has_first_interaction:
383
- context.session.has_first_interaction = True
384
- asyncio.create_task(context.emitter.init_thread(action.name))
385
- res = await process_action(action)
386
- await context.emitter.send_action_response(
387
- id=action.id, status=True, response=res if isinstance(res, str) else None
388
- )
389
-
390
- except asyncio.CancelledError:
391
- await context.emitter.send_action_response(
392
- id=action.id, status=False, response="Action interrupted by the user"
393
- )
394
- except Exception as e:
395
- logger.exception(e)
396
- await context.emitter.send_action_response(
397
- id=action.id, status=False, response="An error occurred"
398
- )
399
-
400
-
401
374
  @sio.on("chat_settings_change")
402
375
  async def change_settings(sid, settings: Dict[str, Any]):
403
376
  """Handle change settings submit from the UI."""
chainlit/step.py CHANGED
@@ -7,6 +7,10 @@ from copy import deepcopy
7
7
  from functools import wraps
8
8
  from typing import Callable, Dict, List, Optional, TypedDict, Union
9
9
 
10
+ from literalai import BaseGeneration
11
+ from literalai.helper import utc_now
12
+ from literalai.observability.step import StepType, TrueStepType
13
+
10
14
  from chainlit.config import config
11
15
  from chainlit.context import CL_RUN_NAMES, context, local_steps
12
16
  from chainlit.data import get_data_layer
@@ -14,9 +18,6 @@ from chainlit.element import Element
14
18
  from chainlit.logger import logger
15
19
  from chainlit.telemetry import trace_event
16
20
  from chainlit.types import FeedbackDict
17
- from literalai import BaseGeneration
18
- from literalai.helper import utc_now
19
- from literalai.observability.step import StepType, TrueStepType
20
21
 
21
22
 
22
23
  def check_add_step_in_cot(step: "Step"):
@@ -61,7 +62,6 @@ class StepDict(TypedDict, total=False):
61
62
  generation: Optional[Dict]
62
63
  showInput: Optional[Union[bool, str]]
63
64
  language: Optional[str]
64
- indent: Optional[int]
65
65
  feedback: Optional[FeedbackDict]
66
66
 
67
67
 
@@ -107,8 +107,8 @@ def step(
107
107
  ) as step:
108
108
  try:
109
109
  step.input = flatten_args_kwargs(func, args, kwargs)
110
- except:
111
- pass
110
+ except Exception as e:
111
+ logger.exception(e)
112
112
  result = await func(*args, **kwargs)
113
113
  try:
114
114
  if result and not step.output:
@@ -134,8 +134,8 @@ def step(
134
134
  ) as step:
135
135
  try:
136
136
  step.input = flatten_args_kwargs(func, args, kwargs)
137
- except:
138
- pass
137
+ except Exception as e:
138
+ logger.exception(e)
139
139
  result = func(*args, **kwargs)
140
140
  try:
141
141
  if result and not step.output:
@@ -317,7 +317,7 @@ class Step:
317
317
  except Exception as e:
318
318
  if self.fail_on_persist_error:
319
319
  raise e
320
- logger.error(f"Failed to persist step update: {str(e)}")
320
+ logger.error(f"Failed to persist step update: {e!s}")
321
321
 
322
322
  tasks = [el.send(for_id=self.id) for el in self.elements]
323
323
  await asyncio.gather(*tasks)
@@ -344,7 +344,7 @@ class Step:
344
344
  except Exception as e:
345
345
  if self.fail_on_persist_error:
346
346
  raise e
347
- logger.error(f"Failed to persist step deletion: {str(e)}")
347
+ logger.error(f"Failed to persist step deletion: {e!s}")
348
348
 
349
349
  await context.emitter.delete_step(step_dict)
350
350
 
@@ -371,7 +371,7 @@ class Step:
371
371
  except Exception as e:
372
372
  if self.fail_on_persist_error:
373
373
  raise e
374
- logger.error(f"Failed to persist step creation: {str(e)}")
374
+ logger.error(f"Failed to persist step creation: {e!s}")
375
375
 
376
376
  tasks = [el.send(for_id=self.id) for el in self.elements]
377
377
  await asyncio.gather(*tasks)
chainlit/sync.py CHANGED
@@ -10,9 +10,10 @@ import asyncio
10
10
  import threading
11
11
 
12
12
  from asyncer import asyncify
13
- from chainlit.context import context_var
14
13
  from syncer import sync
15
14
 
15
+ from chainlit.context import context_var
16
+
16
17
  make_async = asyncify
17
18
 
18
19
  T_Retval = TypeVar("T_Retval")
chainlit/teams/app.py CHANGED
@@ -28,6 +28,7 @@ from botbuilder.schema import (
28
28
  ChannelAccount,
29
29
  HeroCard,
30
30
  )
31
+
31
32
  from chainlit.config import config
32
33
  from chainlit.context import ChainlitContext, HTTPSession, context, context_var
33
34
  from chainlit.data import get_data_layer
@@ -176,7 +176,7 @@
176
176
  }
177
177
  },
178
178
  "DeleteThreadButton": {
179
- "confirmMessage": "This will delete the thread as well as it's messages and elements.",
179
+ "confirmMessage": "This will delete the thread as well as its messages and elements.",
180
180
  "cancel": "Cancel",
181
181
  "confirm": "Confirm",
182
182
  "deletingChat": "Deleting chat",