Merge branch 'new-page-framework' into dev_ty_06_end

This commit is contained in:
tuyang.yhj 2023-06-29 09:59:18 +08:00
commit 1acac4045f
21 changed files with 756 additions and 559 deletions

View File

@ -1,3 +1,8 @@
{ {
"extends": "next/core-web-vitals" "extends": "next/core-web-vitals",
"rules": {
"indent": ["warning", 2, {
"SwitchCase": 1
}]
}
} }

View File

@ -0,0 +1,25 @@
"use client"
import { useRequest } from 'ahooks';
import { sendGetRequest } from '@/utils/request';
import useAgentChat from '@/hooks/useAgentChat';
import ChatBoxComp from '@/components/chatBox';
const AgentPage = (props) => {
const { data: historyList } = useRequest(async () => await sendGetRequest('/v1/chat/dialogue/messages/history', {
con_uid: props.params?.agentId
}), {
ready: !!props.params?.agentId
});
const { handleChatSubmit, history } = useAgentChat({
queryAgentURL: `/v1/chat/completions`,
queryBody: {}
});
return (
<div className='mx-auto flex h-full max-w-3xl flex-col gap-6 px-5 pt-6 sm:gap-8 xl:max-w-4xl'>
<ChatBoxComp messages={historyList?.data || []} onSubmit={handleChatSubmit}/>
</div>
)
}
export default AgentPage;

View File

