Skip to content
Open
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
61 changes: 26 additions & 35 deletions frontend/src/components/chatMessage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,49 @@
import { Remark } from 'react-remark';
import { Avatar } from 'reshaped';

type ChatMessageProps = {
index: number;
userName?: string;
userPicture?: string;
refresh: (_: number) => void;
deleteMessage: (_: number) => void;
convertToComment: (_: number) => void;
};

export default function ChatMessage(props: ChatMessage & ChatMessageProps) {
const getAvatarSrc = () => {
if (props.role === 'user') {
// Use Auth0 picture if available, otherwise use initials avatar
if (props.userPicture) {
return props.userPicture;
}
const userName = props.userName || 'User';
return `https://api.dicebear.com/9.x/initials/svg?seed=${encodeURIComponent(userName)}&backgroundColor=1e88e5`;
} else {
// AI assistant avatar
return 'https://api.dicebear.com/9.x/bottts/svg?seed=AI&backgroundColor=10b981';
}
};

return (
<div
className={`w-full mb-2 ${props.role === 'user' ? 'flex justify-end' : 'flex justify-start'}`}
className={`w-full flex ${props.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
{/*
props.role !== 'assistant' ? (
<div className={ classes.toolbar }>
<FiRefreshCcw
className={classes.icon}
onClick={() => props.refresh(props.index)}
/>
</div>
) : (
<div className={ classes.toolbar }>
<FiTrash2
className={classes.icon}
onClick={() => props.deleteMessage(props.index)}
/>

<TfiCommentAlt
className={classes.icon}
onClick={() => props.convertToComment(props.index)}
/>
</div>
)*/}
<div
className={`max-w-[85%] p-4 border rounded-lg flex gap-4 items-start ${
className={`max-w-[75%] p-5 rounded-xl flex gap-4 items-start shadow-sm ${
props.role === 'user'
? 'bg-blue-50 border-blue-300 mr-2'
: 'bg-gray-50 border-gray-300 ml-2'
? 'bg-blue-50 border border-blue-200'
: 'bg-gray-50 border border-gray-200'
}`}
>
<div className="flex-shrink-0">
{props.role === 'user' ? (
<img
src="https://api.dicebear.com/9.x/initials/svg?seed=User"
/*hardcoded for now, to change later with api, backgroundColor=00acc1,1e88e5,5e35b1,7cb342,8e24aa,039be5,43a047,00897b,3949ab,c0ca33,d81b60,e53935,f4511e,fb8c00,fdd835,ffb300*/
className="w-8 h-8"
/>
) : (
<div className="w-[30px] h-[30px] bg-emerald-400" />
)}
<Avatar
src={getAvatarSrc()}
size={10}
/>
</div>

<div className="text-gray-800">
<div className="text-gray-800 flex-1 min-w-0 prose prose-sm max-w-none">
<Remark>{props.content}</Remark>
</div>
</div>
Expand Down
65 changes: 39 additions & 26 deletions frontend/src/pages/chat/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useState, useContext, useEffect } from 'react';
import { useAuth0 } from '@auth0/auth0-react';

import { AiOutlineSend } from 'react-icons/ai';
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { Button, TextField } from 'reshaped';

import ChatMessage from '@/components/chatMessage';

Expand All @@ -20,6 +22,7 @@ export default function Chat() {
const username = useAtomValue(usernameAtom);
const editorAPI = useContext(EditorContext);
const { getAccessToken, authErrorType } = useAccessToken();
const { user } = useAuth0();

const docContext = useDocContext(editorAPI);

Expand Down Expand Up @@ -137,14 +140,16 @@ export default function Chat() {
}

return (
<div className="m-2 flex flex-col gap-4">
<div className="flex-col gap-2 max-h-[500px] bottom-0 overflow-y-auto">
<div className="h-full flex flex-col p-4 gap-4">
<div className="flex-1 flex flex-col gap-3 overflow-y-auto pb-4">
{messagesWithCurDocContext.slice(2).map((message, index) => (
<ChatMessage
key={index + 2}
role={message.role}
content={message.content}
index={index + 2}
userName={user?.name}
userPicture={user?.picture}
refresh={(index: number) => {
void regenMessage(index);
}}
Expand All @@ -154,35 +159,43 @@ export default function Chat() {
))}
</div>

<form
className="w-full flex flex-col gap-2"
onSubmit={(e) => {
void sendMessage(e);
}}
>
<label className="flex items-center border border-gray-500 justify-between p-[10px]">
<textarea
<div className="flex-shrink-0 flex flex-col gap-2">
<form
className="w-full flex gap-2 items-end"
onSubmit={(e) => {
void sendMessage(e);
}}
>
<div className="flex-1">
<TextField
name="message"
disabled={isSendingMessage}
placeholder="Send a message"
placeholder="Send a message..."
value={message}
onChange={(e) => updateMessage(e.target.value)}
onChange={(event) => updateMessage(event.value)}
multiline
/>

<button
</div>
<Button
type="submit"
className="bg-transparent cursor-pointer border border-black px-[10px] py-[5px] bottom-0 transition duration-150 self-end hover:bg-black hover:text-white"
color="primary"
variant="solid"
disabled={isSendingMessage || !message.trim()}
icon={<AiOutlineSend />}
>
<AiOutlineSend />
</button>
</label>
</form>

<button
onClick={() => updateChatMessages([])}
className="bg-transparent cursor-pointer border boder-black px-[10px] py-[5px] bottom-0 transition duration-150 self-end hover:bg-black hover:text-white"
>
Clear Chat
</button>
Send
</Button>
</form>

<Button
onClick={() => updateChatMessages([])}
color="neutral"
variant="outline"
size="small"
>
Clear Chat
</Button>
</div>
</div>
);
}