Use Turnstyle UX pattern to group Khoj chats into conversation turns

A turn is defined as a single back and forth between the user and AI.

A typical user - agent interaction is more strongly coupled into a
request, response pair than a typical chat message interaction between
humans.

Turnstyle UX emphasis this natural coupling of the request, response
pairs by grouping a conversation turn more strongly together than
the older, more typical chat bubble UX.

Turnstyle UX renders each user's query and AI's response into a
section heading, section content respectively for cleaner, more
natural demarcation of a single interaction
This commit is contained in:
Debanjum Singh Solanky 2024-07-31 22:27:15 +05:30
parent 5a8ea884a9
commit dc47ba065c
3 changed files with 78 additions and 63 deletions

View file

@ -265,7 +265,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
chatMessage={chatMessage} chatMessage={chatMessage}
customClassName='fullHistory' customClassName='fullHistory'
borderLeftColor={`${data?.agent.color}-500`} borderLeftColor={`${data?.agent.color}-500`}
isLastMessage={index === data.chat.length - 1} isLastMessage={index === data.chat.length - 2}
/> />
))} ))}
{ {
@ -288,6 +288,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
} }
customClassName='fullHistory' customClassName='fullHistory'
borderLeftColor={`${data?.agent.color}-500`} borderLeftColor={`${data?.agent.color}-500`}
isLastMessage={true}
/> />
{ {
message.trainOfThought && message.trainOfThought &&
@ -313,7 +314,6 @@ export default function ChatHistory(props: ChatHistoryProps) {
} }
customClassName='fullHistory' customClassName='fullHistory'
borderLeftColor={`${data?.agent.color}-500`} borderLeftColor={`${data?.agent.color}-500`}
isLastMessage={true}
/> />
</React.Fragment> </React.Fragment>
) )

View file

@ -1,14 +1,22 @@
div.chatMessageContainer { div.chatMessageContainer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 12px;
border-radius: 16px; border-radius: 16px;
}
div.chatMessageContainer.khoj {
margin: 0;
padding: 8px 16px 0 16px; padding: 8px 16px 0 16px;
box-shadow: 0 4px 10px var(--box-shadow-color);
border-top: transparent;
}
div.chatMessageContainer.you {
margin-top: 12px;
padding-top: 16px;
box-shadow: none;
border-top: transparent;
} }
div.chatMessageWrapper { div.chatMessageWrapper {
padding-left: 1rem;
padding-bottom: 1rem;
max-width: 80vw; max-width: 80vw;
} }
div.chatMessageWrapper p:not(:last-child) { div.chatMessageWrapper p:not(:last-child) {
@ -25,23 +33,22 @@ div.youfullHistory {
} }
div.chatMessageContainer.youfullHistory { div.chatMessageContainer.youfullHistory {
padding-left: 0px; padding-left: 16px;
} }
div.you { div.you {
background-color: hsla(var(--secondary)); background-color: transparent;
align-self: flex-end; align-self: flex-start;
border-radius: 16px;
} }
div.khoj { div.khoj {
background-color: transparent; background-color: transparent;
color: hsl(var(--accent-foreground)); color: hsl(var(--accent-foreground));
align-self: flex-start; align-self: flex-start;
padding-left: 1rem;
} }
div.khojChatMessage { div.khojChatMessage {
padding-top: 8px;
padding-left: 16px; padding-left: 16px;
} }
@ -64,8 +71,6 @@ div.author {
} }
div.chatFooter { div.chatFooter {
display: flex;
justify-content: space-between;
min-height: 28px; min-height: 28px;
} }

View file

