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

various bug fixes #600

Merged
merged 10 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading