mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-08-12 13:42:23 +00:00
feat: 布局初版
This commit is contained in:
parent
b198f8d326
commit
6a2df06f88
@ -6,9 +6,8 @@ export default function RootLayout({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<Box sx={{ color: 'red' }}>
|
||||
123
|
||||
<>
|
||||
{children}
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,8 +1,214 @@
|
||||
"use client"
|
||||
import ChatBoxComp from '@/components/chatBox';
|
||||
import { Chart, LineAdvance, Interval, Tooltip, getTheme } from 'bizcharts';
|
||||
import { Card, CardContent, Typography, Grid, styled, Sheet } from '@/lib/mui';
|
||||
import { Stack } from '@mui/material';
|
||||
import useAgentChat from '@/hooks/useAgentChat';
|
||||
|
||||
|
||||
const Item = styled(Sheet)(({ theme }) => ({
|
||||
...theme.typography.body2,
|
||||
padding: theme.spacing(1),
|
||||
textAlign: 'center',
|
||||
borderRadius: 4,
|
||||
color: theme.vars.palette.text.secondary,
|
||||
}));
|
||||
|
||||
const Agents = () => {
|
||||
const { handleChatSubmit, history } = useAgentChat({
|
||||
queryAgentURL: `/api/agents/query`,
|
||||
});
|
||||
|
||||
const data = [
|
||||
{
|
||||
month: "Jan",
|
||||
city: "Tokyo",
|
||||
temperature: 7
|
||||
},
|
||||
{
|
||||
month: "Feb",
|
||||
city: "Tokyo",
|
||||
temperature: 13
|
||||
},
|
||||
{
|
||||
month: "Mar",
|
||||
city: "Tokyo",
|
||||
temperature: 16.5
|
||||
},
|
||||
{
|
||||
month: "Apr",
|
||||
city: "Tokyo",
|
||||
temperature: 14.5
|
||||
},
|
||||
{
|
||||
month: "May",
|
||||
city: "Tokyo",
|
||||
temperature: 10
|
||||
},
|
||||
{
|
||||
month: "Jun",
|
||||
city: "Tokyo",
|
||||
temperature: 7.5
|
||||
},
|
||||
{
|
||||
month: "Jul",
|
||||
city: "Tokyo",
|
||||
temperature: 9.2
|
||||
},
|
||||
{
|
||||
month: "Aug",
|
||||
city: "Tokyo",
|
||||
temperature: 14.5
|
||||
},
|
||||
{
|
||||
month: "Sep",
|
||||
city: "Tokyo",
|
||||
temperature: 9.3
|
||||
},
|
||||
{
|
||||
month: "Oct",
|
||||
city: "Tokyo",
|
||||
temperature: 8.3
|
||||
},
|
||||
{
|
||||
month: "Nov",
|
||||
city: "Tokyo",
|
||||
temperature: 8.9
|
||||
},
|
||||
{
|
||||
month: "Dec",
|
||||
city: "Tokyo",
|
||||
temperature: 5.6
|
||||
},
|
||||
];
|
||||
|
||||
const d1 = [
|
||||
{ year: '1951 年', sales: 0 },
|
||||
{ year: '1952 年', sales: 52 },
|
||||
{ year: '1956 年', sales: 61 },
|
||||
{ year: '1957 年', sales: 45 },
|
||||
{ year: '1958 年', sales: 48 },
|
||||
{ year: '1959 年', sales: 38 },
|
||||
{ year: '1960 年', sales: 38 },
|
||||
{ year: '1962 年', sales: 38 },
|
||||
];
|
||||
|
||||
const topCard = [{
|
||||
label: 'Revenue Won',
|
||||
value: '$7,811,851'
|
||||
}, {
|
||||
label: 'Close %',
|
||||
value: '37.7%'
|
||||
}, {
|
||||
label: 'AVG Days to Close',
|
||||
value: '121'
|
||||
}, {
|
||||
label: 'Opportunities Won',
|
||||
value: '526'
|
||||
}];
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
HHH
|
||||
<div className='p-4 flex flex-row gap-6 min-h-full w-full'>
|
||||
<div className='flex w-full'>
|
||||
<Grid container spacing={2} sx={{ flexGrow: 1 }}>
|
||||
<Grid xs={8}>
|
||||
<Stack spacing={2} className='h-full'>
|
||||
<Item>
|
||||
<Grid container spacing={2}>
|
||||
{topCard.map((item) => (
|
||||
<Grid key={item.label} xs={3}>
|
||||
<Card className="flex-1 h-full">
|
||||
<CardContent className="justify-around">
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
{item.label}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{item.value}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Item>
|
||||
<Item className='flex-1'>
|
||||
<Card className='h-full'>
|
||||
<CardContent className='h-full'>
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
Revenue Won by Month
|
||||
</Typography>
|
||||
<div className='flex-1'>
|
||||
<Chart padding={[10, 20, 50, 40]} autoFit data={data} >
|
||||
<LineAdvance
|
||||
shape="smooth"
|
||||
point
|
||||
area
|
||||
position="month*temperature"
|
||||
color="city"
|
||||
/>
|
||||
</Chart>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Item>
|
||||
<Item className='flex-1'>
|
||||
<Grid container spacing={2} className='h-full'>
|
||||
<Grid xs={4} className='h-full'>
|
||||
<Card className='flex-1 h-full'>
|
||||
<CardContent className='h-full'>
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
Close % by Month
|
||||
</Typography>
|
||||
<div className='flex-1'>
|
||||
<Chart autoFit data={d1} >
|
||||
<Interval position="year*sales" style={{ lineWidth: 3, stroke: getTheme().colors10[0] }} />
|
||||
<Tooltip shared />
|
||||
</Chart>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid xs={4} className='h-full'>
|
||||
<Card className='flex-1 h-full'>
|
||||
<CardContent className='h-full'>
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
Close % by Month
|
||||
</Typography>
|
||||
<div className='flex-1'>
|
||||
<Chart autoFit data={d1} >
|
||||
<Interval position="year*sales" style={{ lineWidth: 3, stroke: getTheme().colors10[0] }} />
|
||||
<Tooltip shared />
|
||||
</Chart>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid xs={4} className='h-full'>
|
||||
<Card className='flex-1 h-full'>
|
||||
<CardContent className='h-full'>
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
Close % by Month
|
||||
</Typography>
|
||||
<div className='flex-1'>
|
||||
<Chart autoFit data={d1} >
|
||||
<Interval position="year*sales" style={{ lineWidth: 3, stroke: getTheme().colors10[0] }} />
|
||||
<Tooltip shared />
|
||||
</Chart>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Item>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid xs={4}>
|
||||
<ChatBoxComp messages={history} onSubmit={handleChatSubmit}/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Agents;
|
@ -2,60 +2,62 @@ import { extendTheme } from '@mui/joy/styles';
|
||||
import colors from '@mui/joy/colors';
|
||||
|
||||
export const joyTheme = extendTheme({
|
||||
colorSchemes: {
|
||||
light: {
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
...colors.purple,
|
||||
},
|
||||
neutral: {
|
||||
plainColor: '#25252D',
|
||||
plainHoverColor: '#131318',
|
||||
plainHoverBg: '#EBEBEF',
|
||||
plainActiveBg: '#D8D8DF',
|
||||
plainDisabledColor: '#B9B9C6'
|
||||
},
|
||||
background: {
|
||||
body: '#fff'
|
||||
},
|
||||
text: {
|
||||
primary: '#25252D'
|
||||
},
|
||||
colorSchemes: {
|
||||
light: {
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
...colors.purple,
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
palette: {
|
||||
mode: 'light',
|
||||
primary: {
|
||||
...colors.purple,
|
||||
},
|
||||
neutral: {
|
||||
plainColor: '#D8D8DF',
|
||||
plainHoverColor: '#F7F7F8',
|
||||
plainHoverBg: '#25252D',
|
||||
plainActiveBg: '#434356',
|
||||
plainDisabledColor: '#434356'
|
||||
},
|
||||
text: {
|
||||
primary: '#EBEBEF'
|
||||
},
|
||||
background: {
|
||||
body: '#09090D'
|
||||
}
|
||||
neutral: {
|
||||
plainColor: '#25252D',
|
||||
plainHoverColor: '#131318',
|
||||
plainHoverBg: '#EBEBEF',
|
||||
plainActiveBg: '#D8D8DF',
|
||||
plainDisabledColor: '#B9B9C6'
|
||||
},
|
||||
background: {
|
||||
body: '#fff',
|
||||
surface: '#fff'
|
||||
},
|
||||
text: {
|
||||
primary: '#25252D'
|
||||
},
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
body: 'Josefin Sans, sans-serif',
|
||||
display: 'Josefin Sans, sans-serif',
|
||||
},
|
||||
typography: {
|
||||
display1: {
|
||||
background:
|
||||
'linear-gradient(-30deg, var(--joy-palette-primary-900), var(--joy-palette-primary-400))',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
dark: {
|
||||
palette: {
|
||||
mode: 'light',
|
||||
primary: {
|
||||
...colors.purple,
|
||||
},
|
||||
neutral: {
|
||||
plainColor: '#D8D8DF',
|
||||
plainHoverColor: '#F7F7F8',
|
||||
plainHoverBg: '#25252D',
|
||||
plainActiveBg: '#434356',
|
||||
plainDisabledColor: '#434356'
|
||||
},
|
||||
text: {
|
||||
primary: '#EBEBEF'
|
||||
},
|
||||
background: {
|
||||
body: '#09090D',
|
||||
surface: '#1e293b40'
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
fontFamily: {
|
||||
body: 'Josefin Sans, sans-serif',
|
||||
display: 'Josefin Sans, sans-serif',
|
||||
},
|
||||
typography: {
|
||||
display1: {
|
||||
background:
|
||||
'linear-gradient(-30deg, var(--joy-palette-primary-900), var(--joy-palette-primary-400))',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
},
|
||||
},
|
||||
});
|
234
datacenter/components/chatBox.tsx
Normal file
234
datacenter/components/chatBox.tsx
Normal file
@ -0,0 +1,234 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import SendRoundedIcon from '@mui/icons-material/SendRounded';
|
||||
import Button from '@mui/joy/Button';
|
||||
import Card from '@mui/joy/Card';
|
||||
import CircularProgress from '@mui/joy/CircularProgress';
|
||||
import IconButton from '@mui/joy/IconButton';
|
||||
import Input from '@mui/joy/Input';
|
||||
import Stack from '@mui/joy/Stack';
|
||||
import React, { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { z } from 'zod';
|
||||
import { Message } from '@/types';
|
||||
|
||||
type Props = {
|
||||
messages: Message[];
|
||||
onSubmit: (message: string) => Promise<any>;
|
||||
messageTemplates?: string[];
|
||||
initialMessage?: string;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
const Schema = z.object({ query: z.string().min(1) });
|
||||
|
||||
const ChatBoxComp = ({
|
||||
messages,
|
||||
onSubmit,
|
||||
messageTemplates,
|
||||
initialMessage,
|
||||
readOnly,
|
||||
}: Props) => {
|
||||
const scrollableRef = React.useRef<HTMLDivElement>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [firstMsg, setFirstMsg] = useState<Message>();
|
||||
const [hideTemplateMessages, setHideTemplateMessages] = useState(false);
|
||||
console.log(messages, 'mmm');
|
||||
const methods = useForm<z.infer<typeof Schema>>({
|
||||
resolver: zodResolver(Schema),
|
||||
defaultValues: {},
|
||||
});
|
||||
|
||||
const submit = async ({ query }: z.infer<typeof Schema>) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setHideTemplateMessages(true);
|
||||
methods.reset();
|
||||
await onSubmit(query);
|
||||
} catch (err) {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!scrollableRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollableRef.current.scrollTo(0, scrollableRef.current.scrollHeight);
|
||||
}, [messages?.length]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setFirstMsg(
|
||||
initialMessage ? { from: 'agent', message: initialMessage } : undefined
|
||||
);
|
||||
}, 0);
|
||||
}, [initialMessage]);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction={'column'}
|
||||
gap={2}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
flexBasis: '100%',
|
||||
maxWidth: '700px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
maxHeight: '100%',
|
||||
minHeight: '100%',
|
||||
mx: 'auto',
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
ref={scrollableRef}
|
||||
direction={'column'}
|
||||
gap={2}
|
||||
sx={{
|
||||
boxSizing: 'border-box',
|
||||
maxWidth: '100%',
|
||||
width: '100%',
|
||||
mx: 'auto',
|
||||
flex: 1,
|
||||
maxHeight: '100%',
|
||||
overflowY: 'auto',
|
||||
p: 2,
|
||||
border: '1px solid',
|
||||
borderColor: 'var(--joy-palette-divider)'
|
||||
}}
|
||||
>
|
||||
{firstMsg && (
|
||||
<Card
|
||||
size="sm"
|
||||
variant={'outlined'}
|
||||
color={'primary'}
|
||||
className="message-agent"
|
||||
sx={{
|
||||
mr: 'auto',
|
||||
ml: 'none',
|
||||
whiteSpace: 'pre-wrap',
|
||||
}}
|
||||
>
|
||||
{firstMsg?.message}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{messages.map((each, index) => (
|
||||
<Stack
|
||||
key={index}
|
||||
sx={{
|
||||
mr: each.from === 'agent' ? 'auto' : 'none',
|
||||
ml: each.from === 'human' ? 'auto' : 'none',
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
size="sm"
|
||||
variant={'outlined'}
|
||||
className={
|
||||
each.from === 'agent' ? 'message-agent' : 'message-human'
|
||||
}
|
||||
color={each.from === 'agent' ? 'primary' : 'neutral'}
|
||||
sx={(theme) => ({
|
||||
px: 2,
|
||||
'ol, ul': {
|
||||
my: 0,
|
||||
pl: 2,
|
||||
},
|
||||
ol: {
|
||||
listStyle: 'numeric',
|
||||
},
|
||||
ul: {
|
||||
listStyle: 'disc',
|
||||
mb: 2,
|
||||
},
|
||||
li: {
|
||||
my: 1,
|
||||
},
|
||||
a: {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} linkTarget={'_blank'}>
|
||||
{each.message}
|
||||
</ReactMarkdown>
|
||||
</Card>
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
{isLoading && (
|
||||
<CircularProgress
|
||||
variant="soft"
|
||||
color="neutral"
|
||||
size="sm"
|
||||
sx={{ mx: 'auto', my: 2 }}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
{!readOnly && (
|
||||
<form
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
marginTop: 'auto',
|
||||
overflow: 'visible',
|
||||
background: 'none',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}
|
||||
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%' }}
|
||||
variant="outlined"
|
||||
endDecorator={
|
||||
<IconButton type="submit" disabled={isLoading}>
|
||||
<SendRoundedIcon />
|
||||
</IconButton>
|
||||
}
|
||||
{...methods.register('query')}
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatBoxComp;
|
@ -18,6 +18,7 @@ const Header = () => {
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 1100,
|
||||
background: 'var(--joy-palette-background-body)'
|
||||
}}
|
||||
>
|
||||
<div className='flex items-center justify-center gap-3'>
|
||||
|
175
datacenter/hooks/useAgentChat.ts
Normal file
175
datacenter/hooks/useAgentChat.ts
Normal file
@ -0,0 +1,175 @@
|
||||
import {
|
||||
EventStreamContentType,
|
||||
fetchEventSource,
|
||||
} from '@microsoft/fetch-event-source';
|
||||
import { ApiError, ApiErrorType } from '@/utils/api-error';
|
||||
import useStateReducer from './useStateReducer';
|
||||
import useVisitorId from './useVisitorId';
|
||||
|
||||
type Props = {
|
||||
queryAgentURL: string;
|
||||
queryHistoryURL?: string;
|
||||
channel?: "dashboard" | "website" | "slack" | "crisp";
|
||||
queryBody?: any;
|
||||
};
|
||||
|
||||
const useAgentChat = ({
|
||||
queryAgentURL,
|
||||
queryHistoryURL,
|
||||
channel,
|
||||
queryBody,
|
||||
}: Props) => {
|
||||
const [state, setState] = useStateReducer({
|
||||
history: [{
|
||||
from: 'human',
|
||||
message: 'hello',
|
||||
}, {
|
||||
from: 'agent',
|
||||
message: 'Hello! How can I assist you today?',
|
||||
}] as { from: 'human' | 'agent'; message: string; id?: string }[],
|
||||
});
|
||||
|
||||
const { visitorId } = useVisitorId();
|
||||
|
||||
const handleChatSubmit = async (message: string) => {
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
const history = [...state.history, { from: 'human', message }];
|
||||
const nextIndex = history.length;
|
||||
|
||||
setState({
|
||||
history: history as any,
|
||||
});
|
||||
|
||||
let answer = '';
|
||||
let error = '';
|
||||
|
||||
try {
|
||||
const ctrl = new AbortController();
|
||||
let buffer = '';
|
||||
|
||||
class RetriableError extends Error {}
|
||||
class FatalError extends Error {}
|
||||
|
||||
await fetchEventSource(queryAgentURL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...queryBody,
|
||||
streaming: true,
|
||||
query: message,
|
||||
visitorId: visitorId,
|
||||
channel,
|
||||
}),
|
||||
signal: ctrl.signal,
|
||||
|
||||
async onopen(response) {
|
||||
console.log('onopen', response);
|
||||
if (
|
||||
response.ok &&
|
||||
response.headers.get('content-type') === EventStreamContentType
|
||||
) {
|
||||
return; // everything's good
|
||||
} else if (
|
||||
response.status >= 400 &&
|
||||
response.status < 500 &&
|
||||
response.status !== 429
|
||||
) {
|
||||
if (response.status === 402) {
|
||||
throw new ApiError(ApiErrorType.USAGE_LIMIT);
|
||||
}
|
||||
throw new FatalError();
|
||||
} else {
|
||||
throw new RetriableError();
|
||||
}
|
||||
},
|
||||
onclose() {
|
||||
throw new RetriableError();
|
||||
},
|
||||
onerror(err) {
|
||||
console.log('on error', err, Object.keys(err));
|
||||
throw new Error(err);
|
||||
// if (err instanceof FatalError) {
|
||||
// ctrl.abort();
|
||||
// throw new Error(); // rethrow to stop the operation
|
||||
// } else if (err instanceof ApiError) {
|
||||
// console.log('ApiError', ApiError);
|
||||
// throw new Error();
|
||||
// } else {
|
||||
// throw new Error(err);
|
||||
// }
|
||||
},
|
||||
onmessage: (event) => {
|
||||
console.log(event, 'event');
|
||||
if (event.data === '[DONE]') {
|
||||
ctrl.abort();
|
||||
} else if (event.data?.startsWith('[ERROR]')) {
|
||||
ctrl.abort();
|
||||
|
||||
setState({
|
||||
history: [
|
||||
...history,
|
||||
{
|
||||
from: 'agent',
|
||||
message: event.data.replace('[ERROR]', ''),
|
||||
} as any,
|
||||
],
|
||||
});
|
||||
} else {
|
||||
buffer += decodeURIComponent(event.data) as string;
|
||||
const h = [...history];
|
||||
if (h?.[nextIndex]) {
|
||||
h[nextIndex].message = `${buffer}`;
|
||||
} else {
|
||||
h.push({ from: 'agent', message: buffer });
|
||||
}
|
||||
setState({
|
||||
history: h as any,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('err', err);
|
||||
setState({
|
||||
history: [
|
||||
...history,
|
||||
{ from: 'agent', message: answer || '请求出错' as string },
|
||||
] as any,
|
||||
});
|
||||
// if (err instanceof ApiError) {
|
||||
// if (err?.message) {
|
||||
// error = err?.message;
|
||||
|
||||
// if (error === ApiErrorType.USAGE_LIMIT) {
|
||||
// answer =
|
||||
// 'Usage limit reached. Please upgrade your plan to get higher usage.';
|
||||
// } else {
|
||||
// answer = `Error: ${error}`;
|
||||
// }
|
||||
// } else {
|
||||
// answer = `Error: ${error}`;
|
||||
// }
|
||||
|
||||
// setState({
|
||||
// history: [
|
||||
// ...history,
|
||||
// { from: 'agent', message: answer as string },
|
||||
// ] as any,
|
||||
// });
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleChatSubmit,
|
||||
history: state.history,
|
||||
};
|
||||
};
|
||||
|
||||
export default useAgentChat;
|
||||
|
17
datacenter/hooks/useStateReducer.ts
Normal file
17
datacenter/hooks/useStateReducer.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { useReducer } from 'react';
|
||||
|
||||
const useStateReducer = <T>(initialState: T) => {
|
||||
const methods = useReducer(
|
||||
(state: T, newState: Partial<T>) => ({
|
||||
...state,
|
||||
...newState,
|
||||
}),
|
||||
{
|
||||
...initialState,
|
||||
}
|
||||
);
|
||||
|
||||
return methods;
|
||||
};
|
||||
|
||||
export default useStateReducer;
|
28
datacenter/hooks/useVisitorId.ts
Normal file
28
datacenter/hooks/useVisitorId.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import cuid from 'cuid';
|
||||
|
||||
const useVisitorId = () => {
|
||||
const [visitorId, setVisitorId] = React.useState('');
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
let id = localStorage.getItem('visitorId');
|
||||
|
||||
if (!id) {
|
||||
id = cuid();
|
||||
localStorage.setItem('visitorId', id);
|
||||
}
|
||||
|
||||
setVisitorId(id);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
visitorId,
|
||||
};
|
||||
};
|
||||
|
||||
export default useVisitorId;
|
@ -1,4 +1,8 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {}
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
esmExternals: 'loose'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
|
4235
datacenter/package-lock.json
generated
4235
datacenter/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,23 +12,34 @@
|
||||
"@emotion/cache": "^11.10.5",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@hookform/resolvers": "^3.0.0",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@mui/icons-material": "^5.11.16",
|
||||
"@mui/joy": "5.0.0-alpha.72",
|
||||
"@mui/lab": "5.0.0-alpha.124",
|
||||
"@mui/material": "^5.11.14",
|
||||
"@mui/utils": "^5.11.13",
|
||||
"@prisma/client": "^4.12.0",
|
||||
"@types/node": "20.3.1",
|
||||
"@types/react": "18.2.14",
|
||||
"@types/react-dom": "18.2.6",
|
||||
"autoprefixer": "10.4.14",
|
||||
"axios": "^1.3.4",
|
||||
"bizcharts": "^4.1.22",
|
||||
"cuid": "^3.0.0",
|
||||
"eslint": "8.43.0",
|
||||
"eslint-config-next": "13.4.7",
|
||||
"next": "13.4.7",
|
||||
"next-auth": "^4.20.1",
|
||||
"postcss": "8.4.24",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.8",
|
||||
"react-markdown": "^8.0.7",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"swr": "^2.1.1",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "5.1.3"
|
||||
"typescript": "5.1.3",
|
||||
"zod": "^3.19.1"
|
||||
}
|
||||
}
|
||||
|
8
datacenter/types/index.ts
Normal file
8
datacenter/types/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { NextApiRequest, NextPage } from 'next/types';
|
||||
import { Session } from 'next-auth';
|
||||
|
||||
export type Message = { from: 'human' | 'agent'; message: string; createdAt?: Date };
|
||||
|
||||
export type AppNextApiRequest = NextApiRequest & {
|
||||
session: Session;
|
||||
};
|
40
datacenter/utils/api-error.ts
Normal file
40
datacenter/utils/api-error.ts
Normal file
@ -0,0 +1,40 @@
|
||||
export enum ApiErrorType {
|
||||
UNAUTHORIZED = 'UNAUTHORIZED',
|
||||
USAGE_LIMIT = 'USAGE_LIMIT',
|
||||
NOT_FOUND = 'NOT_FOUND',
|
||||
INVALID_REQUEST = 'INVALID_REQUEST',
|
||||
WEBPAGE_IS_SITEMAP = 'WEBPAGE_IS_SITEMAP',
|
||||
EMPTY_DATASOURCE = 'EMPTY_DATASOURCE',
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(message: ApiErrorType, public status?: number) {
|
||||
super(message);
|
||||
|
||||
if (!status) {
|
||||
switch (message) {
|
||||
case ApiErrorType.UNAUTHORIZED:
|
||||
this.status = 403;
|
||||
break;
|
||||
case ApiErrorType.USAGE_LIMIT:
|
||||
this.status = 402;
|
||||
break;
|
||||
case ApiErrorType.NOT_FOUND:
|
||||
this.status = 404;
|
||||
break;
|
||||
case ApiErrorType.INVALID_REQUEST:
|
||||
this.status = 400;
|
||||
break;
|
||||
case ApiErrorType.EMPTY_DATASOURCE:
|
||||
this.status = 400;
|
||||
break;
|
||||
default:
|
||||
this.status = 500;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Object.setPrototypeOf(this, ApiError.prototype);
|
||||
}
|
||||
}
|
||||
|
19
datacenter/utils/swr-fetcher.ts
Normal file
19
datacenter/utils/swr-fetcher.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
|
||||
export const postFetcher = <T>(uri: string, { arg }: { arg: T }) =>
|
||||
axios(uri, {
|
||||
method: 'POST',
|
||||
data: arg,
|
||||
}).then((r) => r.data);
|
||||
|
||||
export const createFetcher =
|
||||
(config: AxiosRequestConfig) =>
|
||||
<T>(url: string, { arg }: { arg: T }) =>
|
||||
axios({
|
||||
url,
|
||||
...config,
|
||||
data: arg,
|
||||
}).then((r) => r.data);
|
||||
|
||||
export const fetcher = (...args: Parameters<typeof axios>) =>
|
||||
axios(...args).then((r) => r.data);
|
Loading…
Reference in New Issue
Block a user