@ -10,7 +10,7 @@ import 'katex/dist/katex.min.css';
import { TeaserReferencesSection, constructAllReferences } from '../referencePanel/referencePanel'; import { TeaserReferencesSection, constructAllReferences } from '../referencePanel/referencePanel';
import { ThumbsUp, ThumbsDown, Copy, Brain, Cloud, Folder, Book, Aperture, SpeakerHigh, MagnifyingGlass, Pause, Palette } from '@phosphor-icons/react'; import { ThumbsUp, ThumbsDown, Copy, Brain, Cloud, Folder, Book, Aperture, SpeakerHigh, MagnifyingGlass, Pause, Palette, Clock } from '@phosphor-icons/react';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import { InlineLoading } from '../loading/loading'; import { InlineLoading } from '../loading/loading';
@ -331,8 +331,12 @@ export default function ChatMessage(props: ChatMessageProps) {
} }
function chatMessageWrapperClasses(chatMessage: SingleChatMessage) { function chatMessageWrapperClasses(chatMessage: SingleChatMessage) {
let classes = [styles.chatMessageWrapper]; let classes = [styles.chatMessageWrapper, styles[chatMessage.by]];
classes.push(styles[chatMessage.by]); if (chatMessage.by === "you") {
let fontClass = chatMessage.message.length > 100 ? "text-md" : "text-xl";
classes.push(fontClass);
}
if (chatMessage.by === "khoj") { if (chatMessage.by === "khoj") {
classes.push(`border-l-4 border-opacity-50 ${"border-l-" + props.borderLeftColor || "border-l-orange-400"}`); classes.push(`border-l-4 border-opacity-50 ${"border-l-" + props.borderLeftColor || "border-l-orange-400"}`);
} }
@ -429,58 +433,64 @@ export default function ChatMessage(props: ChatMessageProps) {
notesReferenceCardData={allReferences.notesReferenceCardData} notesReferenceCardData={allReferences.notesReferenceCardData}
onlineReferenceCardData={allReferences.onlineReferenceCardData} /> onlineReferenceCardData={allReferences.onlineReferenceCardData} />
</div> </div>
<div className={styles.chatFooter}>
{ {
(isHovering || props.isMobileWidth || props.isLastMessage || isPlaying) && props.chatMessage.by === "you" &&
( (
<> <div className={`${styles.chatFooter} flex justify-start`}>
<div title={formatDate(props.chatMessage.created)} className={`text-gray-400 relative top-0 left-4`}> {
{renderTimeStamp(props.chatMessage.created)} (isHovering || props.isMobileWidth || props.isLastMessage || isPlaying) && (
</div> <div title={formatDate(props.chatMessage.created)} className={`text-gray-400 relative top-0 text-sm`}>
<div className={`${styles.chatButtons} shadow-sm`}> <Clock className={`inline mr-2 top-1 text-gray-400`} />
{ {renderTimeStamp(props.chatMessage.created)}
(props.chatMessage.by === "khoj") && </div>
( )
isPlaying ? }
( </div>
interrupted ? ) || props.chatMessage.by === "khoj" &&
<InlineLoading iconClassName='p-0' className='m-0' /> (
: <button title="Pause Speech" onClick={(event) => setInterrupted(true)}> <div className={`${styles.chatFooter} flex justify-end`}>
<Pause alt="Pause Message" color='hsl(var(--muted-foreground))' /> {
</button> (isHovering || props.isMobileWidth || props.isLastMessage || isPlaying) &&
) (
: <button title="Speak" onClick={(event) => playTextToSpeech()}> <div className={`${styles.chatButtons} shadow-sm`}>
<SpeakerHigh alt="Speak Message" color='hsl(var(--muted-foreground))' /> {
</button> isPlaying ?
) (
} interrupted ?
<button title="Copy" className={`${styles.copyButton}`} onClick={() => { <InlineLoading iconClassName='p-0' className='m-0' />
navigator.clipboard.writeText(props.chatMessage.message); : <button title="Pause Speech" onClick={(event) => setInterrupted(true)}>
setCopySuccess(true); <Pause alt="Pause Message" color='hsl(var(--muted-foreground))' />
}}> </button>
{ )
copySuccess ? : <button title="Speak" onClick={(event) => playTextToSpeech()}>
<Copy alt="Copied Message" weight="fill" color='green' /> <SpeakerHigh alt="Speak Message" color='hsl(var(--muted-foreground))' />
: <Copy alt="Copy Message" color='hsl(var(--muted-foreground))' /> </button>
} }
</button> <button title="Copy" className={`${styles.copyButton}`} onClick={() => {
{ navigator.clipboard.writeText(props.chatMessage.message);
(props.chatMessage.by === "khoj") && setCopySuccess(true);
( }}>
props.chatMessage.intent ? {
<FeedbackButtons copySuccess ?
uquery={props.chatMessage.intent.query} <Copy alt="Copied Message" weight="fill" color='green' />
kquery={props.chatMessage.message} /> : <Copy alt="Copy Message" color='hsl(var(--muted-foreground))' />
: <FeedbackButtons }
uquery={props.chatMessage.rawQuery || props.chatMessage.message} </button>
kquery={props.chatMessage.message} /> {
) props.chatMessage.intent ?
} <FeedbackButtons
</div> uquery={props.chatMessage.intent.query}
</> kquery={props.chatMessage.message} />
: <FeedbackButtons
uquery={props.chatMessage.rawQuery || props.chatMessage.message}
kquery={props.chatMessage.message} />
}
</div>
)
}
</div>
) )
} }
</div>
</div> </div>
) )
} }