Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paul assistant #1342

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
81d21d1
feat: v0 of chat assistants feature
ModEnter Aug 22, 2024
dcfe390
fix: brought back markdown description, enlarged the new assistant po…
ModEnter Aug 23, 2024
2b1d0ac
change: moved the assistant related ui elements to the sidebar, chang…
ModEnter Aug 23, 2024
b825360
fix: list_assistants returns a baseassistant
ModEnter Aug 23, 2024
4c8b119
fix: list_assistants returns a baseassistant
ModEnter Aug 23, 2024
1d749f6
feat: assistant avatars work, folder /publid/avatars
ModEnter Aug 26, 2024
c041027
feat: added id and created_by atttributes
ModEnter Aug 27, 2024
179fd65
feat: added warning banner on missing assistant field
ModEnter Aug 27, 2024
3876a26
feat: edit assistants
ModEnter Aug 27, 2024
eab3e47
feat: changed the assistant paradigm, from inheritance to extendability
ModEnter Aug 28, 2024
e13dde7
feat: changed the assistant paradigm, from inheritance to extendability
ModEnter Aug 28, 2024
07f2839
fix: fix forgot to update project.ts in previous commit
ModEnter Aug 28, 2024
2b2d453
fix: fixed icons
ModEnter Aug 28, 2024
430376c
fix: fixed session clear issues, added assistant selection in session
ModEnter Aug 28, 2024
ef1face
temporary commit, not working, to add created_by and id
ModEnter Aug 29, 2024
e10bec5
feat: created_by and id working, issue with pictures on edit assistant
ModEnter Aug 29, 2024
661aa4b
feat: added assitantinfoscreen (chatprofile welcome screen equivalent…
ModEnter Aug 30, 2024
c32d849
feat: working icons and assistants splash screens, bug cannot unset icon
ModEnter Aug 30, 2024
30baa5d
feat: select first assistant by default (if any)
ModEnter Aug 30, 2024
5384a6c
fix: fixed icons that could not be removed from assistant
ModEnter Aug 31, 2024
d075a22
fix: fixed fetchassistants call on no assistants error
ModEnter Sep 16, 2024
9cff5b1
fix: removed comments
ModEnter Sep 16, 2024
85e285d
merge conflict paul-ssistant main
ModEnter Sep 17, 2024
108863f
fix: fix init broken after merge
ModEnter Sep 17, 2024
243e87e
fix: fixed all cypress tests, added callback tests and e2e tests for …
ModEnter Sep 17, 2024
c5301f8
fix: fixed missing auth secret for assistant profiles e2e tests
ModEnter Sep 17, 2024
76140bb
fix: fixed backend tests for assistant profiles
ModEnter Sep 17, 2024
42c83ea
fix: added comment to mark assistant features as experimental
ModEnter Sep 17, 2024
c610991
feat: added @experimental decorator to mark feature as experimental
ModEnter Sep 17, 2024
50efe4a
bump version
willydouhard Sep 18, 2024
4c05051
chore: pre-release from current branch
willydouhard Sep 18, 2024
0424ae1
use this branch for the prerelease
willydouhard Sep 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
ref: main
ref: paul-assistant
- uses: ./.github/actions/pnpm-node-install
name: Install Node, pnpm and dependencies.
with:
Expand Down
10 changes: 10 additions & 0 deletions backend/chainlit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import chainlit.input_widget as input_widget
from chainlit.action import Action
from chainlit.assistant import Assistant
from chainlit.assistant_settings import AssistantSettings
from chainlit.cache import cache
from chainlit.chat_context import chat_context
from chainlit.chat_settings import ChatSettings
Expand Down Expand Up @@ -61,6 +63,8 @@
on_chat_end,
on_chat_resume,
on_chat_start,
on_create_assistant,
on_list_assistants,
on_logout,
on_message,
on_settings_update,
Expand Down Expand Up @@ -134,6 +138,10 @@ def acall(self):
"TaskStatus",
"Video",
"ChatSettings",
# Experimental
"AssistantSettings",
# Experimental
"Assistant",
"input_widget",
"Message",
"ErrorMessage",
Expand All @@ -153,6 +161,8 @@ def acall(self):
"action_callback",
"author_rename",
"on_settings_update",
"on_create_assistant",
"on_list_assistants",
"password_auth_callback",
"header_auth_callback",
"sleep",
Expand Down
16 changes: 16 additions & 0 deletions backend/chainlit/assistant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import List, Dict
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we have multiple assistant file it can be in a dedicated folder/module

from chainlit.input_widget import InputWidget

class Assistant:
input_widgets: List[InputWidget] = []
settings_values: Dict = {}

def __init__(self, input_widgets: List[InputWidget], settings_values: Dict):
self.input_widgets = input_widgets
self.settings_values = settings_values

def to_dict(self):
return {
"input_widgets": [widget.__repr__() for widget in self.input_widgets],
"settings_values": self.settings_values
}
35 changes: 35 additions & 0 deletions backend/chainlit/assistant_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logging
from typing import List

from chainlit.context import context
from chainlit.input_widget import InputWidget
from pydantic.dataclasses import Field, dataclass


@dataclass
class AssistantSettings:
"""Useful to create chat settings that the user can change."""

inputs: List[InputWidget] = Field(default_factory=list, exclude=True)

def __init__(
self,
inputs: List[InputWidget],
) -> None:
self.inputs = inputs

def settings(self):
return dict(
[(input_widget.id, input_widget.initial) for input_widget in self.inputs]
)

async def send(self):
settings = self.settings()
context.emitter.set_assistant_settings(settings)

inputs_content = [input_widget.to_dict() for input_widget in self.inputs]
# logging.info(f"Sending assistant settings: {inputs_content}")
await context.emitter.emit("assistant_settings", inputs_content)

# logging.info(f"Assistant settings sent: {settings}")
return settings
32 changes: 32 additions & 0 deletions backend/chainlit/callbacks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import functools
import inspect
from typing import Any, Awaitable, Callable, Dict, List, Optional

from chainlit.action import Action
from chainlit.assistant import Assistant
from chainlit.assistant_settings import AssistantSettings
from chainlit.config import config
from chainlit.message import Message
from chainlit.oauth_providers import get_configured_oauth_providers
Expand All @@ -14,6 +17,15 @@
from starlette.datastructures import Headers


def experimental(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"\033[1;33mexperimental feature: {func.__name__}\033[0m")
return func(*args, **kwargs)

return wrapper


@trace
def password_auth_callback(
func: Callable[[str, str], Awaitable[Optional[User]]]
Expand Down Expand Up @@ -306,3 +318,23 @@ def on_settings_update(

config.code.on_settings_update = wrap_user_function(func, with_task=True)
return func


# Experimental
@trace
@experimental
def on_create_assistant(
func: Callable[[Optional[User], AssistantSettings], Any]
) -> Callable[[Optional[User], AssistantSettings], Any]:
config.code.on_create_assistant = wrap_user_function(func)
return func


# Experimental
@trace
@experimental
def on_list_assistants(
func: Callable[[Optional[User]], List[Assistant]]
) -> Callable[[Optional[User]], List[Assistant]]:
config.code.on_list_assistants = wrap_user_function(func)
return func
18 changes: 11 additions & 7 deletions backend/chainlit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@

if TYPE_CHECKING:
from chainlit.action import Action
from chainlit.assistant import Assistant
from chainlit.element import ElementBased
from chainlit.message import Message
from chainlit.types import AudioChunk, ChatProfile, Starter, ThreadDict
from chainlit.user import User
from fastapi import Request, Response


BACKEND_ROOT = os.path.dirname(__file__)
PACKAGE_ROOT = os.path.dirname(os.path.dirname(BACKEND_ROOT))
TRANSLATIONS_DIR = os.path.join(BACKEND_ROOT, "translations")
Expand Down Expand Up @@ -285,9 +285,9 @@ class CodeSettings:
password_auth_callback: Optional[
Callable[[str, str], Awaitable[Optional["User"]]]
] = None
header_auth_callback: Optional[
Callable[[Headers], Awaitable[Optional["User"]]]
] = None
header_auth_callback: Optional[Callable[[Headers], Awaitable[Optional["User"]]]] = (
None
)
oauth_callback: Optional[
Callable[[str, str, Dict[str, str], "User"], Awaitable[Optional["User"]]]
] = None
Expand All @@ -305,9 +305,13 @@ class CodeSettings:
set_chat_profiles: Optional[
Callable[[Optional["User"]], Awaitable[List["ChatProfile"]]]
] = None
set_starters: Optional[
Callable[[Optional["User"]], Awaitable[List["Starter"]]]
] = None
set_starters: Optional[Callable[[Optional["User"]], Awaitable[List["Starter"]]]] = (
None
)

# assistant-related callback function
on_create_assistant: Optional[Callable[[Optional["User"], Any], Any]] = None
on_list_assistants: Optional[Callable[[Optional["User"]], List["Assistant"]]] = None


@dataclass()
Expand Down
3 changes: 2 additions & 1 deletion backend/chainlit/data/sql_alchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,8 @@ async def get_all_user_threads(
tags=step_feedback.get("step_tags"),
input=(
step_feedback.get("step_input", "")
if step_feedback.get("step_showinput") not in [None, "false"]
if step_feedback.get("step_showinput")
not in [None, "false"]
else None
),
output=step_feedback.get("step_output", ""),
Expand Down
7 changes: 7 additions & 0 deletions backend/chainlit/emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ async def set_chat_settings(self, settings: dict):
"""Stub method to set chat settings."""
pass

async def set_assistant_settings(self, settings: dict):
"""Stub method to send assistant settings to the UI."""
pass

async def send_action_response(
self, id: str, status: bool, response: Optional[str] = None
):
Expand Down Expand Up @@ -361,6 +365,9 @@ def send_token(self, id: str, token: str, is_sequence=False, is_input=False):
def set_chat_settings(self, settings: Dict[str, Any]):
self.session.chat_settings = settings

def set_assistant_settings(self, settings: Dict[str, Any]):
self.session.assistant_settings = settings

def send_action_response(
self, id: str, status: bool, response: Optional[str] = None
):
Expand Down
22 changes: 22 additions & 0 deletions backend/chainlit/input_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,28 @@ def to_dict(self) -> Dict[str, Any]:
"description": self.description,
}

@dataclass
class FileUploadInput(InputWidget):
"""Useful to create a file upload input."""

type: InputWidgetType = "fileupload"
initial: Optional[str] = None
placeholder: Optional[str] = None
accept: List[str] = Field(default_factory=lambda: [])
max_size_mb: Optional[int] = None
max_files: Optional[int] = None

def to_dict(self) -> Dict[str, Any]:
return {
"type": self.type,
"id": self.id,
"label": self.label,
"initial": self.initial,
"placeholder": self.placeholder,
"tooltip": self.tooltip,
"description": self.description,
}


@dataclass
class Tags(InputWidget):
Expand Down
24 changes: 23 additions & 1 deletion backend/chainlit/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ def get_build_dir(local_target: str, packaged_target: str) -> str:
name="copilot",
)


# -------------------------------------------------------------------------------
# SLACK HANDLER
# -------------------------------------------------------------------------------
Expand Down Expand Up @@ -985,6 +984,29 @@ async def get_avatar(avatar_id: str):
return await get_favicon()


# post avatar/{avatar_id} (only for authenticated users)
@router.post("/avatars/{avatar_id}")
async def upload_avatar(
avatar_id: str,
file: UploadFile,
current_user: Annotated[
Union[None, User, PersistedUser], Depends(get_current_user)
],
):
try:
avatar_path = os.path.join(APP_ROOT, "public", "avatars", avatar_id)

# Ensure the avatars directory exists
os.makedirs(os.path.dirname(avatar_path), exist_ok=True)

with open(avatar_path, "wb") as f:
f.write(await file.read())
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

return {"id": avatar_id}


@router.head("/")
def status_check():
"""Check if the site is operational."""
Expand Down
20 changes: 20 additions & 0 deletions backend/chainlit/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)

import aiofiles
from chainlit.assistant import Assistant
from chainlit.logger import logger

if TYPE_CHECKING:
Expand Down Expand Up @@ -72,8 +73,12 @@ def __init__(
user_env: Optional[Dict[str, str]],
# Chat profile selected before the session was created
chat_profile: Optional[str] = None,
# Selected assistant
selected_assistant: Optional[Assistant] = None,
# Origin of the request
http_referer: Optional[str] = None,
# assistant settings
assistant_settings: Optional[Dict[str, Any]] = None,
):
if thread_id:
self.thread_id_to_resume = thread_id
Expand All @@ -90,7 +95,9 @@ def __init__(

self.id = id

self.assistant_settings = assistant_settings
self.chat_settings: Dict[str, Any] = {}
self.selected_assistant = selected_assistant

@property
def files_dir(self):
Expand Down Expand Up @@ -153,6 +160,7 @@ def to_persistable(self) -> Dict:
user_session = user_sessions.get(self.id) or {} # type: Dict
user_session["chat_settings"] = self.chat_settings
user_session["chat_profile"] = self.chat_profile
user_session["selected_assistant"] = self.selected_assistant
user_session["http_referer"] = self.http_referer
user_session["client_type"] = self.client_type
metadata = clean_metadata(user_session)
Expand All @@ -176,6 +184,10 @@ def __init__(
user_env: Optional[Dict[str, str]] = None,
# Origin of the request
http_referer: Optional[str] = None,
# assistant settings
assistant_settings: Optional[Dict[str, Any]] = None,
# selected assistant
selected_assistant: Optional[Assistant] = None,
):
super().__init__(
id=id,
Expand All @@ -185,6 +197,8 @@ def __init__(
client_type=client_type,
user_env=user_env,
http_referer=http_referer,
assistant_settings=assistant_settings,
selected_assistant=selected_assistant,
)

def delete(self):
Expand Down Expand Up @@ -231,10 +245,14 @@ def __init__(
token: Optional[str] = None,
# Chat profile selected before the session was created
chat_profile: Optional[str] = None,
# Selected assistant
selected_assistant: Optional[Assistant] = None,
# Languages of the user's browser
languages: Optional[str] = None,
# Origin of the request
http_referer: Optional[str] = None,
# chat settings
assistant_settings: Optional[Dict[str, Any]] = None,
):
super().__init__(
id=id,
Expand All @@ -244,7 +262,9 @@ def __init__(
user_env=user_env,
client_type=client_type,
chat_profile=chat_profile,
selected_assistant=selected_assistant,
http_referer=http_referer,
assistant_settings=assistant_settings,
)

self.socket_id = socket_id
Expand Down
Loading
Loading