chainlit 2.7.0__tar.gz → 2.7.1__tar.gz
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-2.7.0 → chainlit-2.7.1}/PKG-INFO +1 -1
- {chainlit-2.7.0 → chainlit-2.7.1}/pyproject.toml +15 -3
- chainlit-2.7.0/build.py +0 -102
- chainlit-2.7.0/chainlit/__init__.py +0 -207
- chainlit-2.7.0/chainlit/__main__.py +0 -4
- chainlit-2.7.0/chainlit/_utils.py +0 -8
- chainlit-2.7.0/chainlit/action.py +0 -33
- chainlit-2.7.0/chainlit/auth/__init__.py +0 -95
- chainlit-2.7.0/chainlit/auth/cookie.py +0 -197
- chainlit-2.7.0/chainlit/auth/jwt.py +0 -42
- chainlit-2.7.0/chainlit/cache.py +0 -45
- chainlit-2.7.0/chainlit/callbacks.py +0 -433
- chainlit-2.7.0/chainlit/chat_context.py +0 -64
- chainlit-2.7.0/chainlit/chat_settings.py +0 -34
- chainlit-2.7.0/chainlit/cli/__init__.py +0 -235
- chainlit-2.7.0/chainlit/config.py +0 -621
- chainlit-2.7.0/chainlit/context.py +0 -112
- chainlit-2.7.0/chainlit/data/__init__.py +0 -111
- chainlit-2.7.0/chainlit/data/acl.py +0 -19
- chainlit-2.7.0/chainlit/data/base.py +0 -107
- chainlit-2.7.0/chainlit/data/chainlit_data_layer.py +0 -687
- chainlit-2.7.0/chainlit/data/dynamodb.py +0 -616
- chainlit-2.7.0/chainlit/data/literalai.py +0 -501
- chainlit-2.7.0/chainlit/data/sql_alchemy.py +0 -741
- chainlit-2.7.0/chainlit/data/storage_clients/__init__.py +0 -0
- chainlit-2.7.0/chainlit/data/storage_clients/azure.py +0 -84
- chainlit-2.7.0/chainlit/data/storage_clients/azure_blob.py +0 -94
- chainlit-2.7.0/chainlit/data/storage_clients/base.py +0 -28
- chainlit-2.7.0/chainlit/data/storage_clients/gcs.py +0 -101
- chainlit-2.7.0/chainlit/data/storage_clients/s3.py +0 -88
- chainlit-2.7.0/chainlit/data/utils.py +0 -29
- chainlit-2.7.0/chainlit/discord/__init__.py +0 -6
- chainlit-2.7.0/chainlit/discord/app.py +0 -364
- chainlit-2.7.0/chainlit/element.py +0 -454
- chainlit-2.7.0/chainlit/emitter.py +0 -450
- chainlit-2.7.0/chainlit/hello.py +0 -12
- chainlit-2.7.0/chainlit/input_widget.py +0 -182
- chainlit-2.7.0/chainlit/langchain/__init__.py +0 -6
- chainlit-2.7.0/chainlit/langchain/callbacks.py +0 -682
- chainlit-2.7.0/chainlit/langflow/__init__.py +0 -25
- chainlit-2.7.0/chainlit/llama_index/__init__.py +0 -6
- chainlit-2.7.0/chainlit/llama_index/callbacks.py +0 -206
- chainlit-2.7.0/chainlit/logger.py +0 -16
- chainlit-2.7.0/chainlit/markdown.py +0 -57
- chainlit-2.7.0/chainlit/mcp.py +0 -99
- chainlit-2.7.0/chainlit/message.py +0 -619
- chainlit-2.7.0/chainlit/mistralai/__init__.py +0 -50
- chainlit-2.7.0/chainlit/oauth_providers.py +0 -835
- chainlit-2.7.0/chainlit/openai/__init__.py +0 -53
- chainlit-2.7.0/chainlit/py.typed +0 -0
- chainlit-2.7.0/chainlit/secret.py +0 -9
- chainlit-2.7.0/chainlit/semantic_kernel/__init__.py +0 -111
- chainlit-2.7.0/chainlit/server.py +0 -1616
- chainlit-2.7.0/chainlit/session.py +0 -304
- chainlit-2.7.0/chainlit/sidebar.py +0 -55
- chainlit-2.7.0/chainlit/slack/__init__.py +0 -6
- chainlit-2.7.0/chainlit/slack/app.py +0 -427
- chainlit-2.7.0/chainlit/socket.py +0 -381
- chainlit-2.7.0/chainlit/step.py +0 -490
- chainlit-2.7.0/chainlit/sync.py +0 -43
- chainlit-2.7.0/chainlit/teams/__init__.py +0 -6
- chainlit-2.7.0/chainlit/teams/app.py +0 -348
- chainlit-2.7.0/chainlit/translations/bn.json +0 -214
- chainlit-2.7.0/chainlit/translations/el-GR.json +0 -214
- chainlit-2.7.0/chainlit/translations/en-US.json +0 -214
- chainlit-2.7.0/chainlit/translations/fr-FR.json +0 -214
- chainlit-2.7.0/chainlit/translations/gu.json +0 -214
- chainlit-2.7.0/chainlit/translations/he-IL.json +0 -214
- chainlit-2.7.0/chainlit/translations/hi.json +0 -214
- chainlit-2.7.0/chainlit/translations/ja.json +0 -214
- chainlit-2.7.0/chainlit/translations/kn.json +0 -214
- chainlit-2.7.0/chainlit/translations/ml.json +0 -214
- chainlit-2.7.0/chainlit/translations/mr.json +0 -214
- chainlit-2.7.0/chainlit/translations/nl.json +0 -214
- chainlit-2.7.0/chainlit/translations/ta.json +0 -214
- chainlit-2.7.0/chainlit/translations/te.json +0 -214
- chainlit-2.7.0/chainlit/translations/zh-CN.json +0 -214
- chainlit-2.7.0/chainlit/translations.py +0 -60
- chainlit-2.7.0/chainlit/types.py +0 -334
- chainlit-2.7.0/chainlit/user.py +0 -43
- chainlit-2.7.0/chainlit/user_session.py +0 -153
- chainlit-2.7.0/chainlit/utils.py +0 -173
- chainlit-2.7.0/chainlit/version.py +0 -8
- chainlit-2.7.0/tests/__init__.py +0 -1
- chainlit-2.7.0/tests/auth/__init__.py +0 -0
- chainlit-2.7.0/tests/auth/test_cookie.py +0 -148
- chainlit-2.7.0/tests/conftest.py +0 -115
- chainlit-2.7.0/tests/data/__init__.py +0 -0
- chainlit-2.7.0/tests/data/conftest.py +0 -21
- chainlit-2.7.0/tests/data/storage_clients/test_gcs.py +0 -337
- chainlit-2.7.0/tests/data/storage_clients/test_s3.py +0 -47
- chainlit-2.7.0/tests/data/test_get_data_layer.py +0 -18
- chainlit-2.7.0/tests/data/test_literalai.py +0 -1063
- chainlit-2.7.0/tests/data/test_sql_alchemy.py +0 -218
- chainlit-2.7.0/tests/llama_index/test_callbacks.py +0 -97
- chainlit-2.7.0/tests/test_callbacks.py +0 -595
- chainlit-2.7.0/tests/test_context.py +0 -55
- chainlit-2.7.0/tests/test_emitter.py +0 -165
- chainlit-2.7.0/tests/test_server.py +0 -922
- chainlit-2.7.0/tests/test_slack_socket_mode.py +0 -54
- chainlit-2.7.0/tests/test_user_session.py +0 -14
- chainlit-2.7.0/uv.lock +0 -5982
- {chainlit-2.7.0 → chainlit-2.7.1}/.gitignore +0 -0
- {chainlit-2.7.0 → chainlit-2.7.1}/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "chainlit"
|
|
3
|
-
version = "2.7.
|
|
3
|
+
version = "2.7.1"
|
|
4
4
|
keywords = [
|
|
5
5
|
"LLM",
|
|
6
6
|
"Agents",
|
|
@@ -109,11 +109,23 @@ custom-data = [
|
|
|
109
109
|
requires = ["hatchling"]
|
|
110
110
|
build-backend = "hatchling.build"
|
|
111
111
|
|
|
112
|
+
[tool.hatch.build]
|
|
113
|
+
exclude = [
|
|
114
|
+
"chainlit/frontend/**/*",
|
|
115
|
+
"chainlit/copilot/**/**/"
|
|
116
|
+
]
|
|
117
|
+
|
|
112
118
|
[tool.hatch.build.targets.wheel]
|
|
113
119
|
packages = ["chainlit"]
|
|
114
120
|
include = [
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
"chainlit/frontend/dist/**/*",
|
|
122
|
+
"chainlit/copilot/dist/**/*"
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
[tool.hatch.build.targets.sdist]
|
|
126
|
+
include = [
|
|
127
|
+
"chainlit/frontend/dist/**/*",
|
|
128
|
+
"chainlit/copilot/dist/**/*"
|
|
117
129
|
]
|
|
118
130
|
|
|
119
131
|
[tool.mypy]
|
chainlit-2.7.0/build.py
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
"""Build script gets called on uv/pip build."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import pathlib
|
|
5
|
-
import shutil
|
|
6
|
-
import subprocess
|
|
7
|
-
import sys
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class BuildError(Exception):
|
|
11
|
-
"""Custom exception for build failures"""
|
|
12
|
-
|
|
13
|
-
pass
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def run_subprocess(cmd: list[str], cwd: os.PathLike) -> None:
|
|
17
|
-
"""
|
|
18
|
-
Run a subprocess, allowing natural signal propagation.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
cmd: Command and arguments as a list of strings
|
|
22
|
-
cwd: Working directory for the subprocess
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
print(f"-- Running: {' '.join(cmd)}")
|
|
26
|
-
subprocess.run(cmd, cwd=cwd, check=True)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def pnpm_install(project_root, pnpm_path):
|
|
30
|
-
run_subprocess([pnpm_path, "install", "--frozen-lockfile"], project_root)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def pnpm_buildui(project_root, pnpm_path):
|
|
34
|
-
run_subprocess([pnpm_path, "buildUi"], project_root)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def copy_directory(src, dst, description):
|
|
38
|
-
"""Copy directory with proper error handling"""
|
|
39
|
-
print(f"Copying {src} to {dst}")
|
|
40
|
-
try:
|
|
41
|
-
dst.mkdir(parents=True, exist_ok=True)
|
|
42
|
-
shutil.copytree(src, dst, dirs_exist_ok=True)
|
|
43
|
-
except KeyboardInterrupt:
|
|
44
|
-
print("\nInterrupt received during copy operation...")
|
|
45
|
-
# Clean up partial copies
|
|
46
|
-
if dst.exists():
|
|
47
|
-
shutil.rmtree(dst)
|
|
48
|
-
raise
|
|
49
|
-
except Exception as e:
|
|
50
|
-
raise BuildError(f"Failed to copy {src} to {dst}: {e!s}")
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def copy_frontend(project_root):
|
|
54
|
-
"""Copy the frontend dist directory to the backend for inclusion in the package."""
|
|
55
|
-
backend_frontend_dir = project_root / "backend" / "chainlit" / "frontend" / "dist"
|
|
56
|
-
frontend_dist = project_root / "frontend" / "dist"
|
|
57
|
-
copy_directory(frontend_dist, backend_frontend_dir, "frontend assets")
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def copy_copilot(project_root):
|
|
61
|
-
"""Copy the copilot dist directory to the backend for inclusion in the package."""
|
|
62
|
-
backend_copilot_dir = project_root / "backend" / "chainlit" / "copilot" / "dist"
|
|
63
|
-
copilot_dist = project_root / "libs" / "copilot" / "dist"
|
|
64
|
-
copy_directory(copilot_dist, backend_copilot_dir, "copilot assets")
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def build():
|
|
68
|
-
"""Main build function with proper error handling"""
|
|
69
|
-
|
|
70
|
-
print(
|
|
71
|
-
"\n-- Building frontend, this might take a while!\n\n"
|
|
72
|
-
" If you don't need to build the frontend and just want dependencies installed, use:\n"
|
|
73
|
-
" `uv sync --no-build`\n"
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
try:
|
|
77
|
-
# Find directory containing this file
|
|
78
|
-
backend_dir = pathlib.Path(__file__).resolve().parent
|
|
79
|
-
project_root = backend_dir.parent
|
|
80
|
-
|
|
81
|
-
pnpm = shutil.which("pnpm")
|
|
82
|
-
if not pnpm:
|
|
83
|
-
raise BuildError("pnpm not found!")
|
|
84
|
-
|
|
85
|
-
pnpm_install(project_root, pnpm)
|
|
86
|
-
pnpm_buildui(project_root, pnpm)
|
|
87
|
-
copy_frontend(project_root)
|
|
88
|
-
copy_copilot(project_root)
|
|
89
|
-
|
|
90
|
-
except KeyboardInterrupt:
|
|
91
|
-
print("\nBuild interrupted by user")
|
|
92
|
-
sys.exit(1)
|
|
93
|
-
except BuildError as e:
|
|
94
|
-
print(f"\nBuild failed: {e!s}")
|
|
95
|
-
sys.exit(1)
|
|
96
|
-
except Exception as e:
|
|
97
|
-
print(f"\nUnexpected error: {e!s}")
|
|
98
|
-
sys.exit(1)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if __name__ == "__main__":
|
|
102
|
-
build()
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
from dotenv import load_dotenv
|
|
4
|
-
|
|
5
|
-
# ruff: noqa: E402
|
|
6
|
-
# Keep this here to ensure imports have environment available.
|
|
7
|
-
env_file = os.getenv("CHAINLIT_ENV_FILE", ".env")
|
|
8
|
-
env_found = load_dotenv(dotenv_path=os.path.join(os.getcwd(), env_file))
|
|
9
|
-
|
|
10
|
-
from chainlit.logger import logger
|
|
11
|
-
|
|
12
|
-
if env_found:
|
|
13
|
-
logger.info(f"Loaded {env_file} file")
|
|
14
|
-
|
|
15
|
-
import asyncio
|
|
16
|
-
from typing import TYPE_CHECKING, Any, Dict
|
|
17
|
-
|
|
18
|
-
from literalai import ChatGeneration, CompletionGeneration, GenerationMessage
|
|
19
|
-
from pydantic.dataclasses import dataclass
|
|
20
|
-
|
|
21
|
-
import chainlit.input_widget as input_widget
|
|
22
|
-
from chainlit.action import Action
|
|
23
|
-
from chainlit.cache import cache
|
|
24
|
-
from chainlit.chat_context import chat_context
|
|
25
|
-
from chainlit.chat_settings import ChatSettings
|
|
26
|
-
from chainlit.context import context
|
|
27
|
-
from chainlit.element import (
|
|
28
|
-
Audio,
|
|
29
|
-
CustomElement,
|
|
30
|
-
Dataframe,
|
|
31
|
-
File,
|
|
32
|
-
Image,
|
|
33
|
-
Pdf,
|
|
34
|
-
Plotly,
|
|
35
|
-
Pyplot,
|
|
36
|
-
Task,
|
|
37
|
-
TaskList,
|
|
38
|
-
TaskStatus,
|
|
39
|
-
Text,
|
|
40
|
-
Video,
|
|
41
|
-
)
|
|
42
|
-
from chainlit.message import (
|
|
43
|
-
AskActionMessage,
|
|
44
|
-
AskElementMessage,
|
|
45
|
-
AskFileMessage,
|
|
46
|
-
AskUserMessage,
|
|
47
|
-
ErrorMessage,
|
|
48
|
-
Message,
|
|
49
|
-
)
|
|
50
|
-
from chainlit.sidebar import ElementSidebar
|
|
51
|
-
from chainlit.step import Step, step
|
|
52
|
-
from chainlit.sync import make_async, run_sync
|
|
53
|
-
from chainlit.types import ChatProfile, InputAudioChunk, OutputAudioChunk, Starter
|
|
54
|
-
from chainlit.user import PersistedUser, User
|
|
55
|
-
from chainlit.user_session import user_session
|
|
56
|
-
from chainlit.utils import make_module_getattr
|
|
57
|
-
from chainlit.version import __version__
|
|
58
|
-
|
|
59
|
-
from .callbacks import (
|
|
60
|
-
action_callback,
|
|
61
|
-
author_rename,
|
|
62
|
-
data_layer,
|
|
63
|
-
header_auth_callback,
|
|
64
|
-
oauth_callback,
|
|
65
|
-
on_app_shutdown,
|
|
66
|
-
on_app_startup,
|
|
67
|
-
on_audio_chunk,
|
|
68
|
-
on_audio_end,
|
|
69
|
-
on_audio_start,
|
|
70
|
-
on_chat_end,
|
|
71
|
-
on_chat_resume,
|
|
72
|
-
on_chat_start,
|
|
73
|
-
on_feedback,
|
|
74
|
-
on_logout,
|
|
75
|
-
on_mcp_connect,
|
|
76
|
-
on_mcp_disconnect,
|
|
77
|
-
on_message,
|
|
78
|
-
on_settings_update,
|
|
79
|
-
on_stop,
|
|
80
|
-
on_window_message,
|
|
81
|
-
password_auth_callback,
|
|
82
|
-
send_window_message,
|
|
83
|
-
set_chat_profiles,
|
|
84
|
-
set_starters,
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
if TYPE_CHECKING:
|
|
88
|
-
from chainlit.langchain.callbacks import (
|
|
89
|
-
AsyncLangchainCallbackHandler,
|
|
90
|
-
LangchainCallbackHandler,
|
|
91
|
-
)
|
|
92
|
-
from chainlit.llama_index.callbacks import LlamaIndexCallbackHandler
|
|
93
|
-
from chainlit.mistralai import instrument_mistralai
|
|
94
|
-
from chainlit.openai import instrument_openai
|
|
95
|
-
from chainlit.semantic_kernel import SemanticKernelFilter
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def sleep(duration: int):
|
|
99
|
-
"""
|
|
100
|
-
Sleep for a given duration.
|
|
101
|
-
Args:
|
|
102
|
-
duration (int): The duration in seconds.
|
|
103
|
-
"""
|
|
104
|
-
return asyncio.sleep(duration)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@dataclass()
|
|
108
|
-
class CopilotFunction:
|
|
109
|
-
name: str
|
|
110
|
-
args: Dict[str, Any]
|
|
111
|
-
|
|
112
|
-
def acall(self):
|
|
113
|
-
return context.emitter.send_call_fn(self.name, self.args)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
__getattr__ = make_module_getattr(
|
|
117
|
-
{
|
|
118
|
-
"LangchainCallbackHandler": "chainlit.langchain.callbacks",
|
|
119
|
-
"AsyncLangchainCallbackHandler": "chainlit.langchain.callbacks",
|
|
120
|
-
"LlamaIndexCallbackHandler": "chainlit.llama_index.callbacks",
|
|
121
|
-
"instrument_openai": "chainlit.openai",
|
|
122
|
-
"instrument_mistralai": "chainlit.mistralai",
|
|
123
|
-
"SemanticKernelFilter": "chainlit.semantic_kernel",
|
|
124
|
-
"server": "chainlit.server",
|
|
125
|
-
}
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
__all__ = [
|
|
129
|
-
"Action",
|
|
130
|
-
"AskActionMessage",
|
|
131
|
-
"AskElementMessage",
|
|
132
|
-
"AskFileMessage",
|
|
133
|
-
"AskUserMessage",
|
|
134
|
-
"AsyncLangchainCallbackHandler",
|
|
135
|
-
"Audio",
|
|
136
|
-
"ChatGeneration",
|
|
137
|
-
"ChatProfile",
|
|
138
|
-
"ChatSettings",
|
|
139
|
-
"CompletionGeneration",
|
|
140
|
-
"CopilotFunction",
|
|
141
|
-
"CustomElement",
|
|
142
|
-
"Dataframe",
|
|
143
|
-
"ElementSidebar",
|
|
144
|
-
"ErrorMessage",
|
|
145
|
-
"File",
|
|
146
|
-
"GenerationMessage",
|
|
147
|
-
"Image",
|
|
148
|
-
"InputAudioChunk",
|
|
149
|
-
"LangchainCallbackHandler",
|
|
150
|
-
"LlamaIndexCallbackHandler",
|
|
151
|
-
"Message",
|
|
152
|
-
"OutputAudioChunk",
|
|
153
|
-
"Pdf",
|
|
154
|
-
"PersistedUser",
|
|
155
|
-
"Plotly",
|
|
156
|
-
"Pyplot",
|
|
157
|
-
"SemanticKernelFilter",
|
|
158
|
-
"Starter",
|
|
159
|
-
"Step",
|
|
160
|
-
"Task",
|
|
161
|
-
"TaskList",
|
|
162
|
-
"TaskStatus",
|
|
163
|
-
"Text",
|
|
164
|
-
"User",
|
|
165
|
-
"Video",
|
|
166
|
-
"__version__",
|
|
167
|
-
"action_callback",
|
|
168
|
-
"author_rename",
|
|
169
|
-
"cache",
|
|
170
|
-
"chat_context",
|
|
171
|
-
"context",
|
|
172
|
-
"data_layer",
|
|
173
|
-
"header_auth_callback",
|
|
174
|
-
"input_widget",
|
|
175
|
-
"instrument_mistralai",
|
|
176
|
-
"instrument_openai",
|
|
177
|
-
"make_async",
|
|
178
|
-
"oauth_callback",
|
|
179
|
-
"on_app_shutdown",
|
|
180
|
-
"on_app_startup",
|
|
181
|
-
"on_audio_chunk",
|
|
182
|
-
"on_audio_end",
|
|
183
|
-
"on_audio_start",
|
|
184
|
-
"on_chat_end",
|
|
185
|
-
"on_chat_resume",
|
|
186
|
-
"on_chat_start",
|
|
187
|
-
"on_feedback",
|
|
188
|
-
"on_logout",
|
|
189
|
-
"on_mcp_connect",
|
|
190
|
-
"on_mcp_disconnect",
|
|
191
|
-
"on_message",
|
|
192
|
-
"on_settings_update",
|
|
193
|
-
"on_stop",
|
|
194
|
-
"on_window_message",
|
|
195
|
-
"password_auth_callback",
|
|
196
|
-
"run_sync",
|
|
197
|
-
"send_window_message",
|
|
198
|
-
"set_chat_profiles",
|
|
199
|
-
"set_starters",
|
|
200
|
-
"sleep",
|
|
201
|
-
"step",
|
|
202
|
-
"user_session",
|
|
203
|
-
]
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def __dir__():
|
|
207
|
-
return __all__
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"""Util functions which are explicitly not part of the public API."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def is_path_inside(child_path: Path, parent_path: Path) -> bool:
|
|
7
|
-
"""Check if the child path is inside the parent path."""
|
|
8
|
-
return parent_path.resolve() in child_path.resolve().parents
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import uuid
|
|
2
|
-
from typing import Dict, Optional
|
|
3
|
-
|
|
4
|
-
from dataclasses_json import DataClassJsonMixin
|
|
5
|
-
from pydantic import Field
|
|
6
|
-
from pydantic.dataclasses import dataclass
|
|
7
|
-
|
|
8
|
-
from chainlit.context import context
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass
|
|
12
|
-
class Action(DataClassJsonMixin):
|
|
13
|
-
# Name of the action, this should be used in the action_callback
|
|
14
|
-
name: str
|
|
15
|
-
# The parameters to call this action with.
|
|
16
|
-
payload: Dict
|
|
17
|
-
# The label of the action. This is what the user will see.
|
|
18
|
-
label: str = ""
|
|
19
|
-
# The tooltip of the action button. This is what the user will see when they hover the action.
|
|
20
|
-
tooltip: str = ""
|
|
21
|
-
# The lucid icon name for this action.
|
|
22
|
-
icon: Optional[str] = None
|
|
23
|
-
# This should not be set manually, only used internally.
|
|
24
|
-
forId: Optional[str] = None
|
|
25
|
-
# The ID of the action
|
|
26
|
-
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
27
|
-
|
|
28
|
-
async def send(self, for_id: str):
|
|
29
|
-
self.forId = for_id
|
|
30
|
-
await context.emitter.emit("action", self.to_dict())
|
|
31
|
-
|
|
32
|
-
async def remove(self):
|
|
33
|
-
await context.emitter.emit("remove_action", self.to_dict())
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
from fastapi import Depends, HTTPException
|
|
4
|
-
|
|
5
|
-
from chainlit.config import config
|
|
6
|
-
from chainlit.data import get_data_layer
|
|
7
|
-
from chainlit.logger import logger
|
|
8
|
-
from chainlit.oauth_providers import get_configured_oauth_providers
|
|
9
|
-
|
|
10
|
-
from .cookie import (
|
|
11
|
-
OAuth2PasswordBearerWithCookie,
|
|
12
|
-
clear_auth_cookie,
|
|
13
|
-
get_token_from_cookies,
|
|
14
|
-
set_auth_cookie,
|
|
15
|
-
)
|
|
16
|
-
from .jwt import create_jwt, decode_jwt, get_jwt_secret
|
|
17
|
-
|
|
18
|
-
reuseable_oauth = OAuth2PasswordBearerWithCookie(tokenUrl="/login", auto_error=False)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def ensure_jwt_secret():
|
|
22
|
-
if require_login() and get_jwt_secret() is None:
|
|
23
|
-
raise ValueError(
|
|
24
|
-
"You must provide a JWT secret in the environment to use authentication. Run `chainlit create-secret` to generate one."
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def is_oauth_enabled():
|
|
29
|
-
return config.code.oauth_callback and len(get_configured_oauth_providers()) > 0
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def require_login():
|
|
33
|
-
return bool(os.environ.get("CHAINLIT_AUTH_SECRET"))
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def get_configuration():
|
|
37
|
-
return {
|
|
38
|
-
"requireLogin": require_login(),
|
|
39
|
-
"passwordAuth": config.code.password_auth_callback is not None,
|
|
40
|
-
"headerAuth": config.code.header_auth_callback is not None,
|
|
41
|
-
"oauthProviders": (
|
|
42
|
-
get_configured_oauth_providers() if is_oauth_enabled() else []
|
|
43
|
-
),
|
|
44
|
-
"default_theme": config.ui.default_theme,
|
|
45
|
-
"ui": {
|
|
46
|
-
"login_page_image": config.ui.login_page_image,
|
|
47
|
-
"login_page_image_filter": config.ui.login_page_image_filter,
|
|
48
|
-
"login_page_image_dark_filter": config.ui.login_page_image_dark_filter,
|
|
49
|
-
},
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
async def authenticate_user(token: str = Depends(reuseable_oauth)):
|
|
54
|
-
try:
|
|
55
|
-
user = decode_jwt(token)
|
|
56
|
-
except Exception as e:
|
|
57
|
-
raise HTTPException(
|
|
58
|
-
status_code=401, detail="Invalid authentication token"
|
|
59
|
-
) from e
|
|
60
|
-
|
|
61
|
-
if data_layer := get_data_layer():
|
|
62
|
-
# Get or create persistent user if we've a data layer available.
|
|
63
|
-
try:
|
|
64
|
-
persisted_user = await data_layer.get_user(user.identifier)
|
|
65
|
-
if persisted_user is None:
|
|
66
|
-
persisted_user = await data_layer.create_user(user)
|
|
67
|
-
assert persisted_user
|
|
68
|
-
except Exception as e:
|
|
69
|
-
logger.exception("Unable to get persisted_user from data layer: %s", e)
|
|
70
|
-
return user
|
|
71
|
-
|
|
72
|
-
if user and user.display_name:
|
|
73
|
-
# Copy ephemeral display_name from authenticated user to persistent user.
|
|
74
|
-
persisted_user.display_name = user.display_name
|
|
75
|
-
|
|
76
|
-
return persisted_user
|
|
77
|
-
|
|
78
|
-
return user
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
async def get_current_user(token: str = Depends(reuseable_oauth)):
|
|
82
|
-
if not require_login():
|
|
83
|
-
return None
|
|
84
|
-
|
|
85
|
-
return await authenticate_user(token)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
__all__ = [
|
|
89
|
-
"clear_auth_cookie",
|
|
90
|
-
"create_jwt",
|
|
91
|
-
"get_configuration",
|
|
92
|
-
"get_current_user",
|
|
93
|
-
"get_token_from_cookies",
|
|
94
|
-
"set_auth_cookie",
|
|
95
|
-
]
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import Literal, Optional, cast
|
|
3
|
-
|
|
4
|
-
from fastapi import Request, Response
|
|
5
|
-
from fastapi.exceptions import HTTPException
|
|
6
|
-
from fastapi.security.base import SecurityBase
|
|
7
|
-
from fastapi.security.utils import get_authorization_scheme_param
|
|
8
|
-
from starlette.status import HTTP_401_UNAUTHORIZED
|
|
9
|
-
|
|
10
|
-
from chainlit.config import config
|
|
11
|
-
|
|
12
|
-
""" Module level cookie settings. """
|
|
13
|
-
_cookie_samesite = cast(
|
|
14
|
-
Literal["lax", "strict", "none"],
|
|
15
|
-
os.environ.get("CHAINLIT_COOKIE_SAMESITE", "lax"),
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
assert _cookie_samesite in [
|
|
19
|
-
"lax",
|
|
20
|
-
"strict",
|
|
21
|
-
"none",
|
|
22
|
-
], (
|
|
23
|
-
"Invalid value for CHAINLIT_COOKIE_SAMESITE. Must be one of 'lax', 'strict' or 'none'."
|
|
24
|
-
)
|
|
25
|
-
_cookie_secure = _cookie_samesite == "none"
|
|
26
|
-
if _cookie_root_path := os.environ.get("CHAINLIT_ROOT_PATH", None):
|
|
27
|
-
_cookie_path = os.environ.get(_cookie_root_path, "/")
|
|
28
|
-
else:
|
|
29
|
-
_cookie_path = os.environ.get("CHAINLIT_AUTH_COOKIE_PATH", "/")
|
|
30
|
-
_state_cookie_lifetime = 3 * 60 # 3m
|
|
31
|
-
_auth_cookie_name = os.environ.get("CHAINLIT_AUTH_COOKIE_NAME", "access_token")
|
|
32
|
-
_state_cookie_name = "oauth_state"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class OAuth2PasswordBearerWithCookie(SecurityBase):
|
|
36
|
-
"""
|
|
37
|
-
OAuth2 password flow with cookie support with fallback to bearer token.
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
def __init__(
|
|
41
|
-
self,
|
|
42
|
-
tokenUrl: str,
|
|
43
|
-
scheme_name: Optional[str] = None,
|
|
44
|
-
auto_error: bool = True,
|
|
45
|
-
):
|
|
46
|
-
self.tokenUrl = tokenUrl
|
|
47
|
-
self.scheme_name = scheme_name or self.__class__.__name__
|
|
48
|
-
self.auto_error = auto_error
|
|
49
|
-
|
|
50
|
-
async def __call__(self, request: Request) -> Optional[str]:
|
|
51
|
-
# First try to get the token from the cookie
|
|
52
|
-
token = get_token_from_cookies(request.cookies)
|
|
53
|
-
|
|
54
|
-
# If no cookie, try the Authorization header as fallback
|
|
55
|
-
if not token:
|
|
56
|
-
# TODO: Only bother to check if cookie auth is explicitly disabled.
|
|
57
|
-
authorization = request.headers.get("Authorization")
|
|
58
|
-
if authorization:
|
|
59
|
-
scheme, token = get_authorization_scheme_param(authorization)
|
|
60
|
-
if scheme.lower() != "bearer":
|
|
61
|
-
if self.auto_error:
|
|
62
|
-
raise HTTPException(
|
|
63
|
-
status_code=HTTP_401_UNAUTHORIZED,
|
|
64
|
-
detail="Invalid authentication credentials",
|
|
65
|
-
headers={"WWW-Authenticate": "Bearer"},
|
|
66
|
-
)
|
|
67
|
-
else:
|
|
68
|
-
return None
|
|
69
|
-
else:
|
|
70
|
-
if self.auto_error:
|
|
71
|
-
raise HTTPException(
|
|
72
|
-
status_code=HTTP_401_UNAUTHORIZED,
|
|
73
|
-
detail="Not authenticated",
|
|
74
|
-
headers={"WWW-Authenticate": "Bearer"},
|
|
75
|
-
)
|
|
76
|
-
else:
|
|
77
|
-
return None
|
|
78
|
-
|
|
79
|
-
return token
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _get_chunked_cookie(cookies: dict[str, str], name: str) -> Optional[str]:
|
|
83
|
-
# Gather all auth_chunk_i cookies, sorted by their index
|
|
84
|
-
chunk_parts = []
|
|
85
|
-
|
|
86
|
-
i = 0
|
|
87
|
-
while True:
|
|
88
|
-
cookie_key = f"{_auth_cookie_name}_{i}"
|
|
89
|
-
if cookie_key not in cookies:
|
|
90
|
-
break
|
|
91
|
-
|
|
92
|
-
chunk_parts.append(cookies[cookie_key])
|
|
93
|
-
i += 1
|
|
94
|
-
|
|
95
|
-
joined = "".join(chunk_parts)
|
|
96
|
-
|
|
97
|
-
return joined if joined != "" else None
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def get_token_from_cookies(cookies: dict[str, str]) -> Optional[str]:
|
|
101
|
-
"""
|
|
102
|
-
Read all chunk cookies and reconstruct the token
|
|
103
|
-
"""
|
|
104
|
-
|
|
105
|
-
# Default/unchunked cookies
|
|
106
|
-
if value := cookies.get(_auth_cookie_name):
|
|
107
|
-
return value
|
|
108
|
-
|
|
109
|
-
return _get_chunked_cookie(cookies, _auth_cookie_name)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def set_auth_cookie(request: Request, response: Response, token: str):
|
|
113
|
-
"""
|
|
114
|
-
Helper function to set the authentication cookie with secure parameters
|
|
115
|
-
and remove any leftover chunks from a previously larger token.
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
_chunk_size = 3000
|
|
119
|
-
|
|
120
|
-
existing_cookies = {
|
|
121
|
-
k for k in request.cookies.keys() if k.startswith(_auth_cookie_name)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if len(token) > _chunk_size:
|
|
125
|
-
chunks = [token[i : i + _chunk_size] for i in range(0, len(token), _chunk_size)]
|
|
126
|
-
|
|
127
|
-
for i, chunk in enumerate(chunks):
|
|
128
|
-
k = f"{_auth_cookie_name}_{i}"
|
|
129
|
-
|
|
130
|
-
response.set_cookie(
|
|
131
|
-
key=k,
|
|
132
|
-
value=chunk,
|
|
133
|
-
httponly=True,
|
|
134
|
-
secure=_cookie_secure,
|
|
135
|
-
samesite=_cookie_samesite,
|
|
136
|
-
max_age=config.project.user_session_timeout,
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
existing_cookies.discard(k)
|
|
140
|
-
else:
|
|
141
|
-
# Default (shorter cookies)
|
|
142
|
-
response.set_cookie(
|
|
143
|
-
key=_auth_cookie_name,
|
|
144
|
-
value=token,
|
|
145
|
-
httponly=True,
|
|
146
|
-
secure=_cookie_secure,
|
|
147
|
-
samesite=_cookie_samesite,
|
|
148
|
-
max_age=config.project.user_session_timeout,
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
existing_cookies.discard(_auth_cookie_name)
|
|
152
|
-
|
|
153
|
-
# Delete remaining prior cookies/cookie chunks
|
|
154
|
-
for k in existing_cookies:
|
|
155
|
-
response.delete_cookie(
|
|
156
|
-
key=k, path=_cookie_path, secure=_cookie_secure, samesite=_cookie_samesite
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def clear_auth_cookie(request: Request, response: Response):
|
|
161
|
-
"""
|
|
162
|
-
Helper function to clear the authentication cookie
|
|
163
|
-
"""
|
|
164
|
-
|
|
165
|
-
existing_cookies = {
|
|
166
|
-
k for k in request.cookies.keys() if k.startswith(_auth_cookie_name)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
for k in existing_cookies:
|
|
170
|
-
response.delete_cookie(
|
|
171
|
-
key=k, path=_cookie_path, secure=_cookie_secure, samesite=_cookie_samesite
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def set_oauth_state_cookie(response: Response, token: str):
|
|
176
|
-
response.set_cookie(
|
|
177
|
-
_state_cookie_name,
|
|
178
|
-
token,
|
|
179
|
-
httponly=True,
|
|
180
|
-
samesite=_cookie_samesite,
|
|
181
|
-
secure=_cookie_secure,
|
|
182
|
-
max_age=_state_cookie_lifetime,
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def validate_oauth_state_cookie(request: Request, state: str):
|
|
187
|
-
"""Check the state from the oauth provider against the browser cookie."""
|
|
188
|
-
|
|
189
|
-
oauth_state = request.cookies.get(_state_cookie_name)
|
|
190
|
-
|
|
191
|
-
if oauth_state != state:
|
|
192
|
-
raise Exception("oauth state does not correspond")
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def clear_oauth_state_cookie(response: Response):
|
|
196
|
-
"""Oauth complete, delete state token."""
|
|
197
|
-
response.delete_cookie(_state_cookie_name) # Do we set path here?
|