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

Update thread list on first interaction #923

Merged
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

Nothing unreleased!

## [1.0.505] - 2024-04-23

### Added

- The user's browser language configuration is available in `cl.user_session.get("languages")`

### Changed

- The thread auto-tagging feature is now opt-in using `auto_tag_thread` in the config.toml file

## [1.0.504] - 2024-04-16

### Changed
Expand Down
16 changes: 15 additions & 1 deletion backend/chainlit/emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,27 @@ async def flush_thread_queues(self, interaction: str):
tags=tags,
)
)
asyncio.create_task(
tpatel marked this conversation as resolved.
Show resolved Hide resolved
data_layer.update_thread(
thread_id=self.session.thread_id,
name=interaction,
user_id=user_id,
tags=tags,
)
)
except Exception as e:
logger.error(f"Error updating thread: {e}")
asyncio.create_task(self.session.flush_method_queue())

async def init_thread(self, interaction: str):
await self.flush_thread_queues(interaction)
await self.emit("first_interaction", interaction)
await self.emit(
"first_interaction",
{
"interaction": interaction,
"thread_id": self.session.thread_id,
},
)

async def process_user_message(self, payload: UIMessagePayload):
step_dict = payload["message"]
Expand Down
5 changes: 4 additions & 1 deletion backend/chainlit/socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,10 @@ async def connection_successful(sid):
thread = await resume_thread(context.session)
if thread:
context.session.has_first_interaction = True
await context.emitter.emit("first_interaction", "resume")
await context.emitter.emit(
"first_interaction",
{"interaction": "resume", "thread_id": thread.get("id")},
)
await context.emitter.resume_thread(thread)
await config.code.on_chat_resume(thread)
return
Expand Down
14 changes: 14 additions & 0 deletions cypress/e2e/data_layer/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ function login() {
}

