Skip to content

Commit

Permalink
various bug fixes (#600)
Browse files Browse the repository at this point in the history
* various bug fixes

* file upload should flush thread queues

* relax python dependency

* fix deps

* don't send steps if hide_cot is true

* make oauth cookie samesite policy configurable

* add copy button and replace message buttons

* 1.0.0rc2

* changelog

* bump sdk version
  • Loading branch information
willydouhard committed Dec 18, 2023
1 parent b373e73 commit 4d9f629
Show file tree
Hide file tree
Showing 24 changed files with 148 additions and 134 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

Nothing is unreleased!

## [1.0.0rc2] - 2023-12-18

### Added

- Copy button under messages
- OAuth samesite cookie policy is now configurable through the `CHAINLIT_COOKIE_SAMESITE` env var

### Changed

- Relax Python version requirements
- If `hide_cot` is configured to `true`, steps will never be sent to the UI, but still persisted.
- Message buttons are now positioned below

## [1.0.0rc0] - 2023-12-12

### Added
Expand Down
3 changes: 3 additions & 0 deletions backend/chainlit/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections import deque
from typing import TYPE_CHECKING, Dict, List, Optional

from chainlit.config import config
from chainlit.context import context
from chainlit.logger import logger
from chainlit.session import WebsocketSession
Expand Down Expand Up @@ -332,6 +333,8 @@ 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:
continue
for attachment in step.attachments:
elements.append(self.attachment_to_element_dict(attachment))
steps.append(self.step_to_step_dict(step))
Expand Down
14 changes: 11 additions & 3 deletions backend/chainlit/emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,7 @@ def clear_ask(self):

return self.emit("clear_ask", {})

async def init_thread(self, step: StepDict):
"""Signal the UI that a new thread (with a user message) exists"""
async def flush_thread_queues(self, name: str):
if data_layer := get_data_layer():
if isinstance(self.session.user, PersistedUser):
user_id = self.session.user.id
Expand All @@ -177,10 +176,13 @@ async def init_thread(self, step: StepDict):
await data_layer.update_thread(
thread_id=self.session.thread_id,
user_id=user_id,
metadata={"name": step["output"]},
metadata={"name": name},
)
await self.session.flush_method_queue()

async def init_thread(self, step: StepDict):
"""Signal the UI that a new thread (with a user message) exists"""
await self.flush_thread_queues(name=step["output"])
await self.emit("init_thread", step)

async def process_user_message(self, payload: UIMessagePayload):
Expand Down Expand Up @@ -251,6 +253,12 @@ async def send_ask_user(
if file["id"] in self.session.files
]
final_res = files
if not self.session.has_user_message:
self.session.has_user_message = True
await self.flush_thread_queues(
name=",".join([file["name"] for file in files])
)

if get_data_layer():
coros = [
File(
Expand Down
4 changes: 2 additions & 2 deletions backend/chainlit/message.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import json
import time
import uuid
from abc import ABC
from datetime import datetime
Expand Down Expand Up @@ -146,7 +147,6 @@ async def _create(self):
async def send(self):
if not self.created_at:
self.created_at = datetime.utcnow().isoformat()

if self.content is None:
self.content = ""

Expand All @@ -157,7 +157,6 @@ async def send(self):
self.streaming = False

step_dict = await self._create()

await context.emitter.send_step(step_dict)

return self.id
Expand Down Expand Up @@ -209,6 +208,7 @@ def __init__(
id: Optional[str] = None,
created_at: Union[str, None] = None,
):
time.sleep(0.001)
self.language = language

if isinstance(content, dict):
Expand Down
2 changes: 1 addition & 1 deletion backend/chainlit/playground/providers/langchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def format_message(self, message, prompt):
return self.prompt_message_to_langchain_message(message)

def message_to_string(self, message: BaseMessage) -> str: # type: ignore[override]
return message.content
return str(message.content)

async def create_completion(self, request):
from langchain.schema.messages import BaseMessageChunk
Expand Down
7 changes: 5 additions & 2 deletions backend/chainlit/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import mimetypes
import shutil
import urllib.parse
from typing import Optional, Union
from typing import Any, Optional, Union

from chainlit.oauth_providers import get_oauth_provider
from chainlit.secret import random_secret
Expand Down Expand Up @@ -317,7 +317,10 @@ async def oauth_login(provider_id: str, request: Request):
response = RedirectResponse(
url=f"{provider.authorize_url}?{params}",
)
response.set_cookie("oauth_state", random, httponly=True, max_age=3 * 60)
samesite = os.environ.get("CHAINLIT_COOKIE_SAMESITE", "lax") # type: Any
response.set_cookie(
"oauth_state", random, httponly=True, samesite=samesite, max_age=3 * 60
)
return response


Expand Down
19 changes: 17 additions & 2 deletions backend/chainlit/step.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import inspect
import json
import time
import uuid
from datetime import datetime
from functools import wraps
Expand Down Expand Up @@ -158,6 +159,7 @@ def __init__(
show_input: Union[bool, str] = False,
):
trace_event(f"init {self.__class__.__name__} {type}")
time.sleep(0.001)
self._input = ""
self._output = ""
self.thread_id = context.session.thread_id
Expand Down Expand Up @@ -264,6 +266,9 @@ async def update(self):
tasks = [el.send(for_id=self.id) for el in self.elements]
await asyncio.gather(*tasks)

if config.ui.hide_cot and self.parent_id:
return

if not config.features.prompt_playground and "generation" in step_dict:
step_dict.pop("generation", None)

Expand Down Expand Up @@ -318,6 +323,9 @@ async def send(self):
tasks = [el.send(for_id=self.id) for el in self.elements]
await asyncio.gather(*tasks)

if config.ui.hide_cot and self.parent_id:
return self.id

if not config.features.prompt_playground and "generation" in step_dict:
step_dict.pop("generation", None)

Expand All @@ -342,6 +350,10 @@ async def stream_token(self, token: str, is_sequence=False):
self.output += token

assert self.id

if config.ui.hide_cot and self.parent_id:
return

await context.emitter.send_token(
id=self.id, token=token, is_sequence=is_sequence
)
Expand Down Expand Up @@ -372,7 +384,9 @@ async def __aenter__(self):

async def __aexit__(self, exc_type, exc_val, exc_tb):
self.end = datetime.utcnow().isoformat()
context.session.active_steps.pop()

if self in context.session.active_steps:
context.session.active_steps.remove(self)
await self.update()

def __enter__(self):
Expand All @@ -389,5 +403,6 @@ def __enter__(self):

def __exit__(self, exc_type, exc_val, exc_tb):
self.end = datetime.utcnow().isoformat()
context.session.active_steps.pop()
if self in context.session.active_steps:
context.session.active_steps.remove(self)
asyncio.create_task(self.update())
10 changes: 5 additions & 5 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "chainlit"
version = "1.0.0rc1"
version = "1.0.0rc2"
keywords = ['LLM', 'Agents', 'gen ai', 'chat ui', 'chatbot ui', 'langchain']
description = "A faster way to build chatbot UIs."
authors = ["Chainlit"]
Expand All @@ -19,8 +19,8 @@ include = [
chainlit = 'chainlit.cli:cli'

[tool.poetry.dependencies]
python = ">=3.8.1,<3.12"
chainlit_client = "0.1.0rc5"
python = ">=3.8.1,<4.0.0"
chainlit_client = "0.1.0rc7"
dataclasses_json = "^0.5.7"
uvicorn = "^0.23.2"
fastapi = "^0.100"
Expand Down Expand Up @@ -48,8 +48,8 @@ optional = true

[tool.poetry.group.tests.dependencies]
openai = ">=1.1.0"
langchain = "^0.0.331"
llama-index = "^0.8.64"
langchain = "^0.0.350"
llama-index = "^0.9.15"
transformers = "^4.30.1"
matplotlib = "3.7.1"
farm-haystack = "^1.18.0"
Expand Down
12 changes: 6 additions & 6 deletions cypress/e2e/avatar/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@ async def start():
url="https://avatars.githubusercontent.com/u/128686189?s=400&u=a1d1553023f8ea0921fba0debbe92a8c5f840dd9&v=4",
).send()

await cl.Avatar(name="Cat", path="./cat.jpeg").send()

await cl.Avatar(
name="Tool 1",
url="https://avatars.githubusercontent.com/u/128686189?s=400&u=a1d1553023f8ea0921fba0debbe92a8c5f840dd9&v=4",
).send()
await cl.Avatar(name="Cat", path="./public/cat.jpeg").send()
await cl.Avatar(name="Cat 2", url="/public/cat.jpeg").send()

await cl.Message(
content="This message should not have an avatar!", author="Tool 0"
Expand All @@ -30,3 +26,7 @@ async def start():
await cl.Message(
content="This message should have a cat avatar!", author="Cat"
).send()

await cl.Message(
content="This message should have a cat avatar!", author="Cat 2"
).send()
File renamed without changes
3 changes: 2 additions & 1 deletion cypress/e2e/avatar/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ describe('Avatar', () => {
});

it('should be able to display avatars', () => {
cy.get('.step').should('have.length', 4);
cy.get('.step').should('have.length', 5);

cy.get('.step').eq(0).find('.message-avatar').should('have.length', 0);
cy.get('.step').eq(1).find('.message-avatar').should('have.length', 1);
cy.get('.step').eq(2).find('.message-avatar').should('have.length', 0);
cy.get('.step').eq(3).find('.message-avatar').should('have.length', 1);
cy.get('.step').eq(4).find('.message-avatar').should('have.length', 1);

cy.get('.element-link').should('have.length', 0);
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
max-width: 100%;
}

.markdown-body *:first-child {
.markdown-body *:first-child:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) {
margin-top: 0;
}

Expand Down
60 changes: 20 additions & 40 deletions libs/react-components/src/ClipboardCopy.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,40 @@
import { grey } from 'theme/palette';
import { useCopyToClipboard, useToggle } from 'usehooks-ts';
import { useState } from 'react';
import { useCopyToClipboard } from 'usehooks-ts';

import CopyAll from '@mui/icons-material/CopyAll';
import { IconProps } from '@mui/material/Icon';
import IconButton from '@mui/material/IconButton';
import ContentPaste from '@mui/icons-material/ContentPaste';
import IconButton, { IconButtonProps } from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';

import { useIsDarkMode } from 'hooks/useIsDarkMode';

interface ClipboardCopyProps {
value: string;
theme?: 'dark' | 'light';
size?: IconProps['fontSize'];
edge?: IconButtonProps['edge'];
}

const ClipboardCopy = ({
value,
size,
theme
}: ClipboardCopyProps): JSX.Element => {
const [showTooltip, toggleTooltip] = useToggle();
const isDarkMode = useIsDarkMode();
const ClipboardCopy = ({ value, edge }: ClipboardCopyProps): JSX.Element => {
const [isCopied, setIsCopied] = useState(false);
const [_, copy] = useCopyToClipboard();

const getColor = () => {
if (theme) {
if (theme === 'dark') return grey[200];
else if (theme === 'light') return grey[800];
}
const handleCopy = () => {
copy(value)
.then(() => {
setIsCopied(true);
})
.catch((err) => console.log('An error occurred while copying: ', err));
};

return isDarkMode ? grey[200] : grey[800];
const handleTooltipClose = () => {
setIsCopied(false);
};

return (
<Tooltip
open={showTooltip}
title={'Copied to clipboard!'}
onClose={toggleTooltip}
title={isCopied ? 'Copied to clipboard!' : 'Copy'}
onClose={handleTooltipClose}
sx={{ zIndex: 2 }}
>
<IconButton
sx={{
color: getColor(),
position: 'absolute',
right: 0,
top: 0
}}
onClick={() => {
copy(value)
.then(() => toggleTooltip())
.catch((err) =>
console.log('An error occurred while copying: ', err)
);
}}
>
<CopyAll fontSize={size} />
<IconButton color="inherit" edge={edge} onClick={handleCopy}>
<ContentPaste sx={{ height: 16, width: 16 }} />
</IconButton>
</Tooltip>
);
Expand Down
6 changes: 4 additions & 2 deletions libs/react-components/src/Code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,19 @@ const Code = ({ children, ...props }: any) => {
>
<Stack
px={2}
py={1}
py={0.5}
direction="row"
sx={{
justifyContent: 'space-between',
alignItems: 'center',
borderTopLeftRadius: '4px',
borderTopRightRadius: '4px',
background: isDarkMode ? grey[900] : grey[200],
borderBottom: `1px solid ${grey[950]}`
}}
>
<Typography variant="caption">{match?.[1] || 'Raw code'}</Typography>
<ClipboardCopy value={code} size="small" />
<ClipboardCopy edge="end" value={code} />
</Stack>

{highlightedCode}
Expand Down

0 comments on commit 4d9f629

Please sign in to comment.