Merge remote-tracking branch 'origin/new-page-framework' into llm_framework

This commit is contained in:
aries_ckt 2023-06-25 19:14:29 +08:00
commit a6190a061c
28 changed files with 15789 additions and 0 deletions

View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

35
datacenter/.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

34
datacenter/README.md Normal file
View File

@ -0,0 +1,34 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@ -0,0 +1,13 @@
import { Box } from '@/lib/mui';
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<>
{children}
</>
)
}

View File

@ -0,0 +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'
}];
return (
<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;

View File

@ -0,0 +1,201 @@
'use client'
import type { ProFormInstance } from '@ant-design/pro-components';
import React, { useState, useRef } from 'react'
import {
ProCard,
ProForm,
ProFormCheckbox,
ProFormDatePicker,
ProFormDateRangePicker,
ProFormSelect,
ProFormText,
ProFormTextArea,
StepsForm
} from '@ant-design/pro-components'
import { Button, Modal, message } from 'antd'
const Index = () => {
const formRef = useRef<ProFormInstance>();
const [isAddKnowledgeSpaceModalShow, setIsAddKnowledgeSpaceModalShow] =
useState<boolean>(false);
const [knowledgeSpaceName, setKnowledgeSpaceName] = useState<string>('');
const [webUrlName, setWebUrlName] = useState<string>('');
const [webPageUrl, setWebPageUrl] = useState<string>('');
return (
<>
<div className="header">
<div>Knowledge Spaces</div>
<Button onClick={() => setIsAddKnowledgeSpaceModalShow(true)} type='primary'>
+ New Knowledge Space
</Button>
</div>
<Modal
title="Add Knowledge Space"
footer={null}
width={900}
open={isAddKnowledgeSpaceModalShow}
onOk={() => console.log('ok')}
onCancel={() => setIsAddKnowledgeSpaceModalShow(false)}
>
<ProCard>
<StepsForm<{
name: string
}>
formRef={formRef}
onFinish={async () => {
message.success('success')
}}
formProps={{
validateMessages: {
required: 'This is required'
}
}}
submitter={{
render: (props) => {
if (props.step === 0) {
return (
<Button type="primary" onClick={async () => {
if (knowledgeSpaceName === '') {
props.onSubmit?.()
} else {
props.onSubmit?.();
const res = await fetch('/knowledge/space/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: knowledgeSpaceName,
vector_type: "Chroma",
owner: "keting",
desc: "test1"
}),
});
const data = await res.json();
if (data.success) {
props.onSubmit?.();
message.success('success');
} else {
message.error(data.err_msg || 'failed');
}
}
}}>
Next {'>'}
</Button>
);
} else if (props.step === 1) {
return (
<Button type="primary" onClick={() => props.onSubmit?.()}>
Web Page {'>'}
</Button>
);
}
return [
<Button key="gotoTwo" onClick={() => props.onPre?.()}>
Previous {'<'}
</Button>,
<Button
type="primary"
key="goToTree"
onClick={async () => {
props.onSubmit?.();
const res = await fetch(`/knowledge/${knowledgeSpaceName}/document/add`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
doc_name: webUrlName,
doc_type: webPageUrl
}),
});
const data = await res.json();
if (data.success) {
props.onSubmit?.();
message.success('success');
} else {
message.error(data.err_msg || 'failed');
}
}}
>
Finish
</Button>,
];
},
}}
>
<StepsForm.StepForm<{
name: string
}>
name="base"
title="Knowledge Space Configuration"
onFinish={async () => {
console.log(formRef.current?.getFieldsValue())
return true
}}
>
<ProFormText
name="name"
label="Knowledge Space Name"
width="lg"
placeholder="Please input the name"
rules={[{ required: true }]}
onChange={(e: any) => setKnowledgeSpaceName(e.target.value)}
/>
</StepsForm.StepForm>
<StepsForm.StepForm<{
checkbox: string
}>
name="checkbox"
title="Choose a Datasource type"
onFinish={async () => {
console.log(formRef.current?.getFieldsValue())
return true
}}
>
</StepsForm.StepForm>
<StepsForm.StepForm
name="time"
title="Setup the Datasource"
>
<ProFormText
name="webUrlName"
label="Name"
width="lg"
placeholder="Please input the name"
rules={[{ required: true }]}
onChange={(e: any) => setWebUrlName(e.target.value)}
/>
<ProFormText
name="webPageUrl"
label="Web Page URL"
width="lg"
placeholder="Please input the name"
rules={[{ required: true }]}
onChange={(e: any) => setWebPageUrl(e.target.value)}
/>
</StepsForm.StepForm>
</StepsForm>
</ProCard>
</Modal>
<style jsx>{`
.header {
display: flex;
justify-content: space-between;
}
.datasource-type-wrap {
height: 100px;
line-height: 100px;
border: 1px solid black;
border-radius: 20px;
margin-bottom: 20px;
cursor: pointer;
}
`}</style>
</>
)
}
export default Index

View File

@ -0,0 +1,63 @@
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',
surface: '#fff'
},
text: {
primary: '#25252D'
},
},
},
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',
},
},
});