function feedback() {
cy.location('pathname').should((loc) => {
expect(loc).to.eq('/');
});
submitMessage('Hello');
cy.location('pathname').should((loc) => {
// starts with /thread/
expect(loc).to.match(/^\/thread\//);
});
cy.get('.negative-feedback-off').should('have.length', 1);
cy.get('.positive-feedback-off').should('have.length', 1).click();
cy.get('#feedbackSubmit').click();
Expand Down Expand Up @@ -40,7 +47,14 @@ function resumeThread() {
cy.get('#thread-test2').click();
cy.get(`#chat-input`).should('not.exist');
cy.get('#resumeThread').click();
let initialUrl;
cy.url().then((url) => {
initialUrl = url;
});
cy.get(`#chat-input`).should('exist');
cy.url().then((newUrl) => {
expect(newUrl).to.equal(initialUrl);
});

cy.get('.step').should('have.length', 4);

Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/molecules/chatProfiles.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import size from 'lodash/size';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { grey } from 'theme';

Expand Down Expand Up @@ -30,10 +31,12 @@ export default function ChatProfiles() {
const [newChatProfile, setNewChatProfile] = useState<string | null>(null);
const [openDialog, setOpenDialog] = useState(false);
const isDarkMode = useIsDarkMode();
const navigate = useNavigate();

const handleClose = () => {
setOpenDialog(false);
setNewChatProfile(null);
navigate('/');
};

const handleConfirm = (newChatProfileWithoutConfirm?: string) => {
Expand Down
22 changes: 18 additions & 4 deletions frontend/src/components/organisms/chat/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useUpload } from 'hooks';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { toast } from 'sonner';
import { v4 as uuidv4 } from 'uuid';
Expand All @@ -10,6 +11,7 @@ import {
threadHistoryState,
useChatData,
useChatInteract,
useChatMessages,
useChatSession
} from '@chainlit/react-client';
import { sideViewState } from '@chainlit/react-client';
Expand Down Expand Up @@ -42,6 +44,7 @@ const Chat = () => {
const { error, disabled } = useChatData();
const { uploadFile } = useChatInteract();
const uploadFileRef = useRef(uploadFile);
const navigate = useNavigate();

const fileSpec = useMemo(
() => ({
Expand Down Expand Up @@ -151,11 +154,22 @@ const Chat = () => {
options: { noClick: true }
});

const { threadId } = useChatMessages();

useEffect(() => {
setThreads((prev) => ({
...prev,
currentThreadId: undefined
}));
const currentPage = new URL(window.location.href);
if (
projectSettings?.dataPersistence &&
threadId &&
currentPage.pathname === '/'
) {
navigate(`/thread/${threadId}`);
} else {
setThreads((prev) => ({
...prev,
currentThreadId: threadId
}));
}
}, []);

const enableMultiModalUpload =
Expand Down
11 changes: 9 additions & 2 deletions frontend/src/components/organisms/threadHistory/Thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
IStep,
IThread,
accessTokenState,
nestMessages
nestMessages,
useChatMessages
} from '@chainlit/react-client';

import SideView from 'components/atoms/element/sideView';
Expand All @@ -33,6 +34,7 @@ const Thread = ({ thread, error, isLoading }: Props) => {
const [steps, setSteps] = useState<IStep[]>([]);
const apiClient = useRecoilValue(apiClientState);
const { t } = useTranslation();
const { threadId } = useChatMessages();

useEffect(() => {
if (!thread) return;
Expand Down Expand Up @@ -164,7 +166,12 @@ const Thread = ({ thread, error, isLoading }: Props) => {
id="thread-info"
severity="info"
action={
<Button component={Link} color="inherit" size="small" to="/">
<Button
component={Link}
color="inherit"
size="small"
to={threadId ? `/thread/${threadId}` : '/'}
>
<Translator path="components.organisms.threadHistory.Thread.backToChat" />
</Button>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Typography from '@mui/material/Typography';
import {
ThreadHistory,
useChatInteract,
useChatMessages,
useChatSession
} from '@chainlit/react-client';

Expand All @@ -41,6 +42,7 @@ const ThreadList = ({
}: Props) => {
const { idToResume } = useChatSession();
const { clear } = useChatInteract();
const { threadId: currentThreadId } = useChatMessages();
const navigate = useNavigate();
if (isFetching || (!threadHistory?.timeGroupedThreads && isLoadingMore)) {
return (
Expand Down Expand Up @@ -89,7 +91,7 @@ const ThreadList = ({
}

const handleDeleteThread = (threadId: string) => {
if (threadId === idToResume) {
if (threadId === idToResume || threadId === currentThreadId) {
clear();
}
if (threadId === threadHistory.currentThreadId) {
Expand Down
36 changes: 33 additions & 3 deletions frontend/src/components/organisms/threadHistory/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useAuth } from 'api/auth';
import isEqual from 'lodash/isEqual';
import uniqBy from 'lodash/uniqBy';
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useRecoilState, useRecoilValue } from 'recoil';

import Box from '@mui/material/Box';
Expand All @@ -13,7 +14,8 @@ import useMediaQuery from '@mui/material/useMediaQuery';
import {
IThreadFilters,
accessTokenState,
threadHistoryState
threadHistoryState,
useChatMessages
} from '@chainlit/react-client';

import { Translator } from 'components/i18n';
Expand Down Expand Up @@ -46,6 +48,8 @@ const _ThreadHistorySideBar = () => {
const [isLoadingMore, setIsLoadingMore] = useState(false);
const [isFetching, setIsFetching] = useState(false);
const apiClient = useRecoilValue(apiClientState);
const { firstInteraction, messages, threadId } = useChatMessages();
const navigate = useNavigate();

const ref = useRef<HTMLDivElement>(null);
const filtersHasChanged = !isEqual(prevFilters, filters);
Expand All @@ -62,9 +66,12 @@ const _ThreadHistorySideBar = () => {
setShouldLoadMore(atBottom);
};

const fetchThreads = async (cursor?: string | number) => {
const fetchThreads = async (
cursor?: string | number,
isLoadingMore?: boolean
) => {
try {
if (cursor) {
if (cursor || isLoadingMore) {
setIsLoadingMore(true);
} else {
setIsFetching(true);
Expand Down Expand Up @@ -129,6 +136,29 @@ const _ThreadHistorySideBar = () => {
}
}, []);

useEffect(() => {
if (!firstInteraction) {
return;
}

// distinguish between the first interaction containing the word "resume"
// and the actual resume message
const isActualResume =
firstInteraction === 'resume' &&
messages.at(0)?.output.toLowerCase() !== 'resume';

if (isActualResume) {
return;
}

fetchThreads(undefined, true).then(() => {
const currentPage = new URL(window.location.href);
if (threadId && currentPage.pathname === '/') {
navigate(`/thread/${threadId}`);
}
});
}, [firstInteraction]);

return (
<Box display="flex" position="relative">
<Drawer
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/pages/ResumeButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ export default function ResumeButton({ threadId }: Props) {
clear();
setIdToResume(threadId!);
toast.success('Chat resumed!');
navigate('/');
if (!pSettings?.dataPersistence) {
navigate('/');
}
};

return (
Expand Down
43 changes: 29 additions & 14 deletions frontend/src/pages/Thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import { useRecoilState, useRecoilValue } from 'recoil';

import { Box } from '@mui/material';

import { IThread, threadHistoryState, useApi } from '@chainlit/react-client';
import {
IThread,
threadHistoryState,
useApi,
useChatMessages
} from '@chainlit/react-client';

import Chat from 'components/organisms/chat';
import { Thread } from 'components/organisms/threadHistory/Thread';

import { apiClientState } from 'state/apiClient';
Expand All @@ -28,6 +34,10 @@ export default function ThreadPage() {

const [threadHistory, setThreadHistory] = useRecoilState(threadHistoryState);

const { threadId } = useChatMessages();

const isCurrentThread = threadId === id;

useEffect(() => {
if (threadHistory?.currentThreadId !== id) {
setThreadHistory((prev) => {
Expand All @@ -38,19 +48,24 @@ export default function ThreadPage() {

return (
<Page>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
gap: 2
}}
>
<Box sx={{ width: '100%', flexGrow: 1, overflow: 'auto' }}>
<Thread thread={data} error={error} isLoading={isLoading} />
</Box>
<ResumeButton threadId={id} />
</Box>
<>
{isCurrentThread && <Chat />}
{!isCurrentThread && (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
gap: 2
}}
>
<Box sx={{ width: '100%', flexGrow: 1, overflow: 'auto' }}>
<Thread thread={data} error={error} isLoading={isLoading} />
</Box>
<ResumeButton threadId={id} />
</Box>
)}
</>
</Page>
);
}
5 changes: 5 additions & 0 deletions libs/react-client/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,8 @@ export const sideViewState = atom<IMessageElement | undefined>({
key: 'SideView',
default: undefined
});

export const currentThreadIdState = atom<string | undefined>({
key: 'CurrentThreadId',
default: undefined
});
3 changes: 3 additions & 0 deletions libs/react-client/src/useChatInteract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
avatarState,
chatSettingsInputsState,
chatSettingsValueState,
currentThreadIdState,
elementState,
firstUserInteraction,
loadingState,
Expand Down Expand Up @@ -43,6 +44,7 @@ const useChatInteract = () => {
const setTokenCount = useSetRecoilState(tokenCountState);
const setIdToResume = useSetRecoilState(threadIdToResumeState);
const setSideView = useSetRecoilState(sideViewState);
const setCurrentThreadId = useSetRecoilState(currentThreadIdState);

const clear = useCallback(() => {
session?.socket.emit('clear_session');
Expand All @@ -59,6 +61,7 @@ const useChatInteract = () => {
resetChatSettings();
resetChatSettingsValue();
setSideView(undefined);
setCurrentThreadId(undefined);
}, [session]);

const sendMessage = useCallback(
Expand Down
Loading
Loading