chainlit 1.1.202__py3-none-any.whl → 1.1.300__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 (60) hide show
  1. chainlit/__init__.py +22 -4
  2. chainlit/cli/__init__.py +53 -6
  3. chainlit/config.py +25 -18
  4. chainlit/context.py +9 -0
  5. chainlit/copilot/dist/index.js +443 -410
  6. chainlit/data/__init__.py +19 -5
  7. chainlit/data/dynamodb.py +586 -0
  8. chainlit/data/sql_alchemy.py +47 -28
  9. chainlit/discord/app.py +4 -2
  10. chainlit/element.py +36 -20
  11. chainlit/emitter.py +8 -7
  12. chainlit/frontend/dist/assets/{DailyMotion-53376209.js → DailyMotion-578b63e6.js} +1 -1
  13. chainlit/frontend/dist/assets/{Facebook-aee41f5b.js → Facebook-b825e5bb.js} +1 -1
  14. chainlit/frontend/dist/assets/{FilePlayer-b2cdb30f.js → FilePlayer-bcba3b4e.js} +1 -1
  15. chainlit/frontend/dist/assets/{Kaltura-51db0377.js → Kaltura-fc1c9497.js} +1 -1
  16. chainlit/frontend/dist/assets/{Mixcloud-cb900886.js → Mixcloud-4cfb2724.js} +1 -1
  17. chainlit/frontend/dist/assets/{Mux-79ac59e6.js → Mux-aa92055c.js} +1 -1
  18. chainlit/frontend/dist/assets/{Preview-cfe7584c.js → Preview-9f55905a.js} +1 -1
  19. chainlit/frontend/dist/assets/{SoundCloud-a985707c.js → SoundCloud-f991fe03.js} +1 -1
  20. chainlit/frontend/dist/assets/{Streamable-3d89aab5.js → Streamable-53128f49.js} +1 -1
  21. chainlit/frontend/dist/assets/{Twitch-bf016588.js → Twitch-fce8b9f5.js} +1 -1
  22. chainlit/frontend/dist/assets/{Vidyard-1891ecd7.js → Vidyard-e35c6102.js} +1 -1
  23. chainlit/frontend/dist/assets/{Vimeo-0645662c.js → Vimeo-fff35f8e.js} +1 -1
  24. chainlit/frontend/dist/assets/{Wistia-3b449fe2.js → Wistia-ec07dc64.js} +1 -1
  25. chainlit/frontend/dist/assets/{YouTube-5ea2381e.js → YouTube-ad068e2a.js} +1 -1
  26. chainlit/frontend/dist/assets/index-aaf974a9.css +1 -0
  27. chainlit/frontend/dist/assets/index-d40d41cc.js +727 -0
  28. chainlit/frontend/dist/assets/{react-plotly-2ff19c9f.js → react-plotly-b2c6442b.js} +1 -1
  29. chainlit/frontend/dist/index.html +2 -3
  30. chainlit/langchain/callbacks.py +4 -2
  31. chainlit/llama_index/callbacks.py +2 -2
  32. chainlit/message.py +30 -25
  33. chainlit/oauth_providers.py +118 -0
  34. chainlit/server.py +208 -83
  35. chainlit/slack/app.py +2 -3
  36. chainlit/socket.py +27 -23
  37. chainlit/step.py +44 -30
  38. chainlit/teams/__init__.py +6 -0
  39. chainlit/teams/app.py +332 -0
  40. chainlit/translations/en-US.json +2 -4
  41. chainlit/types.py +17 -17
  42. chainlit/user.py +9 -1
  43. chainlit/utils.py +47 -3
  44. {chainlit-1.1.202.dist-info → chainlit-1.1.300.dist-info}/METADATA +22 -14
  45. chainlit-1.1.300.dist-info/RECORD +79 -0
  46. chainlit/cli/utils.py +0 -24
  47. chainlit/frontend/dist/assets/index-a0c5a67e.js +0 -698
  48. chainlit/frontend/dist/assets/index-d088547c.css +0 -1
  49. chainlit/playground/__init__.py +0 -2
  50. chainlit/playground/config.py +0 -36
  51. chainlit/playground/provider.py +0 -108
  52. chainlit/playground/providers/__init__.py +0 -11
  53. chainlit/playground/providers/anthropic.py +0 -118
  54. chainlit/playground/providers/huggingface.py +0 -75
  55. chainlit/playground/providers/langchain.py +0 -89
  56. chainlit/playground/providers/openai.py +0 -386
  57. chainlit/playground/providers/vertexai.py +0 -171
  58. chainlit-1.1.202.dist-info/RECORD +0 -86
  59. {chainlit-1.1.202.dist-info → chainlit-1.1.300.dist-info}/WHEEL +0 -0
  60. {chainlit-1.1.202.dist-info → chainlit-1.1.300.dist-info}/entry_points.txt +0 -0
