mirror of
https://github.com/khoj-ai/khoj.git
synced 2024-11-30 10:53:02 +01:00
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:
parent
5a8ea884a9
commit
dc47ba065c
3 changed files with 78 additions and 63 deletions
|
@ -265,7 +265,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||
chatMessage={chatMessage}
|
||||
customClassName='fullHistory'
|
||||
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'
|
||||
borderLeftColor={`${data?.agent.color}-500`}
|
||||
isLastMessage={true}
|
||||
/>
|
||||
{
|
||||
message.trainOfThought &&
|
||||
|
@ -313,7 +314,6 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||
}
|
||||
customClassName='fullHistory'
|
||||
borderLeftColor={`${data?.agent.color}-500`}
|
||||
isLastMessage={true}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
div.chatMessageContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 12px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
div.chatMessageContainer.khoj {
|
||||
margin: 0;
|
||||
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 {
|
||||
padding-left: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
max-width: 80vw;
|
||||
}
|
||||
div.chatMessageWrapper p:not(:last-child) {
|
||||
|
@ -25,23 +33,22 @@ div.youfullHistory {
|
|||
}
|
||||
|
||||
div.chatMessageContainer.youfullHistory {
|
||||
padding-left: 0px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
div.you {
|
||||
background-color: hsla(var(--secondary));
|
||||
align-self: flex-end;
|
||||
border-radius: 16px;
|
||||
background-color: transparent;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
div.khoj {
|
||||
background-color: transparent;
|
||||
color: hsl(var(--accent-foreground));
|
||||
align-self: flex-start;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
div.khojChatMessage {
|
||||
padding-top: 8px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
|
@ -64,8 +71,6 @@ div.author {
|
|||
}
|
||||
|
||||
div.chatFooter {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'katex/dist/katex.min.css';
|
|||
|
||||
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 { InlineLoading } from '../loading/loading';
|
||||
|
@ -331,8 +331,12 @@ export default function ChatMessage(props: ChatMessageProps) {
|
|||
}
|
||||
|
||||
function chatMessageWrapperClasses(chatMessage: SingleChatMessage) {
|
||||
let classes = [styles.chatMessageWrapper];
|
||||
classes.push(styles[chatMessage.by]);
|
||||
let classes = [styles.chatMessageWrapper, styles[chatMessage.by]];
|
||||
if (chatMessage.by === "you") {
|
||||
let fontClass = chatMessage.message.length > 100 ? "text-md" : "text-xl";
|
||||
classes.push(fontClass);
|
||||
}
|
||||
|
||||
if (chatMessage.by === "khoj") {
|
||||
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}
|
||||
onlineReferenceCardData={allReferences.onlineReferenceCardData} />
|
||||
</div>
|
||||
<div className={styles.chatFooter}>
|
||||
{
|
||||
(isHovering || props.isMobileWidth || props.isLastMessage || isPlaying) &&
|
||||
props.chatMessage.by === "you" &&
|
||||
(
|
||||
<>
|
||||
<div title={formatDate(props.chatMessage.created)} className={`text-gray-400 relative top-0 left-4`}>
|
||||
{renderTimeStamp(props.chatMessage.created)}
|
||||
</div>
|
||||
<div className={`${styles.chatButtons} shadow-sm`}>
|
||||
{
|
||||
(props.chatMessage.by === "khoj") &&
|
||||
(
|
||||
isPlaying ?
|
||||
(
|
||||
interrupted ?
|
||||
<InlineLoading iconClassName='p-0' className='m-0' />
|
||||
: <button title="Pause Speech" onClick={(event) => setInterrupted(true)}>
|
||||
<Pause alt="Pause Message" color='hsl(var(--muted-foreground))' />
|
||||
</button>
|
||||
)
|
||||
: <button title="Speak" onClick={(event) => playTextToSpeech()}>
|
||||
<SpeakerHigh alt="Speak Message" color='hsl(var(--muted-foreground))' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
<button title="Copy" className={`${styles.copyButton}`} onClick={() => {
|
||||
navigator.clipboard.writeText(props.chatMessage.message);
|
||||
setCopySuccess(true);
|
||||
}}>
|
||||
{
|
||||
copySuccess ?
|
||||
<Copy alt="Copied Message" weight="fill" color='green' />
|
||||
: <Copy alt="Copy Message" color='hsl(var(--muted-foreground))' />
|
||||
}
|
||||
</button>
|
||||
{
|
||||
(props.chatMessage.by === "khoj") &&
|
||||
(
|
||||
props.chatMessage.intent ?
|
||||
<FeedbackButtons
|
||||
uquery={props.chatMessage.intent.query}
|
||||
kquery={props.chatMessage.message} />
|
||||
: <FeedbackButtons
|
||||
uquery={props.chatMessage.rawQuery || props.chatMessage.message}
|
||||
kquery={props.chatMessage.message} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
<div className={`${styles.chatFooter} flex justify-start`}>
|
||||
{
|
||||
(isHovering || props.isMobileWidth || props.isLastMessage || isPlaying) && (
|
||||
<div title={formatDate(props.chatMessage.created)} className={`text-gray-400 relative top-0 text-sm`}>
|
||||
<Clock className={`inline mr-2 top-1 text-gray-400`} />
|
||||
{renderTimeStamp(props.chatMessage.created)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
) || props.chatMessage.by === "khoj" &&
|
||||
(
|
||||
<div className={`${styles.chatFooter} flex justify-end`}>
|
||||
{
|
||||
(isHovering || props.isMobileWidth || props.isLastMessage || isPlaying) &&
|
||||
(
|
||||
<div className={`${styles.chatButtons} shadow-sm`}>
|
||||
{
|
||||
isPlaying ?
|
||||
(
|
||||
interrupted ?
|
||||
<InlineLoading iconClassName='p-0' className='m-0' />
|
||||
: <button title="Pause Speech" onClick={(event) => setInterrupted(true)}>
|
||||
<Pause alt="Pause Message" color='hsl(var(--muted-foreground))' />
|
||||
</button>
|
||||
)
|
||||
: <button title="Speak" onClick={(event) => playTextToSpeech()}>
|
||||
<SpeakerHigh alt="Speak Message" color='hsl(var(--muted-foreground))' />
|
||||
</button>
|
||||
}
|
||||
<button title="Copy" className={`${styles.copyButton}`} onClick={() => {
|
||||
navigator.clipboard.writeText(props.chatMessage.message);
|
||||
setCopySuccess(true);
|
||||
}}>
|
||||
{
|
||||
copySuccess ?
|
||||
<Copy alt="Copied Message" weight="fill" color='green' />
|
||||
: <Copy alt="Copy Message" color='hsl(var(--muted-foreground))' />
|
||||
}
|
||||
</button>
|
||||
{
|
||||
props.chatMessage.intent ?
|
||||
<FeedbackButtons
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue