mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-08-12 05:32:32 +00:00
Merge remote-tracking branch 'origin/new-page-framework' into llm_framework
This commit is contained in:
commit
a6190a061c
3
datacenter/.eslintrc.json
Normal file
3
datacenter/.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
35
datacenter/.gitignore
vendored
Normal file
35
datacenter/.gitignore
vendored
Normal 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
34
datacenter/README.md
Normal 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.
|
13
datacenter/app/agents/layout.tsx
Normal file
13
datacenter/app/agents/layout.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Box } from '@/lib/mui';
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
214
datacenter/app/agents/page.tsx
Normal file
214
datacenter/app/agents/page.tsx
Normal 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;
|
201
datacenter/app/datastores/page.tsx
Normal file
201
datacenter/app/datastores/page.tsx
Normal 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
|
63
datacenter/app/defaultTheme.ts
Normal file
63
datacenter/app/defaultTheme.ts
Normal 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
BIN
datacenter/app/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
12
datacenter/app/globals.css
Normal file
12
datacenter/app/globals.css
Normal 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
30
datacenter/app/layout.tsx
Normal 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
9
datacenter/app/page.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Image from 'next/image'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Hello GPT
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
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;
|
32
datacenter/components/colorSchemeToggle.tsx
Normal file
32
datacenter/components/colorSchemeToggle.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
43
datacenter/components/header.tsx
Normal file
43
datacenter/components/header.tsx
Normal 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;
|
76
datacenter/components/leftSider.tsx
Normal file
76
datacenter/components/leftSider.tsx
Normal 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;
|
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;
|
8
datacenter/next.config.js
Normal file
8
datacenter/next.config.js
Normal 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
14396
datacenter/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
datacenter/package.json
Normal file
47
datacenter/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
6
datacenter/postcss.config.js
Normal file
6
datacenter/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
BIN
datacenter/public/databerry-logo-icon.png
Normal file
BIN
datacenter/public/databerry-logo-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 115 KiB |
18
datacenter/tailwind.config.js
Normal file
18
datacenter/tailwind.config.js
Normal 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
28
datacenter/tsconfig.json
Normal 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"]
|
||||||
|
}
|
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