Skip to content

Commit

Permalink
Merge pull request #60 from gmpetrov/feature/chat-history
Browse files Browse the repository at this point in the history
feat: chat history (WIP)
  • Loading branch information
gmpetrov authored May 31, 2023
2 parents cb72645 + a9a3879 commit 55d66c6
Show file tree
Hide file tree
Showing 34 changed files with 2,292 additions and 1,364 deletions.
175 changes: 94 additions & 81 deletions components/ChatBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { z } from 'zod';

type Message = { from: 'human' | 'agent'; message: string };
type Message = { from: 'human' | 'agent'; message: string; createdAt?: Date };

type Props = {
messages: Message[];
onSubmit: (message: string) => Promise<any>;
messageTemplates?: string[];
initialMessage?: string;
readOnly?: boolean;
};

const Schema = z.object({ query: z.string().min(1) });
Expand All @@ -28,6 +29,7 @@ function ChatBox({
onSubmit,
messageTemplates,
initialMessage,
readOnly,
}: Props) {
const scrollableRef = React.useRef<HTMLDivElement>(null);
const [isLoading, setIsLoading] = useState(false);
Expand Down Expand Up @@ -116,36 +118,41 @@ function ChatBox({
)}

{messages.map((each, index) => (
<Card
size="sm"
<Stack
key={index}
variant={'outlined'}
className={
each.from === 'agent' ? 'message-agent' : 'message-human'
}
color={each.from === 'agent' ? 'primary' : 'neutral'}
sx={{
mr: each.from === 'agent' ? 'auto' : 'none',
ml: each.from === 'human' ? 'auto' : 'none',
whiteSpace: 'pre-wrap',
'ul, ol': {
listStyleType: 'disc',
pl: 2,
gap: 1,
},
a: {
textDecoration: 'underline',
},
['& p']: {
m: 0,
},
gap: 2,
}}
>
<ReactMarkdown remarkPlugins={[remarkGfm]} linkTarget={'_blank'}>
{each.message}
</ReactMarkdown>
</Card>
<Card
size="sm"
variant={'outlined'}
className={
each.from === 'agent' ? 'message-agent' : 'message-human'
}
color={each.from === 'agent' ? 'primary' : 'neutral'}
sx={{
whiteSpace: 'pre-wrap',
'ul, ol': {
listStyleType: 'disc',
pl: 2,
gap: 1,
},
a: {
textDecoration: 'underline',
},
['& p']: {
m: 0,
},
gap: 2,
}}
>
<ReactMarkdown remarkPlugins={[remarkGfm]} linkTarget={'_blank'}>
{each.message}
</ReactMarkdown>
</Card>
</Stack>
))}

{isLoading && (
Expand All @@ -160,62 +167,68 @@ function ChatBox({
{/* </Stack> */}

{/* <div className="w-full h-12 -translate-y-1/2 pointer-events-none backdrop-blur-lg"></div> */}

<form
style={{
maxWidth: '100%',
width: '100%',
position: 'relative',
display: 'flex',

marginTop: 'auto',
overflow: 'visible',
background: 'none',
justifyContent: 'center',
marginLeft: 'auto',
marginRight: 'auto',
// paddingLeft: 'inherit',
// paddingRight: 'inherit',
}}
onSubmit={methods.handleSubmit(submit)}
>
{!hideTemplateMessages && (messageTemplates?.length || 0) > 0 && (
<Stack
direction="row"
gap={1}
sx={{
position: 'absolute',
zIndex: 1,
transform: 'translateY(-100%)',
flexWrap: 'wrap',
mt: -1,
left: '0',
}}
>
{messageTemplates?.map((each, idx) => (
<Button
key={idx}
size="sm"
variant="soft"
onClick={() => submit({ query: each })}
>
{each}
</Button>
))}
</Stack>
)}
<Input
sx={{ width: '100%' }}
// disabled={!state.currentDatastoreId || state.loading}
variant="outlined"
endDecorator={
<IconButton type="submit" disabled={isLoading}>
<SendRoundedIcon />
</IconButton>
}
{...methods.register('query')}
/>
</form>
{!readOnly && (
<form
style={{
maxWidth: '100%',
width: '100%',
position: 'relative',
display: 'flex',

marginTop: 'auto',
overflow: 'visible',
background: 'none',
justifyContent: 'center',
marginLeft: 'auto',
marginRight: 'auto',
// paddingLeft: 'inherit',
// paddingRight: 'inherit',
}}
onSubmit={(e) => {
e.stopPropagation();

methods.handleSubmit(submit)(e);
}}
>
{!hideTemplateMessages && (messageTemplates?.length || 0) > 0 && (
<Stack
direction="row"
gap={1}
sx={{
position: 'absolute',
zIndex: 1,
transform: 'translateY(-100%)',
flexWrap: 'wrap',
mt: -1,
left: '0',
}}
>
{messageTemplates?.map((each, idx) => (
<Button
key={idx}
size="sm"
variant="soft"
onClick={() => submit({ query: each })}
>
{each}
</Button>
))}
</Stack>
)}

<Input
sx={{ width: '100%' }}
// disabled={!state.currentDatastoreId || state.loading}
variant="outlined"
endDecorator={
<IconButton type="submit" disabled={isLoading}>
<SendRoundedIcon />
</IconButton>
}
{...methods.register('query')}
/>
</form>
)}
</Stack>
);
}
Expand Down
11 changes: 8 additions & 3 deletions components/ChatBoxFrame.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Box from '@mui/joy/Box';
import colors from '@mui/joy/colors';
import { useColorScheme } from '@mui/joy/styles';
import { Agent } from '@prisma/client';
import { Agent, ConversationChannel } from '@prisma/client';
import { useRouter } from 'next/router';
import React, { ReactElement, useEffect, useMemo } from 'react';
import React, { useEffect, useMemo } from 'react';

import ChatBox from '@app/components/ChatBox';
import useAgentChat from '@app/hooks/useAgentChat';
import useVisitorId from '@app/hooks/useVisitorId';
import { AgentInterfaceConfig } from '@app/types/models';
import pickColorBasedOnBgColor from '@app/utils/pick-color-based-on-bgcolor';

Expand All @@ -31,9 +31,14 @@ function ChatBoxFrame(props: { initConfig?: AgentInterfaceConfig }) {
const [config, setConfig] = React.useState<AgentInterfaceConfig>(
props.initConfig || defaultChatBubbleConfig
);
const { visitorId } = useVisitorId();

const { history, handleChatSubmit } = useAgentChat({
queryAgentURL: `${API_URL}/api/external/agents/${agentId}/query`,
channel: ConversationChannel.website,
// queryHistoryURL: visitorId
// ? `/api/external/agents/${router.query?.agentId}/history/${visitorId}`
// : undefined,
});

const textColor = useMemo(() => {
Expand Down
8 changes: 7 additions & 1 deletion components/ChatBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import IconButton from '@mui/joy/IconButton';
import { extendTheme, useColorScheme } from '@mui/joy/styles';
import Typography from '@mui/joy/Typography';
import Stack from '@mui/material/Stack';
import { Agent } from '@prisma/client';
import { Agent, ConversationChannel } from '@prisma/client';
import React, { useEffect, useMemo } from 'react';

import ChatBox from '@app/components/ChatBox';
import useAgentChat from '@app/hooks/useAgentChat';
import useVisitorId from '@app/hooks/useVisitorId';
import { AgentInterfaceConfig } from '@app/types/models';
import pickColorBasedOnBgColor from '@app/utils/pick-color-based-on-bgcolor';

Expand Down Expand Up @@ -47,13 +48,18 @@ const API_URL = process.env.NEXT_PUBLIC_DASHBOARD_URL;
function App(props: { agentId: string; initConfig?: AgentInterfaceConfig }) {
// const { setMode } = useColorScheme();
const [isOpen, setIsOpen] = React.useState(false);
const { visitorId } = useVisitorId();
const [agent, setAgent] = React.useState<Agent | undefined>();
const [config, setConfig] = React.useState<AgentInterfaceConfig>(
props.initConfig || defaultChatBubbleConfig
);

const { history, handleChatSubmit } = useAgentChat({
queryAgentURL: `${API_URL}/api/external/agents/${props.agentId}/query`,
channel: ConversationChannel.website,
// queryHistoryURL: visitorId
// ? `${API_URL}/api/external/agents/${props.agentId}/history/${visitorId}`
// : undefined,
});

const textColor = useMemo(() => {
Expand Down
28 changes: 27 additions & 1 deletion components/Layout/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import AutoFixHighRoundedIcon from '@mui/icons-material/AutoFixHighRounded';
import ChatBubbleIcon from '@mui/icons-material/ChatBubbleOutlineRounded';
import InboxRoundedIcon from '@mui/icons-material/InboxRounded';
import ManageAccountsRoundedIcon from '@mui/icons-material/ManageAccountsRounded';
import QuestionMarkRoundedIcon from '@mui/icons-material/QuestionMarkRounded';
import SmartToyRoundedIcon from '@mui/icons-material/SmartToyRounded'; // Icons import
import StorageRoundedIcon from '@mui/icons-material/StorageRounded';
import Badge from '@mui/joy/Badge';
import Box from '@mui/joy/Box';
import Card from '@mui/joy/Card';
import Divider from '@mui/joy/Divider';
Expand All @@ -20,6 +22,7 @@ import { useRouter } from 'next/router';
import * as React from 'react';
import useSWR from 'swr';

import { countUnread } from '@app/pages/api/logs/count-unread';
import { getStatus } from '@app/pages/api/status';
import { AppStatus, RouteNames } from '@app/types';
import { fetcher } from '@app/utils/swr-fetcher';
Expand All @@ -35,6 +38,14 @@ export default function Navigation() {
}
);

const countUnreadQuery = useSWR<Prisma.PromiseReturnType<typeof countUnread>>(
'/api/logs/count-unread',
fetcher,
{
refreshInterval: 60000,
}
);

const isStatusOK = getDatastoresQuery?.data?.status === AppStatus.OK;

const items = React.useMemo(() => {
Expand All @@ -51,6 +62,21 @@ export default function Navigation() {
icon: <StorageRoundedIcon fontSize="small" />,
active: router.route === RouteNames.DATASTORES,
},
{
label: 'Logs',
route: RouteNames.LOGS,
icon: (
<Badge
badgeContent={countUnreadQuery?.data}
size="sm"
color="danger"
invisible={!countUnreadQuery?.data || countUnreadQuery?.data <= 0}
>
<InboxRoundedIcon fontSize="small" />
</Badge>
),
active: router.route === RouteNames.LOGS,
},
// {
// label: 'Chat',
// route: RouteNames.CHAT,
Expand All @@ -75,7 +101,7 @@ export default function Navigation() {
icon: <QuestionMarkRoundedIcon fontSize="small" />,
},
];
}, [router.route]);
}, [router.route, countUnreadQuery?.data]);

return (
<Stack sx={{ height: '100%' }}>
Expand Down
2 changes: 2 additions & 0 deletions components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ export default function Layout(props: Props) {
zIndex: theme.zIndex.tooltip,
})}
>
<MenuItem>{session?.user?.email}</MenuItem>
<Divider />
<MenuItem onClick={() => signOut()}>Logout</MenuItem>
</Menu>
</Box>
Expand Down
Loading

1 comment on commit 55d66c6

@vercel
Copy link

@vercel vercel bot commented on 55d66c6 May 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.