chainlit/server.py CHANGED
@@ -18,6 +18,7 @@ import webbrowser
18
18
  from contextlib import asynccontextmanager
19
19
  from pathlib import Path
20
20
 
21
+ import socketio
21
22
  from chainlit.auth import create_jwt, get_configuration, get_current_user
22
23
  from chainlit.config import (
23
24
  APP_ROOT,
@@ -33,20 +34,19 @@ from chainlit.data import get_data_layer
33
34
  from chainlit.data.acl import is_thread_author
34
35
  from chainlit.logger import logger
35
36
  from chainlit.markdown import get_markdown_str
36
- from chainlit.playground.config import get_llm_providers
37
- from chainlit.telemetry import trace_event
38
37
  from chainlit.types import (
39
38
  DeleteFeedbackRequest,
40
39
  DeleteThreadRequest,
41
- GenerationRequest,
42
40
  GetThreadsRequest,
43
41
  Theme,
44
42
  UpdateFeedbackRequest,
45
43
  )
46
44
  from chainlit.user import PersistedUser, User
47
45
  from fastapi import (
46
+ APIRouter,
48
47
  Depends,
49
48
  FastAPI,
49
+ Form,
50
50
  HTTPException,
51
51
  Query,
52
52
  Request,
@@ -57,12 +57,14 @@ from fastapi import (
57
57
  from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse
58
58
  from fastapi.security import OAuth2PasswordRequestForm
59
59
  from fastapi.staticfiles import StaticFiles
60
- from fastapi_socketio import SocketManager
61
60
  from starlette.datastructures import URL
62
61
  from starlette.middleware.cors import CORSMiddleware
63
62
  from typing_extensions import Annotated
64
63
  from watchfiles import awatch
65
64
 
65
+ ROOT_PATH = os.environ.get("CHAINLIT_ROOT_PATH", "")
66
+ IS_SUBMOUNT = os.environ.get("CHAINLIT_SUBMOUNT", "") == "true"
67
+
66
68
 
67
69
  @asynccontextmanager
68
70
  async def lifespan(app: FastAPI):
@@ -70,9 +72,9 @@ async def lifespan(app: FastAPI):
70
72
  port = config.run.port
71
73
 
72
74
  if host == DEFAULT_HOST:
73
- url = f"http://localhost:{port}"
75
+ url = f"http://localhost:{port}{ROOT_PATH}"
74
76
  else:
75
- url = f"http://{host}:{port}"
77
+ url = f"http://{host}:{port}{ROOT_PATH}"
76
78
 
77
79
  logger.info(f"Your app is available at {url}")
78
80
 
@@ -113,7 +115,7 @@ async def lifespan(app: FastAPI):
113
115
  logger.error(f"Error reloading module: {e}")
114
116
 
115
117
  await asyncio.sleep(1)
116
- await socket.emit("reload", {})
118
+ await sio.emit("reload", {})
117
119
 
118
120
  break
119
121
 
@@ -153,9 +155,9 @@ def get_build_dir(local_target: str, packaged_target: str):
153
155
  packaged_build_dir = os.path.join(BACKEND_ROOT, packaged_target, "dist")
154
156
 
155
157
  if config.ui.custom_build and os.path.exists(
156
- os.path.join(APP_ROOT, config.ui.custom_build, packaged_target, "dist")
158
+ os.path.join(APP_ROOT, config.ui.custom_build)
157
159
  ):
158
- return os.path.join(APP_ROOT, config.ui.custom_build, packaged_target, "dist")
160
+ return os.path.join(APP_ROOT, config.ui.custom_build)
159
161
  elif os.path.exists(local_build_dir):
160
162
  return local_build_dir
161
163
  elif os.path.exists(packaged_build_dir):
@@ -167,12 +169,36 @@ def get_build_dir(local_target: str, packaged_target: str):
167
169
  build_dir = get_build_dir("frontend", "frontend")
168
170
  copilot_build_dir = get_build_dir(os.path.join("libs", "copilot"), "copilot")
169
171
 
170
-
171
172
  app = FastAPI(lifespan=lifespan)
172
173
 
173
- app.mount("/public", StaticFiles(directory="public", check_dir=False), name="public")
174
+ sio = socketio.AsyncServer(
175
+ cors_allowed_origins=[] if IS_SUBMOUNT else "*", async_mode="asgi"
176
+ )
177
+
178
+ combined_asgi_app = socketio.ASGIApp(
179
+ sio,
180
+ app,
181
+ socketio_path=f"{ROOT_PATH}/ws/socket.io" if ROOT_PATH else "/ws/socket.io",
182
+ )
183
+
184
+ app.add_middleware(
185
+ CORSMiddleware,
186
+ allow_origins=config.project.allow_origins,
187
+ allow_credentials=True,
188
+ allow_methods=["*"],
189
+ allow_headers=["*"],
190
+ )
191
+
192
+ router = APIRouter(prefix=ROOT_PATH)
193
+
194
+ app.mount(
195
+ f"{ROOT_PATH}/public",
196
+ StaticFiles(directory="public", check_dir=False),
197
+ name="public",
198
+ )
199
+
174
200
  app.mount(
175
- "/assets",
201
+ f"{ROOT_PATH}/assets",
176
202
  StaticFiles(
177
203
  packages=[("chainlit", os.path.join(build_dir, "assets"))],
178
204
  follow_symlink=config.project.follow_symlink,
@@ -181,7 +207,7 @@ app.mount(
181
207
  )
182
208
 
183
209
  app.mount(
184
- "/copilot",
210
+ f"{ROOT_PATH}/copilot",
185
211
  StaticFiles(
186
212
  packages=[("chainlit", copilot_build_dir)],
187
213
  follow_symlink=config.project.follow_symlink,
@@ -190,22 +216,6 @@ app.mount(
190
216
  )
191
217
 
192
218
 
193
- app.add_middleware(
194
- CORSMiddleware,
195
- allow_origins=config.project.allow_origins,
196
- allow_credentials=True,
197
- allow_methods=["*"],
198
- allow_headers=["*"],
199
- )
200
-
201
- socket = SocketManager(
202
- app,
203
- cors_allowed_origins=[],
204
- async_mode="asgi",
205
- socketio_path="/ws/socket.io",
206
- )
207
-
208
-
209
219
  # -------------------------------------------------------------------------------
210
220
  # SLACK HANDLER
211
221
  # -------------------------------------------------------------------------------
@@ -213,11 +223,28 @@ socket = SocketManager(
213
223
  if os.environ.get("SLACK_BOT_TOKEN") and os.environ.get("SLACK_SIGNING_SECRET"):
214
224
  from chainlit.slack.app import slack_app_handler
215
225
 
216
- @app.post("/slack/events")
217
- async def endpoint(req: Request):
226
+ @router.post("/slack/events")
227
+ async def slack_endpoint(req: Request):
218
228
  return await slack_app_handler.handle(req)
219
229
 
220
230
 
231
+ # -------------------------------------------------------------------------------
232
+ # TEAMS HANDLER
233
+ # -------------------------------------------------------------------------------
234
+
235
+ if os.environ.get("TEAMS_APP_ID") and os.environ.get("TEAMS_APP_PASSWORD"):
236
+ from botbuilder.schema import Activity
237
+ from chainlit.teams.app import adapter, bot
238
+
239
+ @router.post("/teams/events")
240
+ async def teams_endpoint(req: Request):
241
+ body = await req.json()
242
+ activity = Activity().deserialize(body)
243
+ auth_header = req.headers.get("Authorization", "")
244
+ response = await adapter.process_activity(activity, auth_header, bot.on_turn)
245
+ return response
246
+
247
+
221
248
  # -------------------------------------------------------------------------------
222
249
  # HTTP HANDLERS
223
250
  # -------------------------------------------------------------------------------
@@ -234,17 +261,22 @@ def get_html_template():
234
261
  CSS_PLACEHOLDER = "<!-- CSS INJECTION PLACEHOLDER -->"
235
262
 
236
263
  default_url = "https://github.com/Chainlit/chainlit"
237
- default_meta_image_url = "https://chainlit-cloud.s3.eu-west-3.amazonaws.com/logo/chainlit_banner.png"
264
+ default_meta_image_url = (
265
+ "https://chainlit-cloud.s3.eu-west-3.amazonaws.com/logo/chainlit_banner.png"
266
+ )
238
267
  url = config.ui.github or default_url
239
268
  meta_image_url = config.ui.custom_meta_image_url or default_meta_image_url
269
+ favicon_path = ROOT_PATH + "/favicon" if ROOT_PATH else "/favicon"
240
270
 
241
271
  tags = f"""<title>{config.ui.name}</title>
272
+ <link rel="icon" href="{favicon_path}" />
242
273
  <meta name="description" content="{config.ui.description}">
243
274
  <meta property="og:type" content="website">
244
275
  <meta property="og:title" content="{config.ui.name}">
245
276
  <meta property="og:description" content="{config.ui.description}">
246
277
  <meta property="og:image" content="{meta_image_url}">
247
- <meta property="og:url" content="{url}">"""
278
+ <meta property="og:url" content="{url}">
279
+ <meta property="og:root_path" content="{ROOT_PATH}">"""
248
280
 
249
281
  js = f"""<script>{f"window.theme = {json.dumps(config.ui.theme.to_dict())}; " if config.ui.theme else ""}</script>"""
250
282
 
@@ -274,6 +306,9 @@ def get_html_template():
274
306
  content = replace_between_tags(
275
307
  content, "<!-- FONT START -->", "<!-- FONT END -->", font
276
308
  )
309
+ if ROOT_PATH:
310
+ content = content.replace('href="/', f'href="{ROOT_PATH}/')
311
+ content = content.replace('src="/', f'src="{ROOT_PATH}/')
277
312
  return content
278
313
 
279
314
 
@@ -283,6 +318,7 @@ def get_user_facing_url(url: URL):
283
318
  Handles deployment with proxies (like cloud run).
284
319
  """
285
320
 
321
+ ROOT_PATH = os.environ.get("CHAINLIT_ROOT_PATH", "")
286
322
  chainlit_url = os.environ.get("CHAINLIT_URL")
287
323
 
288
324
  # No config, we keep the URL as is
@@ -298,15 +334,26 @@ def get_user_facing_url(url: URL):
298
334
  if config_url.path.endswith("/"):
299
335
  config_url = config_url.replace(path=config_url.path[:-1])
300
336
 
337
+ # Add ROOT_PATH to the final URL if it exists
338
+ if ROOT_PATH:
339
+ # Ensure ROOT_PATH starts with a slash
340
+ if not ROOT_PATH.startswith("/"):
341
+ ROOT_PATH = "/" + ROOT_PATH
342
+ # Ensure ROOT_PATH does not end with a slash
343
+ if ROOT_PATH.endswith("/"):
344
+ ROOT_PATH = ROOT_PATH[:-1]
345
+
346
+ return config_url.__str__() + ROOT_PATH + url.path
347
+
301
348
  return config_url.__str__() + url.path
302
349
 
303
350
 
304
- @app.get("/auth/config")
351
+ @router.get("/auth/config")
305
352
  async def auth(request: Request):
306
353
  return get_configuration()
307
354
 
308
355
 
309
- @app.post("/login")
356
+ @router.post("/login")
310
357
  async def login(form_data: OAuth2PasswordRequestForm = Depends()):
311
358
  if not config.code.password_auth_callback:
312
359
  raise HTTPException(
@@ -335,14 +382,14 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends()):
335
382
  }
336
383
 
337
384
 
338
- @app.post("/logout")
385
+ @router.post("/logout")
339
386
  async def logout(request: Request, response: Response):
340
387
  if config.code.on_logout:
341
388
  return await config.code.on_logout(request, response)
342
389
  return {"success": True}
343
390
 
344
391
 
345
- @app.post("/auth/header")
392
+ @router.post("/auth/header")
346
393
  async def header_auth(request: Request):
347
394
  if not config.code.header_auth_callback:
348
395
  raise HTTPException(
@@ -371,7 +418,7 @@ async def header_auth(request: Request):
371
418
  }
372
419
 
373
420
 
374
- @app.get("/auth/oauth/{provider_id}")
421
+ @router.get("/auth/oauth/{provider_id}")
375
422
  async def oauth_login(provider_id: str, request: Request):
376
423
  if config.code.oauth_callback is None:
377
424
  raise HTTPException(
@@ -412,7 +459,7 @@ async def oauth_login(provider_id: str, request: Request):
412
459
  return response
413
460
 
414
461
 
415
- @app.get("/auth/oauth/{provider_id}/callback")
462
+ @router.get("/auth/oauth/{provider_id}/callback")
416
463
  async def oauth_callback(
417
464
  provider_id: str,
418
465
  request: Request,
@@ -496,41 +543,85 @@ async def oauth_callback(
496
543
  return response
497
544
 
498
545
 
499
- @app.post("/generation")
500
- async def generation(
501
- request: GenerationRequest,
502
- current_user: Annotated[Union[User, PersistedUser], Depends(get_current_user)],
546
+ # specific route for azure ad hybrid flow
547
+ @router.post("/auth/oauth/azure-ad-hybrid/callback")
548
+ async def oauth_azure_hf_callback(
549
+ request: Request,
550
+ error: Optional[str] = None,
551
+ code: Annotated[Optional[str], Form()] = None,
552
+ id_token: Annotated[Optional[str], Form()] = None,
503
553
  ):
504
- """Handle a completion request from the prompt playground."""
554
+ provider_id = "azure-ad-hybrid"
555
+ if config.code.oauth_callback is None:
556
+ raise HTTPException(
557
+ status_code=status.HTTP_400_BAD_REQUEST,
558
+ detail="No oauth_callback defined",
559
+ )
505
560
 
506
- providers = get_llm_providers()
561
+ provider = get_oauth_provider(provider_id)
562
+ if not provider:
563
+ raise HTTPException(
564
+ status_code=status.HTTP_404_NOT_FOUND,
565
+ detail=f"Provider {provider_id} not found",
566
+ )
507
567
 
508
- try:
509
- provider = [p for p in providers if p.id == request.generation.provider][0]
510
- except IndexError:
568
+ if error:
569
+ params = urllib.parse.urlencode(
570
+ {
571
+ "error": error,
572
+ }
573
+ )
574
+ response = RedirectResponse(
575
+ # FIXME: redirect to the right frontend base url to improve the dev environment
576
+ url=f"/login?{params}",
577
+ )
578
+ return response
579
+
580
+ if not code:
511
581
  raise HTTPException(
512
- status_code=404,
513
- detail=f"LLM provider '{request.generation.provider}' not found",
582
+ status_code=status.HTTP_400_BAD_REQUEST,
583
+ detail="Missing code",
514
584
  )
515
585
 
516
- trace_event("pp_create_completion")
517
- response = await provider.create_completion(request)
586
+ url = get_user_facing_url(request.url)
587
+ token = await provider.get_token(code, url)
518
588
 
519
- return response
589
+ (raw_user_data, default_user) = await provider.get_user_info(token)
520
590
 
591
+ user = await config.code.oauth_callback(
592
+ provider_id, token, raw_user_data, default_user, id_token
593
+ )
521
594
 
522
- @app.get("/project/llm-providers")
523
- async def get_providers(
524
- current_user: Annotated[Union[User, PersistedUser], Depends(get_current_user)]
525
- ):
526
- """List the providers."""
527
- trace_event("pp_get_llm_providers")
528
- providers = get_llm_providers()
529
- providers = [p.to_dict() for p in providers]
530
- return JSONResponse(content={"providers": providers})
595
+ if not user:
596
+ raise HTTPException(
597
+ status_code=status.HTTP_401_UNAUTHORIZED,
598
+ detail="Unauthorized",
599
+ )
531
600
 
601
+ access_token = create_jwt(user)
602
+
603
+ if data_layer := get_data_layer():
604
+ try:
605
+ await data_layer.create_user(user)
606
+ except Exception as e:
607
+ logger.error(f"Error creating user: {e}")
532
608
 
533
- @app.get("/project/translations")
609
+ params = urllib.parse.urlencode(
610
+ {
611
+ "access_token": access_token,
612
+ "token_type": "bearer",
613
+ }
614
+ )
615
+ response = RedirectResponse(
616
+ # FIXME: redirect to the right frontend base url to improve the dev environment
617
+ url=f"/login/callback?{params}",
618
+ status_code=302,
619
+ )
620
+ response.delete_cookie("oauth_state")
621
+ return response
622
+
623
+
624
+ @router.get("/project/translations")
534
625
  async def project_translations(
535
626
  language: str = Query(default="en-US", description="Language code"),
536
627
  ):
@@ -546,7 +637,7 @@ async def project_translations(
546
637
  )
547
638
 
548
639
 
549
- @app.get("/project/settings")
640
+ @router.get("/project/settings")
550
641
  async def project_settings(
551
642
  current_user: Annotated[Union[User, PersistedUser], Depends(get_current_user)],
552
643
  language: str = Query(default="en-US", description="Language code"),
@@ -562,9 +653,21 @@ async def project_settings(
562
653
  if chat_profiles:
563
654
  profiles = [p.to_dict() for p in chat_profiles]
564
655
 
656
+ starters = []
657
+ if config.code.set_starters:
658
+ starters = await config.code.set_starters(current_user)
659
+ if starters:
660
+ starters = [s.to_dict() for s in starters]
661
+
565
662
  if config.code.on_audio_chunk:
566
663
  config.features.audio.enabled = True
567
664
 
665
+ debug_url = None
666
+ data_layer = get_data_layer()
667
+
668
+ if data_layer and config.run.debug:
669
+ debug_url = await data_layer.build_debug_url()
670
+
568
671
  return JSONResponse(
569
672
  content={
570
673
  "ui": config.ui.to_dict(),
@@ -574,11 +677,13 @@ async def project_settings(
574
677
  "threadResumable": bool(config.code.on_chat_resume),
575
678
  "markdown": markdown,
576
679
  "chatProfiles": profiles,
680
+ "starters": starters,
681
+ "debugUrl": debug_url,
577
682
  }
578
683
  )
579
684
 
580
685
 
581
- @app.put("/feedback")
686
+ @router.put("/feedback")
582
687
  async def update_feedback(
583
688
  request: Request,
584
689
  update: UpdateFeedbackRequest,
@@ -597,7 +702,7 @@ async def update_feedback(
597
702
  return JSONResponse(content={"success": True, "feedbackId": feedback_id})
598
703
 
599
704
 
600
- @app.delete("/feedback")
705
+ @router.delete("/feedback")
601
706
  async def delete_feedback(
602
707
  request: Request,
603
708
  payload: DeleteFeedbackRequest,
@@ -616,7 +721,7 @@ async def delete_feedback(
616
721
  return JSONResponse(content={"success": True})
617
722
 
618
723
 
619
- @app.post("/project/threads")
724
+ @router.post("/project/threads")
620
725
  async def get_user_threads(
621
726
  request: Request,
622
727
  payload: GetThreadsRequest,
@@ -641,7 +746,7 @@ async def get_user_threads(
641
746
  return JSONResponse(content=res.to_dict())
642
747
 
643
748
 
644
- @app.get("/project/thread/{thread_id}")
749
+ @router.get("/project/thread/{thread_id}")
645
750
  async def get_thread(
646
751
  request: Request,
647
752
  thread_id: str,
@@ -659,7 +764,7 @@ async def get_thread(
659
764
  return JSONResponse(content=res)
660
765
 
661
766
 
662
- @app.get("/project/thread/{thread_id}/element/{element_id}")
767
+ @router.get("/project/thread/{thread_id}/element/{element_id}")
663
768
  async def get_thread_element(
664
769
  request: Request,
665
770
  thread_id: str,
@@ -678,7 +783,7 @@ async def get_thread_element(
678
783
  return JSONResponse(content=res)
679
784
 
680
785
 
681
- @app.delete("/project/thread")
786
+ @router.delete("/project/thread")
682
787
  async def delete_thread(
683
788
  request: Request,
684
789
  payload: DeleteThreadRequest,
@@ -699,7 +804,7 @@ async def delete_thread(
699
804
  return JSONResponse(content={"success": True})
700
805
 
701
806
 
702
- @app.post("/project/file")
807
+ @router.post("/project/file")
703
808
  async def upload_file(
704
809
  session_id: str,
705
810
  file: UploadFile,
@@ -735,7 +840,7 @@ async def upload_file(
735
840
  return JSONResponse(file_response)
736
841
 
737
842
 
738
- @app.get("/project/file/{file_id}")
843
+ @router.get("/project/file/{file_id}")
739
844
  async def get_file(
740
845
  file_id: str,
741
846
  session_id: Optional[str] = None,
@@ -757,7 +862,7 @@ async def get_file(
757
862
  raise HTTPException(status_code=404, detail="File not found")
758
863
 
759
864
 
760
- @app.get("/files/{filename:path}")
865
+ @router.get("/files/{filename:path}")
761
866
  async def serve_file(
762
867
  filename: str,
763
868
  current_user: Annotated[Union[User, PersistedUser], Depends(get_current_user)],
@@ -775,7 +880,7 @@ async def serve_file(
775
880
  raise HTTPException(status_code=404, detail="File not found")
776
881
 
777
882
 
778
- @app.get("/favicon")
883
+ @router.get("/favicon")
779
884
  async def get_favicon():
780
885
  custom_favicon_path = os.path.join(APP_ROOT, "public", "favicon.*")
781
886
  files = glob.glob(custom_favicon_path)
@@ -790,7 +895,7 @@ async def get_favicon():
790
895
  return FileResponse(favicon_path, media_type=media_type)
791
896
 
792
897
 
793
- @app.get("/logo")
898
+ @router.get("/logo")
794
899
  async def get_logo(theme: Optional[Theme] = Query(Theme.light)):
795
900
  theme_value = theme.value if theme else Theme.light.value
796
901
  logo_path = None
@@ -812,19 +917,39 @@ async def get_logo(theme: Optional[Theme] = Query(Theme.light)):
812
917
  return FileResponse(logo_path, media_type=media_type)
813
918
 
814
919
 
815
- @app.head("/")
920
+ @router.get("/avatars/{avatar_id}")
921
+ async def get_avatar(avatar_id: str):
922
+ if avatar_id == "default":
923
+ avatar_id = config.ui.name
924
+
925
+ avatar_id = avatar_id.strip().lower().replace(" ", "_")
926
+
927
+ avatar_path = os.path.join(APP_ROOT, "public", "avatars", f"{avatar_id}.*")
928
+
929
+ files = glob.glob(avatar_path)
930
+
931
+ if files:
932
+ avatar_path = files[0]
933
+ media_type, _ = mimetypes.guess_type(avatar_path)
934
+ return FileResponse(avatar_path, media_type=media_type)
935
+ else:
936
+ return await get_favicon()
937
+
938
+
939
+ @router.head("/")
816
940
  def status_check():
817
941
  return {"message": "Site is operational"}
818
942
 
819
943
 
820
- def register_wildcard_route_handler():
821
- @app.get("/{path:path}")
822
- async def serve(request: Request, path: str):
823
- html_template = get_html_template()
824
- """Serve the UI files."""
825
- response = HTMLResponse(content=html_template, status_code=200)
944
+ @router.get("/{full_path:path}")
945
+ async def serve():
946
+ html_template = get_html_template()
947
+ """Serve the UI files."""
948
+ response = HTMLResponse(content=html_template, status_code=200)
949
+
950
+ return response
826
951
 
827
- return response
828
952
 
953
+ app.include_router(router)
829
954
 
830
955
  import chainlit.socket # noqa
chainlit/slack/app.py CHANGED
@@ -71,7 +71,6 @@ class SlackEmitter(BaseChainlitEmitter):
71
71
  is_message = step_type in [
72
72
  "user_message",
73
73
  "assistant_message",
74
- "system_message",
75
74
  ]
76
75
  is_chain_of_thought = bool(step_dict.get("parentId"))
77
76
  is_empty_output = not step_dict.get("output")
@@ -177,8 +176,8 @@ async def get_user(slack_user_id: str):
177
176
  slack_user = await slack_app.client.users_info(user=slack_user_id)
178
177
  slack_user_profile = slack_user["user"]["profile"]
179
178
 
180
- user_email = slack_user_profile.get("email")
181
- user = User(identifier=USER_PREFIX + user_email, metadata=slack_user_profile)
179
+ user_identifier = slack_user_profile.get("email") or slack_user_id
180
+ user = User(identifier=USER_PREFIX + user_identifier, metadata=slack_user_profile)
182
181
 
183
182
  users_by_slack_id[slack_user_id] = user
184
183