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

chore: change ui #5

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
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
26 changes: 15 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/avatar.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ayesha of Nigcomsat</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/avatar.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.3.0/model-viewer.min.js"></script>
<title>Ayesha of Nigcomsat</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@google/model-viewer": "^3.3.0",
"@heroicons/react": "^2.0.18",
"@tanstack/react-query": "^5.7.0",
"axios": "^1.6.0",
Expand All @@ -19,8 +20,11 @@
"react-dom": "^18.2.0",
"react-hook-form": "^7.48.2",
"react-loader-spinner": "^5.3.4",
"react-router-dom": "^6.18.0",
"socket.io-client": "^4.7.2"
"react-markdown": "^9.0.1",
"react-router-dom": "^6.20.1",
"rehype-sanitize": "^6.0.0",
"socket.io-client": "^4.7.2",
"three": "^0.160.0"
},
"devDependencies": {
"@types/react": "^18.2.15",
Expand Down
Binary file modified public/avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/assets/avatar.png
Binary file not shown.
3 changes: 3 additions & 0 deletions src/assets/mic-filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion src/assets/mic-off.svg

This file was deleted.

2 changes: 1 addition & 1 deletion src/assets/send-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
267 changes: 123 additions & 144 deletions src/components/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,155 +1,134 @@
import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/solid';
import { useMutation } from '@tanstack/react-query';
import axios from 'axios';
import { cx } from 'class-variance-authority';
import { useAtom } from 'jotai';
import React, { forwardRef, useEffect, useRef, useState } from 'react';
import { ThreeDots } from 'react-loader-spinner';
import { connectionAtom, messageAtom } from '../state/atoms';
import { MessageType } from '../types';
import { useMutation } from "@tanstack/react-query";
import axios from "axios";
import { cx } from "class-variance-authority";
import { useAtom } from "jotai";
import React, { forwardRef, useEffect, useRef, useState } from "react";
import { ThreeDots } from "react-loader-spinner";
import { connectionAtom, messageAtom } from "../state/atoms";
import { MessageType } from "../types";
import ReactMarkdown from "react-markdown";
import rehypeSanitize from "rehype-sanitize";

type Message = {
message: MessageType;
createFeedback: (data: unknown) => void;
isLast?: boolean;
isFirst?: boolean;
isConnected?: boolean;
message: MessageType;
createFeedback: (data: unknown) => void;
isLast?: boolean;
isFirst?: boolean;
isConnected?: boolean;
};

const Message = forwardRef<HTMLDivElement, Message>(
({ message, isFirst, isLast, isConnected, createFeedback }, ref) => {
const [feedback, setFeedback] = useState<'like' | 'dislike' | undefined>();

const containerClass = cx('flex w-full', {
'justify-start rounded-md relative': message.role === 'assistant',
'animate-pop': message.role === 'assistant' && isLast && !message.typing,
'justify-end rounded-md': message.role === 'user',
});

const itemClass = cx('p-4 dark:text-white text-black rounded-b-xl max-w-[35rem]', {
'rounded-tr-xl bg-[#ffcb0520] relative': message.role === 'assistant',
'rounded-tl-xl dark:bg-[#ffffff26] bg-[#00000026] ': message.role === 'user',
});

const nowTyping = message.typing && isLast;
const wasTyping = message.typing && !isLast;

const handleFeedback = (which: 'like' | 'dislike') => {
if (!isFirst) {
setFeedback(which);
createFeedback({
orgId: message.orgId,
sessionId: message.sessionId,
feedback: which,
});
}
};

if (wasTyping) return null;

return (
<section className={containerClass} ref={isLast ? ref : undefined}>
<span className={itemClass}>
{nowTyping ? (
<ThreeDots
height='20'
width='20'
radius='5'
color='white'
ariaLabel='three-dots-loading'
visible={true}
/>
) : (
<p>
{message.message}
<span className={cx({ hidden: isFirst || !isLast || !isConnected })}>
<button
className='absolute top-0 -right-5'
onClick={() => handleFeedback('like')}
disabled={!!feedback}
>
<HandThumbUpIcon
className={cx('h-5 w-5 text-gray-400 ', {
'text-green-300': feedback === 'like',
'hover:text-green-500': !feedback,
})}
/>
</button>
<button
className='absolute top-0 -right-12'
onClick={() => handleFeedback('dislike')}
disabled={!!feedback}
>
<HandThumbDownIcon
className={cx('h-5 w-5 text-gray-400', {
'text-red-300': feedback === 'dislike',
'hover:text-red-500': !feedback,
})}
/>
</button>
</span>
</p>
)}
</span>
</section>
);
},
({ message, isLast }, ref) => {
const containerClass = cx("flex w-full text-xs md:text-base", {
"justify-start rounded-md relative": message.role === "assistant",
"animate-pop": message.role === "assistant" && isLast && !message.typing,
"justify-end rounded-md": message.role === "user",
});

const itemClass = cx(
"p-4 text-white rounded-b-xl max-w-[80%] md:max-w-[35rem]",
{
"rounded-tr-xl bg-white bg-opacity-10 relative": message.role === "assistant",
"rounded-tl-xl user-message-gradient":
message.role === "user",
}
);

const nowTyping = message.typing && isLast;
const wasTyping = message.typing && !isLast;

if (wasTyping) return null;

return (
<section className={containerClass} ref={isLast ? ref : undefined}>
<span className={itemClass}>
{nowTyping ? (
<ThreeDots
height="20"
width="20"
radius="5"
color="white"
ariaLabel="three-dots-loading"
visible={true}
/>
) : (
<>
<ReactMarkdown
components={{
code(props) {
const { children } = props;
return <code className="font-sans">{children}</code>;
},
}}
className="[&>pre]:whitespace-pre-line"
rehypePlugins={[rehypeSanitize]}
>
{message.message}
</ReactMarkdown>
</>
)}
</span>
</section>
);
}
);

const Dialog: React.FC = () => {
const [messages] = useAtom(messageAtom);
const [{ totalDislikes, messenger, connected }, setConnection] = useAtom(connectionAtom);
const [isEscalated, setIsEscalated] = useState(false);

const { mutate: createFeedback } = useMutation({
mutationKey: ['createFeedback'],
mutationFn: (data: unknown) => axios.post(`${import.meta.env.VITE_BASE_URL}/feedback`, data),
onSuccess: () => {
setConnection((prev) => ({
...prev,
totalDislikes: prev?.totalDislikes ? prev.totalDislikes + 1 : 1,
}));
},
});

if (!isEscalated && totalDislikes === 3) {
setIsEscalated(true);
}

useEffect(() => {
if (isEscalated) {
setIsEscalated(false);
messenger?.call({
action: 'prompt',
adhoc: 'escalate-level-1',
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isEscalated]);

const lastMessageRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (lastMessageRef.current) {
lastMessageRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [messages]);

return (
<div className='flex flex-1 flex-col gap-y-8 p-3 md:px-6 h-[75%] overflow-y-scroll scrollbar-hide'>
{messages.map((message, index, arr) => (
<Message
key={index}
message={message}
isFirst={index === 0}
isLast={index === arr.length - 1}
isConnected={connected}
createFeedback={createFeedback}
ref={lastMessageRef}
/>
))}
</div>
);
const [messages] = useAtom(messageAtom);
const [{ totalDislikes, messenger, connected }, setConnection] =
useAtom(connectionAtom);
const [isEscalated, setIsEscalated] = useState(false);

const { mutate: createFeedback } = useMutation({
mutationKey: ["createFeedback"],
mutationFn: (data: unknown) =>
axios.post(`${import.meta.env.VITE_BASE_URL}/feedback`, data),
onSuccess: () => {
setConnection((prev) => ({
...prev,
totalDislikes: prev?.totalDislikes ? prev.totalDislikes + 1 : 1,
}));
},
});

if (!isEscalated && totalDislikes === 3) {
setIsEscalated(true);
}

useEffect(() => {
if (isEscalated) {
setIsEscalated(false);
messenger?.call({
action: "prompt",
adhoc: "escalate-level-1",
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isEscalated]);

const lastMessageRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (lastMessageRef.current) {
lastMessageRef.current.scrollIntoView({ behavior: "smooth" });
}
}, [messages]);

return (
<div className="flex flex-1 flex-col gap-y-4 md:gap-y-8 p-3 md:px-6 h-full overflow-y-scroll scrollbar-hide">
{messages.map((message, index, arr) => (
<Message
key={index}
message={message}
isFirst={index === 0}
isLast={index === arr.length - 1}
isConnected={connected}
createFeedback={createFeedback}
ref={lastMessageRef}
/>
))}
</div>
);
};

export default Dialog;
18 changes: 9 additions & 9 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useAtom } from 'jotai';
import { useEffect } from 'react';
import { Radio } from 'react-loader-spinner';
import avatar from '../assets/avatar.png';
import avatar from '/avatar.png';
import cancelIcon from '../assets/cancel.svg';
import wifiIcon from '../assets/wifi.svg';
import { connectionAtom, socketAtom } from '../state/atoms';
Expand Down Expand Up @@ -30,13 +30,13 @@ const Header: React.FC<Props> = ({ agentName }) => {

return (
<>
<div className='flex justify-between items-center p-4 sticky top-0 mb-5'>
<div className='flex items-center gap-5'>
<div className='w-14 h-14 rounded-full overflow-hidden bg-gray-100'>
<div className='flex justify-between items-center px-2 py-1 md:p-4 sticky top-0 mb-2 md:mb-5 lg:hidden'>
<div className='flex items-center gap-3 md:gap-5'>
<div className='w-8 h-8 md:w-14 md:h-14 rounded-full overflow-hidden bg-gray-100'>
<img src={avatar} alt='avatar' />
</div>
<div className='flex flex-col justify-between'>
<h1 className='font-bold text-xl dark:text-white text-black'>Ask {agentName}</h1>
<h1 className='font-bold text-sm md:text-xl text-white'>Ask {agentName}</h1>

{connected ? (
<Radio
Expand All @@ -47,12 +47,12 @@ const Header: React.FC<Props> = ({ agentName }) => {
wrapperClass='radio-wrapper'
/>
) : (
<div className='relative flex gap-x-3 '>
<div className='relative flex gap-x-1 md:gap-x-3 '>
<section>
<img src={wifiIcon} className='h-5 w-5 dark:invert-[100%]' alt='' />
<img src={cancelIcon} className='h-5 w-5 absolute top-0' alt='' />
<img src={wifiIcon} className='h-3 w-3 md:h-5 md:w-5 mt-1 md:mt-0 dark:invert-[100%]' alt='' />
<img src={cancelIcon} className='h-3 w-3 md:h-5 md:w-5 absolute top-1 md:top-0' alt='' />
</section>
<p className='leading-5 text-sm text-rose-500'>
<p className='leading-5 text-xs md:text-sm text-rose-500'>
Your chat session has ended due to inactivity.
</p>
</div>
Expand Down
Loading