Skip to content

Commit

Permalink
prepare release (#1064)
Browse files Browse the repository at this point in the history
* Added the ability to specify a root path (sub directory

* @cl.on_system_message

* add system message

* make sure metadata is a dict

* add useAudio

* fix index error

* rc5

---------

Co-authored-by: JT <[email protected]>
  • Loading branch information
willydouhard and jayteaftw authored Jun 11, 2024
1 parent ca9a4c9 commit 28eb9e2
Show file tree
Hide file tree
Showing 92 changed files with 1,142 additions and 1,178 deletions.
41 changes: 11 additions & 30 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,42 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

### Added

- Teams integration

## [1.1.300rc3] - 2024-05-28

### Added

- Input streaming for tool calls
Nothing unreleased.

## [1.1.300rc2] - 2024-05-28
## [1.1.300rc5] - 2024-06-11

### Added

- `@cl.set_starters` and `cl.Starter` to suggest conversation starters to the user
- Teams integration
- Copilot can now send system_message to the chainlit server
- Expand copilot button

### Fix

- Reworked message padding and spacing
- Chat profile should now support non-ASCII characters (like chinese)

## [1.1.300rc1] - 2024-05-28

### Fix

- Scroll flickering when streaming
- Logo max height
- Remove duplicate new chat button in the sidebar

## [1.1.300rc0] - 2024-05-27

### Added

- Debug mode when starting with `-d`. Only available if the data layer supports it. This replaces the Prompt Playground.
- `@cl.set_starters` and `cl.Starter` to suggest conversation starters to the user
- `default` theme config in `config.toml`
- If only one OAuth provider is set, automatically redirect the user to it
- Input streaming for tool calls

### Changed

- **[BREAKING]** Custom endpoints have been reworked. You should now mount your Chainlit app as a FastAPI subapp.
- **[BREAKING]** Avatars have been reworked. `cl.Avatar` has been removed, instead place your avatars by name in `/public/avatars/*`
- **[BREAKING]** The `running`, `took_one` and `took_other` translations have been replaced by `used`.
- **[BREAKING]** `root` attribute of `cl.Step` has been removed. Use `cl.Message` to send root level messages.
- Chain of Thought has been reworked. Only steps of type `tool` will be displayed if `hide_cot` is false
- The `show_readme_as_default` config has been removed
- No longer collapse root level messages
- The blue alert "Continuing chat" has been removed.

### Fixed
### Fix

- The Chat Profile description should now disappear when not hovered.
- Error handling of steps has been improved
- No longer stream the first token twice
- Copilot should now work as expected even if the user is closing/reopening it
- Copilot CSS should no longer leak/be impacted by the host website CSS
- Fix various `cl.Context` errors
- Reworked message padding and spacing
- Chat profile should now support non-ASCII characters (like chinese)

## [1.1.202] - 2024-05-22

Expand Down
19 changes: 19 additions & 0 deletions backend/chainlit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from chainlit.context import context
from chainlit.element import (
Audio,
Component,
File,
Image,
Pdf,
Expand Down Expand Up @@ -189,6 +190,22 @@ def on_chat_resume(func: Callable[[ThreadDict], Any]) -> Callable:
return func


@trace
def on_system_message(func: Callable) -> Callable:
"""
Hook to react to a system message sent by the copilot.
Args:
func (Callable[], Any]): The hook to execute.
Returns:
Callable[], Any]: The decorated hook.
"""

config.code.on_system_message = wrap_user_function(func)
return func


@trace
def set_chat_profiles(
func: Callable[[Optional["User"]], List["ChatProfile"]]
Expand Down Expand Up @@ -376,6 +393,7 @@ def acall(self):
"Plotly",
"Image",
"Text",
"Component",
"Pyplot",
"File",
"Task",
Expand All @@ -398,6 +416,7 @@ def acall(self):
"on_chat_start",
"on_chat_end",
"on_chat_resume",
"on_system_message",
"on_stop",
"action_callback",
"author_rename",
Expand Down
26 changes: 20 additions & 6 deletions backend/chainlit/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@

nest_asyncio.apply()

from chainlit.auth import ensure_jwt_secret
from chainlit.cache import init_lc_cache
from chainlit.cli.utils import check_file
from chainlit.config import (
BACKEND_ROOT,
DEFAULT_HOST,
DEFAULT_PORT,
DEFAULT_ROOT_PATH,
config,
init_config,
lint_translations,
Expand All @@ -22,8 +21,8 @@
from chainlit.logger import logger
from chainlit.markdown import init_markdown
from chainlit.secret import random_secret
from chainlit.server import app, register_wildcard_route_handler
from chainlit.telemetry import trace_event
from chainlit.utils import check_file, ensure_jwt_secret


# Create the main command group for Chainlit CLI
Expand All @@ -35,8 +34,11 @@ def cli():

# Define the function to run Chainlit with provided options
def run_chainlit(target: str):
from chainlit.server import combined_asgi_app as app

host = os.environ.get("CHAINLIT_HOST", DEFAULT_HOST)
port = int(os.environ.get("CHAINLIT_PORT", DEFAULT_PORT))
root_path = os.environ.get("CHAINLIT_ROOT_PATH", DEFAULT_ROOT_PATH)

ssl_certfile = os.environ.get("CHAINLIT_SSL_CERT", None)
ssl_keyfile = os.environ.get("CHAINLIT_SSL_KEY", None)
Expand All @@ -52,6 +54,7 @@ def run_chainlit(target: str):

config.run.host = host
config.run.port = port
config.run.root_path = root_path

check_file(target)
# Load the module provided by the user
Expand All @@ -60,8 +63,6 @@ def run_chainlit(target: str):

ensure_jwt_secret()

register_wildcard_route_handler()

# Create the chainlit.md file if it doesn't exist
init_markdown(config.root)

Expand Down Expand Up @@ -145,8 +146,19 @@ async def start():
)
@click.option("--host", help="Specify a different host to run the server on")
@click.option("--port", help="Specify a different port to run the server on")
@click.option("--root-path", help="Specify a different root path to run the server on")
def chainlit_run(
target, watch, headless, debug, ci, no_cache, ssl_cert, ssl_key, host, port
target,
watch,
headless,
debug,
ci,
no_cache,
ssl_cert,
ssl_key,
host,
port,
root_path,
):
if host:
os.environ["CHAINLIT_HOST"] = host
Expand All @@ -159,6 +171,8 @@ def chainlit_run(
if ssl_cert:
os.environ["CHAINLIT_SSL_CERT"] = ssl_cert
os.environ["CHAINLIT_SSL_KEY"] = ssl_key
if root_path:
os.environ["CHAINLIT_ROOT_PATH"] = root_path
if ci:
logger.info("Running in CI mode")

Expand Down
24 changes: 0 additions & 24 deletions backend/chainlit/cli/utils.py

This file was deleted.

3 changes: 3 additions & 0 deletions backend/chainlit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@

DEFAULT_HOST = "0.0.0.0"
DEFAULT_PORT = 8000
DEFAULT_ROOT_PATH = ""


@dataclass()
Expand All @@ -173,6 +174,7 @@ class RunSettings:
port: int = DEFAULT_PORT
ssl_cert: Optional[str] = None
ssl_key: Optional[str] = None
root_path: str = DEFAULT_ROOT_PATH
headless: bool = False
watch: bool = False
no_cache: bool = False
Expand Down Expand Up @@ -277,6 +279,7 @@ class CodeSettings:
on_message: Optional[Callable[["Message"], Any]] = None
on_audio_chunk: Optional[Callable[["AudioChunk"], Any]] = None
on_audio_end: Optional[Callable[[List["ElementBased"]], Any]] = None
on_system_message: Optional[Callable[["Message"], Any]] = None

author_rename: Optional[Callable[[str], str]] = None
on_settings_update: Optional[Callable[[Dict[str, Any]], Any]] = None
Expand Down
9 changes: 9 additions & 0 deletions backend/chainlit/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ def init_http_context(
user_env: Optional[Dict[str, str]] = None,
client_type: ClientType = "webapp",
) -> ChainlitContext:
from chainlit.data import get_data_layer

session_id = str(uuid.uuid4())
thread_id = thread_id or str(uuid.uuid4())
session = HTTPSession(
Expand All @@ -81,6 +83,13 @@ def init_http_context(
)
context = ChainlitContext(session)
context_var.set(context)

if data_layer := get_data_layer():
if user_id := getattr(user, "id", None):
asyncio.create_task(
data_layer.update_thread(thread_id=thread_id, user_id=user_id)
)

return context


Expand Down
9 changes: 6 additions & 3 deletions backend/chainlit/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,9 +467,12 @@ async def get_thread(self, thread_id: str) -> "Optional[ThreadDict]":
steps = [] # List[StepDict]
if thread.steps:
for step in thread.steps:
if config.ui.hide_cot and (
step.parent_id or "message" not in step.type
):
if step.type == "system_message":
continue
if config.ui.hide_cot and step.type not in [
"user_message",
"assistant_message",
]:
continue
for attachment in step.attachments:
elements.append(self.attachment_to_element_dict(attachment))
Expand Down
4 changes: 2 additions & 2 deletions backend/chainlit/data/sql_alchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import ssl
import uuid
from dataclasses import asdict
from datetime import datetime, timezone
from datetime import datetime
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

import aiofiles
Expand Down Expand Up @@ -149,7 +149,7 @@ async def get_thread_author(self, thread_id: str) -> str:
query = """SELECT "userIdentifier" FROM threads WHERE "id" = :id"""
parameters = {"id": thread_id}
result = await self.execute_sql(query=query, parameters=parameters)
if isinstance(result, list) and result[0]:
if isinstance(result, list) and result:
author_identifier = result[0].get("userIdentifier")
if author_identifier is not None:
return author_identifier
Expand Down
42 changes: 33 additions & 9 deletions backend/chainlit/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
import uuid
from enum import Enum
from io import BytesIO
from typing import Any, ClassVar, List, Literal, Optional, TypedDict, TypeVar, Union
from typing import (
Any,
ClassVar,
Dict,
List,
Literal,
Optional,
TypedDict,
TypeVar,
Union,
)

import filetype
from chainlit.context import context
Expand All @@ -21,7 +31,7 @@
}

ElementType = Literal[
"image", "text", "pdf", "tasklist", "audio", "video", "file", "plotly"
"image", "text", "pdf", "tasklist", "audio", "video", "file", "plotly", "component"
]
ElementDisplay = Literal["inline", "side", "page"]
ElementSize = Literal["small", "medium", "large"]
Expand Down Expand Up @@ -162,13 +172,13 @@ async def send(self, for_id: str):
self.for_id = for_id

if not self.mime:
# Only guess the mime type when the content is binary
self.mime = (
mime_types[self.type]
if self.type in mime_types
else filetype.guess_mime(self.path or self.content)
)
if not self.mime and self.url:
if self.type in mime_types:
self.mime = mime_types[self.type]
elif self.path or isinstance(self.content, (bytes, bytearray)):
file_type = filetype.guess(self.path or self.content)
if file_type:
self.mime = file_type.mime
elif self.url:
self.mime = mimetypes.guess_type(self.url)[0]

await self._create()
Expand Down Expand Up @@ -349,3 +359,17 @@ def __post_init__(self) -> None:
self.mime = "application/json"

super().__post_init__()


@dataclass
class Component(Element):
"""Useful to send a custom component to the UI."""

type: ClassVar[ElementType] = "component"
mime: str = "application/json"
props: Dict = Field(default_factory=dict)

def __post_init__(self) -> None:
self.content = json.dumps(self.props)

super().__post_init__()
Loading

0 comments on commit 28eb9e2

Please sign in to comment.