Skip to content

Commit

Permalink
fix: use accordion for steps and fix streaming auto scroll (#1874)
Browse files Browse the repository at this point in the history
* fix: use accordion for steps and fix streaming auto scroll

* fix: test

* fix: test

* fix: only show output title in step if output is defined

* fix: should not be able to edit a message in read only thread

* fix: scroll down button
  • Loading branch information
willydouhard authored Feb 8, 2025
1 parent 2c7ae66 commit e08f7cb
Show file tree
Hide file tree
Showing 18 changed files with 167 additions and 119 deletions.
1 change: 1 addition & 0 deletions cypress/e2e/remove_elements/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe('remove_elements', () => {
cy.get('#step-tool1').should('exist');
cy.get('#step-tool1').click();
cy.get('#step-tool1')
.parent()
.parent()
.find('.inline-image')
.should('have.length', 1);
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/streaming/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ function toolStream(tool: string) {
const toolCall = cy.get(`#step-${tool}`);
toolCall.click();
for (const token of tokenList) {
toolCall.parent().should('contain', token);
toolCall.parent().parent().should('contain', token);
}
toolCall.parent().should('contain', tokenList.join(' '));
toolCall.parent().parent().should('contain', tokenList.join(' '));
}

describe('Streaming', () => {
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/update_step/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ describe('Update Step', () => {
cy.get(`#step-tool1`).click();
cy.get('.step').should('have.length', 2);
cy.get('.step').eq(0).should('contain', 'Hello!');
cy.get(`#step-tool1`).parent().should('contain', 'Foo');
cy.get(`#step-tool1`).parent().parent().should('contain', 'Foo');

cy.get('.step').eq(0).should('contain', 'Hello again!');
cy.get(`#step-tool1`).parent().should('contain', 'Foo Bar');
cy.get(`#step-tool1`).parent().parent().should('contain', 'Foo Bar');
});
});
5 changes: 4 additions & 1 deletion frontend/src/components/CodeSnippet.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cn } from '@/lib/utils';
import hljs from 'highlight.js';
import { useEffect, useRef } from 'react';

Expand Down Expand Up @@ -64,7 +65,9 @@ export default function CodeSnippet({ ...props }: CodeProps) {
) : null;

const nonHighlightedCode = showSyntaxHighlighter ? null : (
<div className="p-2 rounded-b-md min-h-20 overflow-x-auto bg-accent">
<div
className={cn('rounded-b-md overflow-x-auto bg-accent', code && 'p-2')}
>
<code className="whitespace-pre-wrap">{code}</code>
</div>
);
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/components/MarkdownAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,6 @@ const AlertComponent = ({
const style = variantStyles[variant];
const Icon = style.Icon;

// console.log('AlertComponent rendering:', variant, children);

return (
<div className={cn('rounded-lg p-4 mb-4', style.container)}>
<div className="flex">
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/ReadOnlyThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ const ReadOnlyThread = ({ id }: Props) => {
return {
allowHtml: config?.features?.unsafe_allow_html,
latex: config?.features?.latex,
editable: false,
loading: false,
showFeedbackButtons: !!config?.dataPersistence,
uiName: config?.ui?.name || '',
Expand Down
14 changes: 3 additions & 11 deletions frontend/src/components/chat/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,26 @@
import { cn, hasMessage } from '@/lib/utils';
import { MutableRefObject } from 'react';

import { FileSpec, useChatMessages } from '@chainlit/react-client';

import WaterMark from '@/components/WaterMark';

import MessageComposer from './MessageComposer';
import ScrollDownButton from './ScrollDownButton';

interface Props {
fileSpec: FileSpec;
onFileUpload: (payload: File[]) => void;
onFileUploadError: (error: string) => void;
setAutoScroll: (autoScroll: boolean) => void;
autoScroll: boolean;
autoScrollRef: MutableRefObject<boolean>;
showIfEmptyThread?: boolean;
}

export default function ChatFooter({
autoScroll,
showIfEmptyThread,
...props
}: Props) {
export default function ChatFooter({ showIfEmptyThread, ...props }: Props) {
const { messages } = useChatMessages();
if (!hasMessage(messages) && !showIfEmptyThread) return null;

return (
<div className={cn('relative flex flex-col items-center gap-2 w-full')}>
{!autoScroll ? (
<ScrollDownButton onClick={() => props.setAutoScroll(true)} />
) : null}
<MessageComposer {...props} />
<WaterMark />
</div>
Expand Down
14 changes: 9 additions & 5 deletions frontend/src/components/chat/MessageComposer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useRef, useState } from 'react';
import { MutableRefObject, useCallback, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
Expand Down Expand Up @@ -29,14 +29,14 @@ interface Props {
fileSpec: FileSpec;
onFileUpload: (payload: File[]) => void;
onFileUploadError: (error: string) => void;
setAutoScroll: (autoScroll: boolean) => void;
autoScrollRef: MutableRefObject<boolean>;
}

export default function MessageComposer({
fileSpec,
onFileUpload,
onFileUploadError,
setAutoScroll
autoScrollRef
}: Props) {
const inputRef = useRef<InputMethods>(null);
const [value, setValue] = useState('');
Expand Down Expand Up @@ -88,7 +88,9 @@ export default function MessageComposer({
?.filter((a) => !!a.serverId)
.map((a) => ({ id: a.serverId! }));

setAutoScroll(true);
if (autoScrollRef) {
autoScrollRef.current = true;
}
sendMessage(message, fileReferences);
},
[user, sendMessage]
Expand All @@ -107,7 +109,9 @@ export default function MessageComposer({
};

replyMessage(message);
setAutoScroll(true);
if (autoScrollRef) {
autoScrollRef.current = true;
}
},
[user, replyMessage]
);
Expand Down
19 changes: 8 additions & 11 deletions frontend/src/components/chat/Messages/Message/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,9 @@ const MessageContent = memo(
const isMessage = message.type.includes('message');

const outputMarkdown = (
<div className="flex flex-col gap-2">
{!isMessage && displayInput ? (
<div className="text-lg font-semibold leading-none tracking-tight">
Output
</div>
<>
{!isMessage && displayInput && message.output ? (
<div className="font-medium">Output</div>
) : null}
<Markdown
allowHtml={allowHtml}
Expand All @@ -51,7 +49,7 @@ const MessageContent = memo(
>
{output}
</Markdown>
</div>
</>
);

let inputMarkdown;
Expand All @@ -73,18 +71,17 @@ const MessageContent = memo(
});

inputMarkdown = (
<div className="flex flex-col gap-2">
<div className="text-lg font-semibold leading-none tracking-tight">
Input
</div>
<>
<div className="font-medium">Input</div>

<Markdown
allowHtml={allowHtml}
latex={latex}
refElements={inputRefElements}
>
{input}
</Markdown>
</div>
</>
);
}

Expand Down
100 changes: 62 additions & 38 deletions frontend/src/components/chat/Messages/Message/Step.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { cn } from '@/lib/utils';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { PropsWithChildren, useMemo, useState } from 'react';
import { PropsWithChildren, useMemo } from 'react';

import type { IStep } from '@chainlit/react-client';

import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger
} from '@/components/ui/accordion';
import { Translator } from 'components/i18n';

interface Props {
Expand All @@ -16,52 +21,71 @@ export default function Step({
children,
isRunning
}: PropsWithChildren<Props>) {
const [open, setOpen] = useState(false);
const using = useMemo(() => {
return isRunning && step.start && !step.end && !step.isError;
}, [step, isRunning]);

const hasContent = step.input || step.output || step.steps?.length;
const isError = step.isError;

const stepName = step.name;

return (
<div className="flex flex-col flex-grow w-0">
<p
className={cn(
'flex items-center gap-1 group/step',
isError && 'text-red-500',
hasContent && 'cursor-pointer',
!using && 'text-muted-foreground hover:text-foreground',
using && 'loading-shimmer'
)}
onClick={() => setOpen(!open)}
id={`step-${stepName}`}
>
{using ? (
<>
<Translator path="chat.messages.status.using" /> {stepName}
</>
) : (
<>
<Translator path="chat.messages.status.used" /> {stepName}
</>
)}
{hasContent ? (
open ? (
<ChevronUp className="invisible group-hover/step:visible !size-4" />
// If there's no content, just render the status without accordion
if (!hasContent) {
return (
<div className="flex flex-col flex-grow w-0">
<p
className={cn(
'flex items-center gap-1 font-medium',
isError && 'text-red-500',
!using && 'text-muted-foreground',
using && 'loading-shimmer'
)}
id={`step-${stepName}`}
>
{using ? (
<>
<Translator path="chat.messages.status.using" /> {stepName}
</>
) : (
<ChevronDown className="invisible group-hover/step:visible !size-4" />
)
) : null}
</p>
<>
<Translator path="chat.messages.status.used" /> {stepName}
</>
)}
</p>
</div>
);
}

{open && (
<div className="flex-grow mt-4 ml-2 pl-4 border-l-2 border-primary">
{children}
</div>
)}
return (
<div className="flex flex-col flex-grow w-0">
<Accordion type="single" collapsible className="w-full">
<AccordionItem value={step.id} className="border-none">
<AccordionTrigger
className={cn(
'flex items-center gap-1 justify-start transition-none p-0 hover:no-underline',
isError && 'text-red-500',
!using && 'text-muted-foreground hover:text-foreground',
using && 'loading-shimmer'
)}
id={`step-${stepName}`}
>
{using ? (
<>
<Translator path="chat.messages.status.using" /> {stepName}
</>
) : (
<>
<Translator path="chat.messages.status.used" /> {stepName}
</>
)}
</AccordionTrigger>
<AccordionContent>
<div className="flex-grow mt-4 ml-1 pl-4 border-l-2 border-primary">
{children}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
}
12 changes: 4 additions & 8 deletions frontend/src/components/chat/Messages/Message/UserMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import {
IMessageElement,
IStep,
messagesState,
useChatInteract,
useConfig
useChatInteract
} from '@chainlit/react-client';

import AutoResizeTextarea from '@/components/AutoResizeTextarea';
Expand All @@ -28,8 +27,7 @@ export default function UserMessage({
elements,
children
}: React.PropsWithChildren<Props>) {
const config = useConfig();
const { askUser, loading } = useContext(MessageContext);
const { askUser, loading, editable } = useContext(MessageContext);
const { editMessage } = useChatInteract();
const setMessages = useSetRecoilState(messagesState);
const disabled = loading || !!askUser;
Expand All @@ -42,8 +40,6 @@ export default function UserMessage({
);
}, [message.id, elements]);

const isEditable = !!config.config?.features.edit_message;

const handleEdit = () => {
if (editValue) {
setMessages((prev) => {
Expand All @@ -65,7 +61,7 @@ export default function UserMessage({
<InlinedElements elements={inlineElements} className="items-end" />

<div className="flex flex-row items-center gap-1 w-full group">
{!isEditing && isEditable && (
{!isEditing && editable && (
<Button
variant="ghost"
size="icon"
Expand All @@ -84,7 +80,7 @@ export default function UserMessage({
'px-5 py-2.5 relative bg-accent rounded-3xl',
inlineElements.length ? 'rounded-tr-lg' : '',
isEditing ? 'w-full flex-grow' : 'max-w-[70%] flex-grow-0',
isEditable ? '' : 'ml-auto'
editable ? '' : 'ml-auto'
)}
>
{isEditing ? (
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/chat/MessagesContainer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ const MessagesContainer = ({ navigate }: Props) => {
askUser,
allowHtml: config?.features?.unsafe_allow_html,
latex: config?.features?.latex,
editable: !!config?.features.edit_message,
loading,
showFeedbackButtons: enableFeedback,
uiName: config?.ui?.name || '',
Expand Down
Loading

0 comments on commit e08f7cb

Please sign in to comment.