@ -203,7 +203,7 @@ const Agents = () => {
</Stack> </Stack>
</Grid> </Grid>
<Grid xs={4}> <Grid xs={4}>
<ChatBoxComp messages={history} onSubmit={handleChatSubmit}/> <ChatBoxComp messages={[]} onSubmit={handleChatSubmit}/>
</Grid> </Grid>
</Grid> </Grid>
</div> </div>

View File

@ -32,7 +32,7 @@ const ChunkList = () => {
}, []) }, [])
return ( return (
<div className="p-4"> <div className="p-4">
<Table sx={{ '& thead th:nth-child(1)': { width: '40%' } }}> <Table color="neutral" stripe="odd" variant="outlined">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>

View File

@ -10,11 +10,25 @@ import {
Box, Box,
Stack, Stack,
Input, Input,
Chip,
styled styled
} from '@/lib/mui' } from '@/lib/mui'
import moment from 'moment' import moment from 'moment'
import { message } from 'antd' import { InboxOutlined } from '@ant-design/icons'
import type { UploadProps } from 'antd'
import { Upload, message } from 'antd'
const { Dragger } = Upload
const Item = styled(Sheet)(({ theme }) => ({
width: '50%',
backgroundColor:
theme.palette.mode === 'dark' ? theme.palette.background.level1 : '#fff',
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
borderRadius: 4,
color: theme.vars.palette.text.secondary
}))
const stepsOfAddingDocument = [ const stepsOfAddingDocument = [
'Choose a Datasource type', 'Choose a Datasource type',
'Setup the Datasource' 'Setup the Datasource'
@ -36,16 +50,6 @@ const documentTypeList = [
subTitle: 'It can be: PDF, CSV, JSON, Text, PowerPoint, Word, Excel' subTitle: 'It can be: PDF, CSV, JSON, Text, PowerPoint, Word, Excel'
} }
] ]
const Item = styled(Sheet)(({ theme }) => ({
width: '50%',
backgroundColor:
theme.palette.mode === 'dark' ? theme.palette.background.level1 : '#fff',
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
borderRadius: 4,
color: theme.vars.palette.text.secondary
}))
const Documents = () => { const Documents = () => {
const router = useRouter() const router = useRouter()
@ -53,9 +57,25 @@ const Documents = () => {
const [isAddDocumentModalShow, setIsAddDocumentModalShow] = const [isAddDocumentModalShow, setIsAddDocumentModalShow] =
useState<boolean>(false) useState<boolean>(false)
const [activeStep, setActiveStep] = useState<number>(0) const [activeStep, setActiveStep] = useState<number>(0)
const [documentType, setDocumentType] = useState<string>('')
const [documents, setDocuments] = useState<any>([]) const [documents, setDocuments] = useState<any>([])
const [webPageUrl, setWebPageUrl] = useState<string>('') const [webPageUrl, setWebPageUrl] = useState<string>('')
const [documentName, setDocumentName] = useState<string>('') const [documentName, setDocumentName] = useState<any>('')
const [originFileObj, setOriginFileObj] = useState<any>(null)
const props: UploadProps = {
name: 'file',
multiple: false,
onChange(info) {
console.log(info)
if (info.fileList.length === 0) {
setOriginFileObj(null)
setDocumentName('')
return
}
setOriginFileObj(info.file.originFileObj)
setDocumentName(info.file.originFileObj?.name)
}
}
useEffect(() => { useEffect(() => {
async function fetchDocuments() { async function fetchDocuments() {
const res = await fetch( const res = await fetch(
@ -90,7 +110,7 @@ const Documents = () => {
+ Add Datasource + Add Datasource
</Button> </Button>
</Sheet> </Sheet>
<Table sx={{ '& thead th:nth-child(1)': { width: '40%' } }}> <Table color="neutral" stripe="odd" variant="outlined">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
@ -108,12 +128,30 @@ const Documents = () => {
<td>{row.doc_type}</td> <td>{row.doc_type}</td>
<td>{row.chunk_size}</td> <td>{row.chunk_size}</td>
<td>{moment(row.last_sync).format('YYYY-MM-DD HH:MM:SS')}</td> <td>{moment(row.last_sync).format('YYYY-MM-DD HH:MM:SS')}</td>
<td>{row.status}</td> <td>
<Chip
color={(function () {
switch (row.status) {
case 'TODO':
return 'neutral'
case 'RUNNING':
return 'primary'
case 'FINISHED':
return 'success'
case 'FAILED':
return 'danger'
}
})()}
>
{row.status}
</Chip>
</td>
<td> <td>
{ {
<> <>
<Button <Button
variant="outlined" variant="outlined"
size="sm"
onClick={async () => { onClick={async () => {
const res = await fetch( const res = await fetch(
`http://localhost:8000/knowledge/${spaceName}/document/sync`, `http://localhost:8000/knowledge/${spaceName}/document/sync`,
@ -139,6 +177,7 @@ const Documents = () => {
</Button> </Button>
<Button <Button
variant="outlined" variant="outlined"
size="sm"
onClick={() => { onClick={() => {
router.push( router.push(
`/datastores/documents/chunklist?spacename=${spaceName}&documentid=${row.id}` `/datastores/documents/chunklist?spacename=${spaceName}&documentid=${row.id}`
@ -204,9 +243,8 @@ const Documents = () => {
cursor: 'pointer' cursor: 'pointer'
}} }}
onClick={() => { onClick={() => {
if (item.type === 'webPage') { setDocumentType(item.type)
setActiveStep(1) setActiveStep(1)
}
}} }}
> >
<Sheet sx={{ fontSize: '20px', fontWeight: 'bold' }}> <Sheet sx={{ fontSize: '20px', fontWeight: 'bold' }}>
@ -227,10 +265,36 @@ const Documents = () => {
sx={{ marginBottom: '20px' }} sx={{ marginBottom: '20px' }}
/> />
Web Page URL: Web Page URL:
<Input {documentType === 'webPage' ? (
placeholder="Please input the Web Page URL" <>
onChange={(e: any) => setWebPageUrl(e.target.value)} Web Page URL:
/> <Input
placeholder="Please input the Web Page URL"
onChange={(e: any) => setWebPageUrl(e.target.value)}
/>
</>
) : documentType === 'file' ? (
<>
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p
style={{ color: 'rgb(22, 108, 255)', fontSize: '20px' }}
>
Select or Drop file
</p>
<p
className="ant-upload-hint"
style={{ color: 'rgb(22, 108, 255)' }}
>
PDF, PowerPoint, Excel, Word, Text, Markdown,
</p>
</Dragger>
</>
) : (
<></>
)}
</Box> </Box>
<Button <Button
onClick={async () => { onClick={async () => {
@ -238,44 +302,83 @@ const Documents = () => {
message.error('Please input the name') message.error('Please input the name')
return return
} }
if (webPageUrl === '') { if (documentType === 'webPage') {
message.error('Please input the Web Page URL') if (webPageUrl === '') {
return message.error('Please input the Web Page URL')
} return
const res = await fetch(
`http://localhost:8000/knowledge/${spaceName}/document/add`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
doc_name: documentName,
content: webPageUrl,
doc_type: 'URL'
})
} }
)
const data = await res.json()
if (data.success) {
message.success('success')
setIsAddDocumentModalShow(false)
const res = await fetch( const res = await fetch(
`http://localhost:8000/knowledge/${spaceName}/document/list`, `http://localhost:8000/knowledge/${spaceName}/document/add`,
{ {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({}) body: JSON.stringify({
doc_name: documentName,
content: webPageUrl,
doc_type: 'URL'
})
} }
) )
const data = await res.json() const data = await res.json()
if (data.success) { if (data.success) {
setDocuments(data.data) message.success('success')
setIsAddDocumentModalShow(false)
const res = await fetch(
`http://localhost:8000/knowledge/${spaceName}/document/list`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
}
)
const data = await res.json()
if (data.success) {
setDocuments(data.data)
}
} else {
message.error(data.err_msg || 'failed')
}
} else if (documentType === 'file') {
if (!originFileObj) {
message.error('Please select a file')
return
}
const formData = new FormData();
formData.append('doc_name', documentName);
formData.append('doc_file', originFileObj);
formData.append('doc_type', 'DOCUMENT');
const res = await fetch(
`http://localhost:8000/knowledge/${spaceName}/document/upload`,
{
method: 'POST',
body: formData
}
)
const data = await res.json()
if (data.success) {
message.success('success')
setIsAddDocumentModalShow(false)
const res = await fetch(
`http://localhost:8000/knowledge/${spaceName}/document/list`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
}
)
const data = await res.json()
if (data.success) {
setDocuments(data.data)
}
} else {
message.error(data.err_msg || 'failed')
} }
} else {
message.error(data.err_msg || 'failed')
} }
}} }}
> >

View File

@ -2,7 +2,9 @@
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { message } from 'antd' import { InboxOutlined } from '@ant-design/icons'
import type { UploadProps } from 'antd'
import { message, Upload } from 'antd'
import { import {
Modal, Modal,
Button, Button,
@ -14,6 +16,8 @@ import {
styled styled
} from '@/lib/mui' } from '@/lib/mui'
const { Dragger } = Upload
const Item = styled(Sheet)(({ theme }) => ({ const Item = styled(Sheet)(({ theme }) => ({
width: '33%', width: '33%',
backgroundColor: backgroundColor:
@ -51,12 +55,28 @@ const documentTypeList = [
const Index = () => { const Index = () => {
const router = useRouter() const router = useRouter()
const [activeStep, setActiveStep] = useState<number>(0) const [activeStep, setActiveStep] = useState<number>(0)
const [documentType, setDocumentType] = useState<string>('')
const [knowledgeSpaceList, setKnowledgeSpaceList] = useState<any>([]) const [knowledgeSpaceList, setKnowledgeSpaceList] = useState<any>([])
const [isAddKnowledgeSpaceModalShow, setIsAddKnowledgeSpaceModalShow] = const [isAddKnowledgeSpaceModalShow, setIsAddKnowledgeSpaceModalShow] =
useState<boolean>(false) useState<boolean>(false)
const [knowledgeSpaceName, setKnowledgeSpaceName] = useState<string>('') const [knowledgeSpaceName, setKnowledgeSpaceName] = useState<string>('')
const [webPageUrl, setWebPageUrl] = useState<string>('') const [webPageUrl, setWebPageUrl] = useState<string>('')
const [documentName, setDocumentName] = useState<string>('') const [documentName, setDocumentName] = useState<any>('')
const [originFileObj, setOriginFileObj] = useState<any>(null)
const props: UploadProps = {
name: 'file',
multiple: false,
onChange(info) {
console.log(info)
if (info.fileList.length === 0) {
setOriginFileObj(null)
setDocumentName('')
return
}
setOriginFileObj(info.file.originFileObj)
setDocumentName(info.file.originFileObj?.name)
}
}
useEffect(() => { useEffect(() => {
async function fetchData() { async function fetchData() {
const res = await fetch('http://localhost:8000/knowledge/space/list', { const res = await fetch('http://localhost:8000/knowledge/space/list', {
@ -75,14 +95,21 @@ const Index = () => {
}, []) }, [])
return ( return (
<> <>
<Sheet sx={{ <Sheet
display: "flex", sx={{
justifyContent: "space-between" display: 'flex',
}} className="p-4"> justifyContent: 'space-between'
<Sheet sx={{ }}
fontSize: '30px', className="p-4"
fontWeight: 'bold' >
}}>Knowledge Spaces</Sheet> <Sheet
sx={{
fontSize: '30px',
fontWeight: 'bold'
}}
>
Knowledge Spaces
</Sheet>
<Button <Button
onClick={() => setIsAddKnowledgeSpaceModalShow(true)} onClick={() => setIsAddKnowledgeSpaceModalShow(true)}
variant="outlined" variant="outlined"
@ -91,7 +118,7 @@ const Index = () => {
</Button> </Button>
</Sheet> </Sheet>
<div className="page-body p-4"> <div className="page-body p-4">
<Table sx={{ '& thead th:nth-child(1)': { width: '40%' } }}> <Table color="neutral" stripe="odd" variant="outlined">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
@ -218,7 +245,7 @@ const Index = () => {
sx={{ sx={{
boxSizing: 'border-box', boxSizing: 'border-box',
height: '80px', height: '80px',
padding: '12px', padding: '12px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'space-between', justifyContent: 'space-between',
@ -228,12 +255,13 @@ const Index = () => {
cursor: 'pointer' cursor: 'pointer'
}} }}
onClick={() => { onClick={() => {
if (item.type === 'webPage') { setDocumentType(item.type)
setActiveStep(2); setActiveStep(2)
}
}} }}
> >
<Sheet sx={{ fontSize: '20px', fontWeight: 'bold' }}>{item.title}</Sheet> <Sheet sx={{ fontSize: '20px', fontWeight: 'bold' }}>
{item.title}
</Sheet>
<Sheet>{item.subTitle}</Sheet> <Sheet>{item.subTitle}</Sheet>
</Sheet> </Sheet>
))} ))}
@ -246,44 +274,94 @@ const Index = () => {
<Input <Input
placeholder="Please input the name" placeholder="Please input the name"
onChange={(e: any) => setDocumentName(e.target.value)} onChange={(e: any) => setDocumentName(e.target.value)}
sx={{ marginBottom: '20px'}} sx={{ marginBottom: '20px' }}
/>
Web Page URL:
<Input
placeholder="Please input the Web Page URL"
onChange={(e: any) => setWebPageUrl(e.target.value)}
/> />
{documentType === 'webPage' ? (
<>
Web Page URL:
<Input
placeholder="Please input the Web Page URL"
onChange={(e: any) => setWebPageUrl(e.target.value)}
/>
</>
) : documentType === 'file' ? (
<>
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p
style={{ color: 'rgb(22, 108, 255)', fontSize: '20px' }}
>
Select or Drop file
</p>
<p
className="ant-upload-hint"
style={{ color: 'rgb(22, 108, 255)' }}
>
PDF, PowerPoint, Excel, Word, Text, Markdown,
</p>
</Dragger>
</>
) : (
<></>
)}
</Box> </Box>
<Button <Button
onClick={async () => { onClick={async () => {
if (documentName === '') { if (documentName === '') {
message.error('Please input the name'); message.error('Please input the name')
return; return
} }
if (webPageUrl === '') { if (documentType === 'webPage') {
message.error('Please input the Web Page URL'); if (webPageUrl === '') {
return; message.error('Please input the Web Page URL')
} return
const res = await fetch( }
`http://localhost:8000/knowledge/${knowledgeSpaceName}/document/add`, const res = await fetch(
{ `http://localhost:8000/knowledge/${knowledgeSpaceName}/document/add`,
method: 'POST', {
headers: { method: 'POST',
'Content-Type': 'application/json' headers: {
}, 'Content-Type': 'application/json'
body: JSON.stringify({ },
doc_name: documentName, body: JSON.stringify({
content: webPageUrl, doc_name: documentName,
doc_type: 'URL' content: webPageUrl,
}) doc_type: 'URL'
})
}
)
const data = await res.json()
if (data.success) {
message.success('success')
setIsAddKnowledgeSpaceModalShow(false)
} else {
message.error(data.err_msg || 'failed')
}
} else if (documentType === 'file') {
if (!originFileObj) {
message.error('Please select a file')
return
}
const formData = new FormData();
formData.append('doc_name', documentName);
formData.append('doc_file', originFileObj);
formData.append('doc_type', 'DOCUMENT');
const res = await fetch(
`http://localhost:8000/knowledge/${knowledgeSpaceName}/document/upload`,
{
method: 'POST',
body: formData
}
)
const data = await res.json()
if (data.success) {
message.success('success')
setIsAddKnowledgeSpaceModalShow(false)
} else {
message.error(data.err_msg || 'failed')
} }
)
const data = await res.json()
if (data.success) {
message.success('success')
setIsAddKnowledgeSpaceModalShow(false)
} else {
message.error(data.err_msg || 'failed')
} }
}} }}
> >

View File

@ -10,7 +10,7 @@ export const joyTheme = extendTheme({
...colors.purple, ...colors.purple,
}, },
neutral: { neutral: {
plainColor: '#25252D', plainColor: '#4d4d4d',
plainHoverColor: '#131318', plainHoverColor: '#131318',
plainHoverBg: '#EBEBEF', plainHoverBg: '#EBEBEF',
plainActiveBg: '#D8D8DF', plainActiveBg: '#D8D8DF',
@ -42,7 +42,7 @@ export const joyTheme = extendTheme({
primary: '#EBEBEF' primary: '#EBEBEF'
}, },
background: { background: {
body: '#09090D', body: '#0f172a',
surface: '#1e293b40' surface: '#1e293b40'
} }
}, },

View File

@ -2,9 +2,6 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
a {
color: #1677ff;
}
body { body {
margin: 0; margin: 0;

View File

@ -10,20 +10,21 @@ export default function RootLayout({
children, children,
}: { }: {
children: React.ReactNode children: React.ReactNode
}) { }) {
return ( return (
<html lang="en" className="min-h-full font-sans"> <html lang="en" className="h-full font-sans">
<body className={`min-h-screen font-sans`}> <body className={`h-full font-sans`}>
<ThemeProvider theme={joyTheme}> <ThemeProvider theme={joyTheme}>
<CssVarsProvider theme={joyTheme} defaultMode="light"> <CssVarsProvider theme={joyTheme} defaultMode="light">
<TopProgressBar /> <TopProgressBar />
<div className='min-h-screen flex flex-col'> <div className={`contents h-full`}>
<div className="flex flex-1 flex-row"> <div className="grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd dark:text-gray-300 md:grid-cols-[280px,1fr] md:grid-rows-[1fr]">
<LeftSider /> <LeftSider />
<div className='flex-1 overflow-auto'>{children}</div> <div className='relative min-h-0 min-w-0'>
{children}
</div>
</div>
</div> </div>
</div>
</CssVarsProvider> </CssVarsProvider>
</ThemeProvider> </ThemeProvider>
</body> </body>

View File

@ -1,267 +1,98 @@
"use client"; "use client";
import { useState } from 'react';
import ReactMarkdown from 'react-markdown'; import { Button, Input, useColorScheme } from '@/lib/mui';
import { Collapse } from 'antd'; import IconButton from '@mui/joy/IconButton';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import SendRoundedIcon from '@mui/icons-material/SendRounded';
import { Box, Slider, Input, Tabs, TabList, Typography, TabPanel, Tab, RadioGroup, Radio, tabClasses, useColorScheme, radioClasses } from '@/lib/mui'; import { zodResolver } from '@hookform/resolvers/zod';
import { sendGetRequest } from '@/utils/request'; import { useForm } from 'react-hook-form';
import { useEffect, useState } from 'react'; import { z } from 'zod';
import ChatBoxComp from '@/components/chatBox'; import { sendPostRequest } from '@/utils/request';
import useAgentChat from '@/hooks/useAgentChat'; import { useRouter } from 'next/navigation';
import { useQueryDialog } from '@/hooks/useQueryDialogue';
export default function Home() { export default function Home() {
const [temperatureNum, setTemperatureNum] = useState(3); const Schema = z.object({ query: z.string().min(1) });
const [tokenSize, setTokenSize] = useState(0); const router = useRouter();
const { mode, setMode } = useColorScheme(); const { mode } = useColorScheme();
const [isLoading, setIsLoading] = useState(false);
const { handleChatSubmit, history } = useAgentChat({ const methods = useForm<z.infer<typeof Schema>>({
queryAgentURL: `/api/agents/query`, resolver: zodResolver(Schema),
defaultValues: {},
}); });
const { refreshDialogList } = useQueryDialog();
const submit = async ({ query }: z.infer<typeof Schema>) => {
try {
setIsLoading(true);
methods.reset();
const res = await sendPostRequest('/v1/chat/dialogue/new', {
chat_mode: 'chat_normal'
});
if (res?.success && res?.data?.conv_uid) {
// router.push(`/agents/${res?.data?.conv_uid}?newMessage=${query}`);
// await refreshDialogList();
}
} catch (err) {
} finally {
setIsLoading(false);
}
};
const handleGetD = () => {
sendGetRequest('/v1/chat/dialogue/list', {
})
}
useEffect(() => {
handleGetD();
}, []);
return ( return (
<div className='p-6 w-full h-full text-sm flex flex-col gap-4'> <>
<ReactMarkdown linkTarget={'_blank'}> <div className={`${mode} absolute z-20 top-0 inset-x-0 flex justify-center overflow-hidden pointer-events-none`}>
[DB-GPT](https://github.com/csunny/DB-GPT) 是一个开源的以数据库为基础的GPT实验项目使用本地化的GPT大模型与您的数据和环境进行交互无数据泄露风险100% 私密100% 安全。 <div className='w-[108rem] flex-none flex justify-end'>
</ReactMarkdown> <picture>
<Box <source srcSet='/bg1.avif' type='image/avif'></source>
sx={{ <img srcSet='/bg2.png' alt="" className='w-[71.75rem] flex-none max-w-none '/>
'& .ant-collapse': { </picture>
backgroundColor: 'var(--joy-palette-background-level1)', </div>
color: 'var(--joy-palette-neutral-plainColor)' </div>
}, <div className='mx-auto flex h-full max-w-3xl flex-col gap-6 px-5 pt-6 sm:gap-8 xl:max-w-4xl'>
'& .ant-collapse>.ant-collapse-item >.ant-collapse-header, & .ant-collapse .ant-collapse-content': { <div className='lg:my-auto grid gap-8 lg:grid-cols-3'>
color: 'var(--joy-palette-neutral-plainColor)' <div className='lg:col-span-3 lg:mt-12'>
}, <p className='mb-3'>Scenes</p>
'& .ant-collapse .ant-collapse-content>.ant-collapse-content-box': { <div className='grid gap-2 lg:grid-cols-4 lg:gap-5'>
paddingTop: 0 <Button size="md" variant="soft">LLM native dialogue</Button>
} <Button size="md" variant="soft">Default documents</Button>
}} <Button size="md" variant="soft">New documents</Button>
> <Button size="md" variant="soft">Chat with url</Button>
<Collapse
collapsible="header"
defaultActiveKey={['1']}
ghost
bordered
expandIcon={({ isActive }) => (
<div className={`text-2xl cursor-pointer ${isActive ? 'rotate-0' : 'rotate-90'}`}>
<ArrowDropDownIcon />
</div> </div>
)} </div>
expandIconPosition="end" </div>
items={[ <div className='h-60 flex-none'></div>
{ </div>
key: '1', <div className='pointer-events-none absolute inset-x-0 bottom-0 z-0 mx-auto flex w-full max-w-3xl flex-col items-center justify-center px-3.5 py-4 max-md:border-t sm:px-5 md:py-8 xl:max-w-4xl [&>*]:pointer-events-auto'>
label: 'This panel can only be collapsed by clicking text', <form
children: ( style={{
<> maxWidth: '100%',
<Box width: '100%',
className="p-4 border" position: 'relative',
sx={{ display: 'flex',
borderColor: 'var(--joy-palette-background-level2)', marginTop: 'auto',
}} overflow: 'visible',
> background: 'none',
<div className='flex flex-row justify-between items-center'> justifyContent: 'center',
<span>Temperature</span> marginLeft: 'auto',
<Input marginRight: 'auto',
size='sm' }}
type="number" onSubmit={(e) => {
value={temperatureNum / 10} methods.handleSubmit(submit)(e);
onChange={(e) => { }}
setTemperatureNum(Number(e.target.value) * 10);
}}
slotProps={{
input: {
min: 0,
max: 1,
step: 0.1,
},
}}
/>
</div>
<Slider
color="info"
value={temperatureNum}
max={10}
onChange={(e, value) => {
setTemperatureNum(value);
}}
/>
</Box>
<Box
className="p-4 border border-t-0"
sx={{
borderColor: 'var(--joy-palette-background-level2)',
}}
>
<div className='flex flex-row justify-between items-center'>
<span>Maximum output token size</span>
<Input
size="sm"
type="number"
value={tokenSize}
onChange={(e) => {
setTokenSize(Number(e.target.value));
}}
slotProps={{
input: {
min: 0,
max: 1024,
step: 64,
},
}}
/>
</div>
<Slider
color="info"
value={tokenSize}
max={1024}
step={64}
onChange={(e, value) => {
setTokenSize(value);
}}
/>
</Box>
</>
),
},
]}
/>
</Box>
<Box>
<Tabs
className='w-full'
aria-label="Pricing plan"
defaultValue={0}
sx={(theme) => ({
'--Tabs-gap': '0px',
borderRadius: 'unset',
boxShadow: 'sm',
overflow: 'auto',
WebkitBoxShadow: 'none'
})}
> >
<TabList <Input
sx={{ sx={{ width: '100%' }}
'--ListItem-radius': '0px', variant="outlined"
borderRadius: 0, placeholder='Ask anything'
bgcolor: 'background.body', endDecorator={
[`& .${tabClasses.root}`]: { <IconButton type="submit" disabled={isLoading}>
fontWeight: 'lg', <SendRoundedIcon />
flex: 'unset', </IconButton>
position: 'relative', }
[`&.${tabClasses.selected}`]: { {...methods.register('query')}
border: '1px solid var(--joy-palette-background-level2)', />
borderTopLeftRadius: '8px', </form>
borderTopRightRadius: '8px', </div>
}, </>
[`&.${tabClasses.selected}:before`]: {
content: '""',
display: 'block',
position: 'absolute',
bottom: -4,
width: '100%',
height: 6,
bgcolor: mode == 'dark' ? '#000' : 'var(--joy-palette-background-surface)',
},
[`&.${tabClasses.focusVisible}`]: {
outlineOffset: '-3px',
},
},
}}
>
<Tab sx={{ py: 1.5 }}>Documents Chat</Tab>
<Tab>SQL Generation & Diagnostics</Tab>
<Tab>Plugin Mode</Tab>
</TabList>
<TabPanel
value={0}
sx={{
p: 3,
border: '1px solid var(--joy-palette-background-level2)',
borderBottomLeftRadius: '8px',
borderBottomRightRadius: '8px',
}}
>
<RadioGroup
orientation="horizontal"
defaultValue="LLM native dialogue"
name="radio-buttons-group"
className='gap-3 p-3'
sx={{
backgroundColor: 'var(--joy-palette-background-level1)',
'& .MuiRadio-radio': {
borderColor: '#707070'
},
}}
>
<Box className="px-2 py-1 border rounded" sx={{ borderColor: 'var(--joy-palette-background-level2)' }}>
<Radio value="LLM native dialogue" label="LLM native dialogue" variant="outlined"
sx={{
borderColor: 'var(--joy-palette-neutral-outlinedHoverColor)',
}}
/>
</Box>
<Box className="px-2 py-1 border rounded" sx={{ borderColor: 'var(--joy-palette-background-level2)' }}>
<Radio value="Default documents" label="Default documents" variant="outlined" />
</Box>
<Box className="px-2 py-1 border rounded" sx={{ borderColor: 'var(--joy-palette-background-level2)' }}>
<Radio value="New documents" label="New documents" variant="outlined" />
</Box>
<Box className="px-2 py-1 border rounded" sx={{ borderColor: 'var(--joy-palette-background-level2)' }}>
<Radio value="Chat with url" label="Chat with url" variant="outlined" />
</Box>
</RadioGroup>
</TabPanel>
<TabPanel value={1} sx={{ p: 3 }}>
<Typography level="inherit">
Best for professional developers building enterprise or data-rich
applications.
</Typography>
<Typography textColor="primary.400" fontSize="xl3" fontWeight="xl" my={1}>
$15{' '}
<Typography fontSize="sm" textColor="text.secondary" fontWeight="md">
/ dev / month
</Typography>
</Typography>
</TabPanel>
<TabPanel value={2} sx={{ p: 3 }}>
<Typography level="inherit">
The most advanced features for data-rich applications, as well as the
highest priority for support.
</Typography>
<Typography textColor="primary.400" fontSize="xl3" fontWeight="xl" my={1}>
<Typography
fontSize="xl"
borderRadius="sm"
px={0.5}
mr={0.5}
sx={(theme) => ({
...theme.variants.soft.danger,
color: 'danger.400',
verticalAlign: 'text-top',
textDecoration: 'line-through',
})}
>
$49
</Typography>
$37*{' '}
<Typography fontSize="sm" textColor="text.secondary" fontWeight="md">
/ dev / month
</Typography>
</Typography>
</TabPanel>
</Tabs>
</Box>
<ChatBoxComp messages={history} onSubmit={handleChatSubmit}/>
</div>
) )
} }

View File

@ -17,7 +17,7 @@ type Props = {
messages: Message[]; messages: Message[];
onSubmit: (message: string) => Promise<any>; onSubmit: (message: string) => Promise<any>;
messageTemplates?: string[]; messageTemplates?: string[];
initialMessage?: string; initialMessage?: Message;
readOnly?: boolean; readOnly?: boolean;
}; };
@ -63,7 +63,7 @@ const ChatBoxComp = ({
React.useEffect(() => { React.useEffect(() => {
setTimeout(() => { setTimeout(() => {
setFirstMsg( setFirstMsg(
initialMessage ? { from: 'agent', message: initialMessage } : undefined initialMessage ? initialMessage : undefined
); );
}, 0); }, 0);
}, [initialMessage]); }, [initialMessage]);
@ -112,7 +112,7 @@ const ChatBoxComp = ({
whiteSpace: 'pre-wrap', whiteSpace: 'pre-wrap',
}} }}
> >
{firstMsg?.message} {firstMsg?.context}
</Card> </Card>
)} )}
@ -120,17 +120,17 @@ const ChatBoxComp = ({
<Stack <Stack
key={index} key={index}
sx={{ sx={{
mr: each.from === 'agent' ? 'auto' : 'none', mr: each.role === 'ai' ? 'auto' : 'none',
ml: each.from === 'human' ? 'auto' : 'none', ml: each.role === 'human' ? 'auto' : 'none',
}} }}
> >
<Card <Card
size="sm" size="sm"
variant={'outlined'} variant={'outlined'}
className={ className={
each.from === 'agent' ? 'message-agent' : 'message-human' each.role === 'ai' ? 'message-agent' : 'message-human'
} }
color={each.from === 'agent' ? 'primary' : 'neutral'} color={each.role === 'ai' ? 'primary' : 'neutral'}
sx={(theme) => ({ sx={(theme) => ({
px: 2, px: 2,
'ol, ul': { 'ol, ul': {
@ -153,7 +153,7 @@ const ChatBoxComp = ({
})} })}
> >
<ReactMarkdown remarkPlugins={[remarkGfm]} linkTarget={'_blank'}> <ReactMarkdown remarkPlugins={[remarkGfm]} linkTarget={'_blank'}>
{each.message} {each.context}
</ReactMarkdown> </ReactMarkdown>
</Card> </Card>
</Stack> </Stack>

View File

@ -3,42 +3,26 @@ import React, { useMemo, useState } from 'react';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import Link from 'next/link'; import Link from 'next/link';
import Image from 'next/image'; import Image from 'next/image';
import { Box, List, ListItem, ListItemButton, ListItemDecorator, ListItemContent, Typography, Button, useColorScheme } from '@/lib/mui'; import { Box, List, ListItem, ListItemButton, ListItemDecorator, ListItemContent, Typography, Button, useColorScheme, Alert } from '@/lib/mui';
import SmartToyRoundedIcon from '@mui/icons-material/SmartToyRounded'; // Icons import import SmartToyRoundedIcon from '@mui/icons-material/SmartToyRounded'; // Icons import
import StorageRoundedIcon from '@mui/icons-material/StorageRounded'; import StorageRoundedIcon from '@mui/icons-material/StorageRounded';
import DarkModeIcon from '@mui/icons-material/DarkMode'; import DarkModeIcon from '@mui/icons-material/DarkMode';
import WbSunnyIcon from '@mui/icons-material/WbSunny'; import WbSunnyIcon from '@mui/icons-material/WbSunny';
import HomeIcon from '@mui/icons-material/Home';
const mockHistory = [{ import MenuIcon from '@mui/icons-material/Menu';
id: 1, import AddIcon from '@mui/icons-material/Add';
summary: "chat1", import { useQueryDialog } from '@/hooks/useQueryDialogue';
history: [{
from: 'human',
message: 'Hello'
}, {
from: 'agent',
message: 'Hello! How can I assist you today?'
}]
}, {
id: 2,
summary: "天气",
history: [{
from: 'human',
message: '讲个笑话'
}, {
from: 'agent',
message: '当然这是一个经典的笑话xxx'
}]
}];
const LeftSider = () => { const LeftSider = () => {
const pathname = usePathname(); const pathname = usePathname();
const { mode, setMode } = useColorScheme(); const { mode, setMode } = useColorScheme();
const [chatSelect, setChatSelect] = useState(); const [chatSelect, setChatSelect] = useState();
const { dialogueList } = useQueryDialog();
const menus = useMemo(() => { const menus = useMemo(() => {
return [{ return [{
label: 'Home', label: 'Home',
icon: <SmartToyRoundedIcon fontSize="small" />, icon: <HomeIcon fontSize="small" />,
route: '/', route: '/',
active: pathname === '/', active: pathname === '/',
}, { }, {
@ -63,153 +47,173 @@ const LeftSider = () => {
}; };
return ( return (
<Box <>
sx={{ <nav className='flex h-12 items-center justify-between border-b bg-gray-50 px-4 dark:border-gray-800 dark:bg-gray-800/70 md:hidden'>
display: 'flex', <div>
flexDirection: 'column', <MenuIcon />
borderRight: '1px solid',
borderColor: 'divider',
maxHeight: '100vh',
position: 'sticky',
left: '0px',
top: '0px',
overflow: 'hidden',
}}
>
<Box
sx={{
p: 2,
gap: 2,
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<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>
</Box> <span className='truncate px-4'>New Chat</span>
<Box <a href='javascript: void(0)' className='-mr-3 flex h-9 w-9 shrink-0 items-center justify-center'>
sx={{ <AddIcon />
px: 2 </a>
}} </nav>
> <nav className="grid max-h-screen h-full max-md:hidden">
<Button variant="outlined" color="primary" className='w-full'>+ </Button>
</Box>
<Box
sx={{
p: 2,
display: {
xs: 'none',
sm: 'initial',
},
maxHeight: '100%',
overflow: 'auto',
}}
>
<List size="sm" sx={{ '--ListItem-radius': '8px' }}>
<ListItem nested>
<List
size="sm"
aria-labelledby="nav-list-browse"
sx={{
'& .JoyListItemButton-root': { p: '8px' },
}}
>
{mockHistory.map((each, index) => (
<ListItem key={index}>
<ListItemButton
selected={chatSelect === each.id}
variant={chatSelect === each.id ? 'soft' : 'plain'}
onClick={() => {
setChatSelect(each.id);
}}
>
<ListItemContent>{each.summary}</ListItemContent>
</ListItemButton>
</ListItem>
))}
</List>
</ListItem>
</List>
</Box>
<div className='flex flex-col justify-between flex-1'>
<div></div>
<Box <Box
sx={{ sx={{
p: 2, display: 'flex',
borderTop: '1px solid', flexDirection: 'column',
borderRight: '1px solid',
borderColor: 'divider', borderColor: 'divider',
display: { maxHeight: '100vh',
xs: 'none',
sm: 'initial',
},
position: 'sticky', position: 'sticky',
bottom: 0, left: '0px',
zIndex: 100, top: '0px',
background: 'var(--joy-palette-background-body)' overflow: 'hidden',
}} }}
> >
<List size="sm" sx={{ '--ListItem-radius': '8px' }}> <Box
<ListItem nested> sx={{
<List p: 2,
size="sm" gap: 2,
aria-labelledby="nav-list-browse" display: 'flex',
sx={{ flexDirection: 'row',
'& .JoyListItemButton-root': { p: '8px' }, justifyContent: 'space-between',
}} alignItems: 'center',
> }}
{menus.map((each) => ( >
<Link key={each.route} href={each.route}> <div className='flex items-center gap-3'>
<ListItem> <Image
src="/databerry-logo-icon.png"
width="100"
height="100"
className='w-12'
alt="Databerry"
/>
<Typography component="h1" fontWeight="xl">
DB-GPT
</Typography>
</div>
</Box>
<Box
sx={{
px: 2
}}
>
<Button variant="outlined" color="primary" className='w-full'>+ </Button>
</Box>
<Box
sx={{
p: 2,
display: {
xs: 'none',
sm: 'initial',
},
maxHeight: '100%',
overflow: 'auto',
}}
>
<List size="sm" sx={{ '--ListItem-radius': '8px' }}>
<ListItem nested>
<List
size="sm"
aria-labelledby="nav-list-browse"
sx={{
'& .JoyListItemButton-root': { p: '8px' },
}}
>
{dialogueList?.data?.map((each) => (
<ListItem key={each.conv_uid}>
<ListItemButton <ListItemButton
color="neutral" selected={chatSelect === each.conv_uid}
selected={each.active} variant={chatSelect === each.conv_uid ? 'soft' : 'plain'}
variant={each.active ? 'soft' : 'plain'} onClick={() => {
setChatSelect(each.conv_uid);
}}
> >
<ListItemDecorator <ListItemContent>
sx={{ <Link href={`/agents/${each.conv_uid}`}>
color: each.active ? 'inherit' : 'neutral.500', <Typography fontSize={14} noWrap={true}>
}} {each?.user_name || each?.user_input || 'undefined'}
> </Typography>
{each.icon} </Link>
</ListItemDecorator>
<ListItemContent>{each.label}</ListItemContent> </ListItemContent>
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
</Link> ))}
))} </List>
</ListItem>
</List>
</Box>
<div className='flex flex-col justify-between flex-1'>
<div></div>
<Box
sx={{
p: 2,
borderTop: '1px solid',
borderColor: 'divider',
display: {
xs: 'none',
sm: 'initial',
},
position: 'sticky',
bottom: 0,
zIndex: 100,
background: 'var(--joy-palette-background-body)'
}}
>
<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
color="neutral"
selected={each.active}
variant={each.active ? 'soft' : 'plain'}
onClick={() => { setChatSelect(undefined); }}
>
<ListItemDecorator
sx={{
color: each.active ? 'inherit' : 'neutral.500',
}}
>
{each.icon}
</ListItemDecorator>
<ListItemContent>{each.label}</ListItemContent>
</ListItemButton>
</ListItem>
</Link>
))}
</List>
</ListItem>
<ListItem>
<ListItemButton
onClick={handleChangeTheme}
>
<ListItemDecorator>
{mode === 'dark' ? (
<DarkModeIcon fontSize="small"/>
) : (
<WbSunnyIcon fontSize="small"/>
)}
</ListItemDecorator>
<ListItemContent>Theme</ListItemContent>
</ListItemButton>
</ListItem>
</List> </List>
</ListItem> </Box>
<ListItem> </div>
<ListItemButton
onClick={handleChangeTheme}
>
<ListItemDecorator>
{mode === 'dark' ? (
<DarkModeIcon fontSize="small"/>
) : (
<WbSunnyIcon fontSize="small"/>
)}
</ListItemDecorator>
<ListItemContent>Theme</ListItemContent>
</ListItemButton>
</ListItem>
</List>
</Box> </Box>
</div> </nav>
</>
</Box>
) )
}; };

View File

@ -21,22 +21,22 @@ import {
}: Props) => { }: Props) => {
const [state, setState] = useStateReducer({ const [state, setState] = useStateReducer({
history: [{ history: [{
from: 'human', role: 'human',
message: 'hello', context: 'hello',
}, { }, {
from: 'agent', role: 'agent',
message: 'Hello! How can I assist you today?', context: 'Hello! How can I assist you today?',
}] as { from: 'human' | 'agent'; message: string; id?: string }[], }] as { role: 'human' | 'agent'; context: string; id?: string }[],
}); });
const { visitorId } = useVisitorId(); const { visitorId } = useVisitorId();
const handleChatSubmit = async (message: string) => { const handleChatSubmit = async (context: string) => {
if (!message) { if (!context) {
return; return;
} }
const history = [...state.history, { from: 'human', message }]; const history = [...state.history, { role: 'human', context }];
const nextIndex = history.length; const nextIndex = history.length;
setState({ setState({
@ -61,7 +61,7 @@ import {
body: JSON.stringify({ body: JSON.stringify({
...queryBody, ...queryBody,
streaming: true, streaming: true,
query: message, query: context,
visitorId: visitorId, visitorId: visitorId,
channel, channel,
}), }),
@ -112,8 +112,8 @@ import {
history: [ history: [
...history, ...history,
{ {
from: 'agent', role: 'agent',
message: event.data.replace('[ERROR]', ''), context: event.data.replace('[ERROR]', ''),
} as any, } as any,
], ],
}); });
@ -121,9 +121,9 @@ import {
buffer += decodeURIComponent(event.data) as string; buffer += decodeURIComponent(event.data) as string;
const h = [...history]; const h = [...history];
if (h?.[nextIndex]) { if (h?.[nextIndex]) {
h[nextIndex].message = `${buffer}`; h[nextIndex].context = `${buffer}`;
} else { } else {
h.push({ from: 'agent', message: buffer }); h.push({ role: 'agent', context: buffer });
} }
setState({ setState({
history: h as any, history: h as any,
@ -136,7 +136,7 @@ import {
setState({ setState({
history: [ history: [
...history, ...history,
{ from: 'agent', message: answer || '请求出错' as string }, { role: 'agent', context: answer || '请求出错' as string },
] as any, ] as any,
}); });
// if (err instanceof ApiError) { // if (err instanceof ApiError) {

View File

@ -0,0 +1,20 @@
import { useRequest } from 'ahooks';
import { sendGetRequest } from '@/utils/request';
export function useQueryDialog() {
const {
run: queryDialogueList,
data: dialogueList,
loading: loadingDialogList,
refresh: refreshDialogList,
} = useRequest(async () => await sendGetRequest('/v1/chat/dialogue/list'), {
manual: false,
});
return {
queryDialogueList,
dialogueList,
loadingDialogList,
refreshDialogList
};
}

View File

@ -24,6 +24,7 @@
"@types/node": "20.3.1", "@types/node": "20.3.1",
"@types/react": "18.2.14", "@types/react": "18.2.14",
"@types/react-dom": "18.2.6", "@types/react-dom": "18.2.6",
"ahooks": "^3.7.8",
"antd": "^5.6.2", "antd": "^5.6.2",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"axios": "^1.3.4", "axios": "^1.3.4",
@ -31,6 +32,7 @@
"cuid": "^3.0.0", "cuid": "^3.0.0",
"eslint": "8.43.0", "eslint": "8.43.0",
"eslint-config-next": "13.4.7", "eslint-config-next": "13.4.7",
"lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
"next": "13.4.7", "next": "13.4.7",
"next-auth": "^4.20.1", "next-auth": "^4.20.1",
@ -48,6 +50,7 @@
"zod": "^3.19.1" "zod": "^3.19.1"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.14.195",
"@types/nprogress": "^0.2.0" "@types/nprogress": "^0.2.0"
} }
}, },
@ -2244,11 +2247,22 @@
"@types/unist": "*" "@types/unist": "*"
} }
}, },
"node_modules/@types/js-cookie": {
"version": "2.2.7",
"resolved": "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-2.2.7.tgz",
"integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA=="
},
"node_modules/@types/json5": { "node_modules/@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
}, },
"node_modules/@types/lodash": {
"version": "4.14.195",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.195.tgz",
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==",
"dev": true
},
"node_modules/@types/mdast": { "node_modules/@types/mdast": {
"version": "3.0.11", "version": "3.0.11",
"resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.11.tgz", "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.11.tgz",
@ -2443,6 +2457,34 @@
"object-assign": "4.x" "object-assign": "4.x"
} }
}, },
"node_modules/ahooks": {
"version": "3.7.8",
"resolved": "https://registry.npmmirror.com/ahooks/-/ahooks-3.7.8.tgz",
"integrity": "sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"@types/js-cookie": "^2.x.x",
"ahooks-v3-count": "^1.0.0",
"dayjs": "^1.9.1",
"intersection-observer": "^0.12.0",
"js-cookie": "^2.x.x",
"lodash": "^4.17.21",
"resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.0",
"tslib": "^2.4.1"
},
"engines": {
"node": ">=8.0.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/ahooks-v3-count": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz",
"integrity": "sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ=="
},
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
@ -4566,6 +4608,11 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/intersection-observer": {
"version": "0.12.2",
"resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz",
"integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg=="
},
"node_modules/is-arguments": { "node_modules/is-arguments": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz",
@ -4856,6 +4903,11 @@
"resolved": "https://registry.npmmirror.com/jose/-/jose-4.14.4.tgz", "resolved": "https://registry.npmmirror.com/jose/-/jose-4.14.4.tgz",
"integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==" "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g=="
}, },
"node_modules/js-cookie": {
"version": "2.2.1",
"resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-2.2.1.tgz",
"integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ=="
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
@ -5018,7 +5070,7 @@
}, },
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"node_modules/lodash.debounce": { "node_modules/lodash.debounce": {
@ -7308,6 +7360,14 @@
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
} }
}, },
"node_modules/screenfull": {
"version": "5.2.0",
"resolved": "https://registry.npmmirror.com/screenfull/-/screenfull-5.2.0.tgz",
"integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/scroll-into-view-if-needed": { "node_modules/scroll-into-view-if-needed": {
"version": "3.0.10", "version": "3.0.10",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz", "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz",
@ -9888,11 +9948,22 @@
"@types/unist": "*" "@types/unist": "*"
} }
}, },
"@types/js-cookie": {
"version": "2.2.7",
"resolved": "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-2.2.7.tgz",
"integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA=="
},
"@types/json5": { "@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
}, },
"@types/lodash": {
"version": "4.14.195",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.195.tgz",
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==",
"dev": true
},
"@types/mdast": { "@types/mdast": {
"version": "3.0.11", "version": "3.0.11",
"resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.11.tgz", "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-3.0.11.tgz",
@ -10049,6 +10120,28 @@
"object-assign": "4.x" "object-assign": "4.x"
} }
}, },
"ahooks": {
"version": "3.7.8",
"resolved": "https://registry.npmmirror.com/ahooks/-/ahooks-3.7.8.tgz",
"integrity": "sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA==",
"requires": {
"@babel/runtime": "^7.21.0",
"@types/js-cookie": "^2.x.x",
"ahooks-v3-count": "^1.0.0",
"dayjs": "^1.9.1",
"intersection-observer": "^0.12.0",
"js-cookie": "^2.x.x",
"lodash": "^4.17.21",
"resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.0",
"tslib": "^2.4.1"
}
},
"ahooks-v3-count": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz",
"integrity": "sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ=="
},
"ajv": { "ajv": {
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
@ -11763,6 +11856,11 @@
"side-channel": "^1.0.4" "side-channel": "^1.0.4"
} }
}, },
"intersection-observer": {
"version": "0.12.2",
"resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz",
"integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg=="
},
"is-arguments": { "is-arguments": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.1.1.tgz",
@ -11980,6 +12078,11 @@
"resolved": "https://registry.npmmirror.com/jose/-/jose-4.14.4.tgz", "resolved": "https://registry.npmmirror.com/jose/-/jose-4.14.4.tgz",
"integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==" "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g=="
}, },
"js-cookie": {
"version": "2.2.1",
"resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-2.2.1.tgz",
"integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ=="
},
"js-tokens": { "js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
@ -12106,7 +12209,7 @@
}, },
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"lodash.debounce": { "lodash.debounce": {
@ -13904,6 +14007,11 @@
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
} }
}, },
"screenfull": {
"version": "5.2.0",
"resolved": "https://registry.npmmirror.com/screenfull/-/screenfull-5.2.0.tgz",
"integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA=="
},
"scroll-into-view-if-needed": { "scroll-into-view-if-needed": {
"version": "3.0.10", "version": "3.0.10",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz", "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz",

View File

@ -25,6 +25,7 @@
"@types/node": "20.3.1", "@types/node": "20.3.1",
"@types/react": "18.2.14", "@types/react": "18.2.14",
"@types/react-dom": "18.2.6", "@types/react-dom": "18.2.6",
"ahooks": "^3.7.8",
"antd": "^5.6.2", "antd": "^5.6.2",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.14",
"axios": "^1.3.4", "axios": "^1.3.4",
@ -32,6 +33,7 @@
"cuid": "^3.0.0", "cuid": "^3.0.0",
"eslint": "8.43.0", "eslint": "8.43.0",
"eslint-config-next": "13.4.7", "eslint-config-next": "13.4.7",
"lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
"next": "13.4.7", "next": "13.4.7",
"next-auth": "^4.20.1", "next-auth": "^4.20.1",
@ -49,6 +51,7 @@
"zod": "^3.19.1" "zod": "^3.19.1"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.14.195",
"@types/nprogress": "^0.2.0" "@types/nprogress": "^0.2.0"
} }
} }

BIN
datacenter/public/bg1.avif Normal file

Binary file not shown.

BIN
datacenter/public/bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

View File

@ -14,5 +14,6 @@ module.exports = {
} }
}, },
}, },
darkMode: 'class',
plugins: [], plugins: [],
} }

View File

@ -1,7 +1,7 @@
import { NextApiRequest, NextPage } from 'next/types'; import { NextApiRequest, NextPage } from 'next/types';
import { Session } from 'next-auth'; import { Session } from 'next-auth';
export type Message = { from: 'human' | 'agent'; message: string; createdAt?: Date }; export type Message = { role: 'human' | 'ai'; context: string; createdAt?: Date };
export type AppNextApiRequest = NextApiRequest & { export type AppNextApiRequest = NextApiRequest & {
session: Session; session: Session;

View File

@ -1,4 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { isPlainObject } from 'lodash';
axios.defaults.baseURL = 'http://30.183.153.244:5000'; axios.defaults.baseURL = 'http://30.183.153.244:5000';
@ -9,6 +10,24 @@ axios.interceptors.response.use(
err => Promise.reject(err) err => Promise.reject(err)
); );
const DEFAULT_HEADERS = {
'content-type': 'application/json',
};
// body 字段 trim
const sanitizeBody = (obj: Record<string, any>): string => {
// simple shallow copy to avoid changing original obj
if (!isPlainObject(obj)) return JSON.stringify(obj);
const resObj = { ...obj };
for (const key in resObj) {
const val = resObj[key];
if (typeof val === 'string') {
resObj[key] = val.trim();
}
}
return JSON.stringify(resObj);
};
export const sendGetRequest = (url: string, qs?: { [key: string]: any }) => { export const sendGetRequest = (url: string, qs?: { [key: string]: any }) => {
if (qs) { if (qs) {
const str = Object.keys(qs) const str = Object.keys(qs)
@ -19,13 +38,15 @@ export const sendGetRequest = (url: string, qs?: { [key: string]: any }) => {
url += `?${str}`; url += `?${str}`;
} }
} }
axios.get(url, { return axios.get(url, {
headers: { headers: DEFAULT_HEADERS
"Content-Type": 'text/plain' }).then(res => res).catch(err => Promise.reject(err));
}
}).then(res => {
console.log(res, 'res');
}).catch(err => {
console.log(err, 'err');
})
} }
export const sendPostRequest = (url: string, body?: any) => {
const reqBody = sanitizeBody(body);
return axios.post(url, {
body: reqBody,
headers: DEFAULT_HEADERS
}).then(res => res).catch(err => Promise.reject(err));
}