BIN
datacenter/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -0,0 +1,12 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
color: var(--joy-palette-text-primary, var(--joy-palette-neutral-800, #25252D));
font-family: var(--joy-fontFamily-body, var(--joy-Josefin Sans, sans-serif));
font-size: var(--joy-fontSize-md, 1rem);
line-height: var(--joy-lineHeight-md, 1.5);
background-color: var(--joy-palette-background-body);
}

30
datacenter/app/layout.tsx Normal file
View File

@ -0,0 +1,30 @@
"use client"
import './globals.css'
import Header from '@/components/header';
import LeftSider from '@/components/leftSider';
import { CssVarsProvider, ThemeProvider } from '@mui/joy/styles';
import { joyTheme } from './defaultTheme';
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className="min-h-full font-sans">
<body className={`min-h-screen font-sans`}>
<ThemeProvider theme={joyTheme}>
<CssVarsProvider theme={joyTheme} defaultMode="light">
<div className='min-h-screen flex flex-col'>
<Header />
<div className="flex flex-1 flex-row">
<LeftSider />
<div className='flex-1'>{children}</div>
</div>
</div>
</CssVarsProvider>
</ThemeProvider>
</body>
</html>
)
}

9
datacenter/app/page.tsx Normal file
View File

@ -0,0 +1,9 @@
import Image from 'next/image'
export default function Home() {
return (
<div>
Hello GPT
</div>
)
}

View 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;

View File

@ -0,0 +1,32 @@
import DarkModeRoundedIcon from '@mui/icons-material/DarkModeRounded';
import LightModeRoundedIcon from '@mui/icons-material/LightModeRounded';
import { IconButton,useColorScheme } from '@/lib/mui';
import React from 'react';
export default function ColorSchemeToggle() {
const { mode, setMode } = useColorScheme();
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return <IconButton size="sm" variant="outlined" color="primary" />;
}
return (
<IconButton
id="toggle-mode"
size="sm"
variant="outlined"
color="primary"
onClick={() => {
if (mode === 'light') {
setMode('dark');
} else {
setMode('light');
}
}}
>
{mode === 'light' ? <DarkModeRoundedIcon /> : <LightModeRoundedIcon />}
</IconButton>
);
}

View File

@ -0,0 +1,43 @@
import { Box, Typography } from '@/lib/mui';
import Image from 'next/image';
import ColorSchemeToggle from './colorSchemeToggle';
const Header = () => {
return (
<Box
sx={{
p: 2,
gap: 2,
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
gridColumn: '1 / -1',
borderBottom: '1px solid',
borderColor: 'divider',
position: 'sticky',
top: 0,
zIndex: 1100,
background: 'var(--joy-palette-background-body)'
}}
>
<div className='flex items-center justify-center gap-3'>
<Image
src="/databerry-logo-icon.png"
width="200"
height="200"
className='w-12'
alt="Databerry"
/>
<Typography component="h1" fontWeight="xl">
DB-GPT
</Typography>
</div>
<div>
<ColorSchemeToggle />
</div>
</Box>
)
};
export default Header;

View File

@ -0,0 +1,76 @@
"use client";
import React, { useMemo } from 'react';
import { usePathname } from 'next/navigation';
import Link from 'next/link';
import { Box, List, ListItem, ListItemButton, ListItemDecorator, ListItemContent } from '@/lib/mui';
import SmartToyRoundedIcon from '@mui/icons-material/SmartToyRounded'; // Icons import
import StorageRoundedIcon from '@mui/icons-material/StorageRounded';
const LeftSider = () => {
const pathname = usePathname();
console.log(pathname, 'router')
const menus = useMemo(() => {
return [{
label: 'Agents',
icon: <SmartToyRoundedIcon fontSize="small" />,
route: '/agents',
active: pathname === '/agents',
}, {
label: 'Datastores',
route: '/datastores',
icon: <StorageRoundedIcon fontSize="small" />,
active: pathname === '/datastores'
}];
}, [pathname]);
return (
<Box
sx={[
{
p: 2,
borderRight: '1px solid',
borderColor: 'divider',
display: {
xs: 'none',
sm: 'initial',
},
},
]}
>
<List size="sm" sx={{ '--ListItem-radius': '8px' }}>
<ListItem nested>
<List
size="sm"
aria-labelledby="nav-list-browse"
sx={{
'& .JoyListItemButton-root': { p: '8px' },
}}
>
{menus.map((each) => (
<Link key={each.route} href={each.route}>
<ListItem>
<ListItemButton
selected={each.active}
variant={each.active ? 'soft' : 'plain'}
>
<ListItemDecorator
sx={{
color: each.active ? 'inherit' : 'neutral.500',
'--ListItemDecorator-size': '26px'
}}
>
{each.icon}
</ListItemDecorator>
<ListItemContent>{each.label}</ListItemContent>
</ListItemButton>
</ListItem>
</Link>
))}
</List>
</ListItem>
</List>
</Box>
)
};
export default LeftSider;

View 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;

View 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;

View 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;

View File

@ -0,0 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
esmExternals: 'loose'
}
}
module.exports = nextConfig

14396
datacenter/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

47
datacenter/package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "datacenter",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@ant-design/pro-components": "^2.6.2",
"@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",
"antd": "^5.6.2",
"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",
"zod": "^3.19.1"
}
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@ -0,0 +1,18 @@
const defaultTheme = require('tailwindcss/defaultTheme');
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
fontFamily: {
sans: ['"Josefin Sans"', ...defaultTheme.fontFamily.sans],
}
},
},
plugins: [],
}

28
datacenter/tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

View 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;
};

View 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);
}
}

View 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);