Merge branch 'new-page-framework' into dev_ty_06_end

This commit is contained in:
tuyang.yhj 2023-06-29 10:28:17 +08:00
commit 16655e5f13
10 changed files with 206 additions and 100 deletions

View File

@ -11,14 +11,27 @@ const AgentPage = (props) => {
ready: !!props.params?.agentId
});
const { handleChatSubmit, history } = useAgentChat({
queryAgentURL: `/v1/chat/completions`,
queryBody: {}
const { history, handleChatSubmit } = useAgentChat({
queryAgentURL: `http://30.183.154.8:5000/v1/chat/completions`,
queryBody: {
conv_uid: props.params?.agentId,
chat_mode: 'chat_normal'
},
initHistory: historyList?.data
});
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}/>
<ChatBoxComp
initialMessage={historyList?.data ? (historyList?.data?.length <= 0 ? props.searchParams?.initMessage : undefined) : undefined}
clearIntialMessage={() => {
const searchParams = new URLSearchParams(window.location.search);
searchParams.delete('initMessage');
window.history.replaceState(null, null, `?${searchParams.toString()}`);
}}
messages={history || []}
onSubmit={handleChatSubmit}
/>
</div>
)
}

View File

@ -10,6 +10,7 @@ import {
Box,
Stack,
Input,
Textarea,
Chip,
styled
} from '@/lib/mui'
@ -61,6 +62,8 @@ const Documents = () => {
const [documents, setDocuments] = useState<any>([])
const [webPageUrl, setWebPageUrl] = useState<string>('')
const [documentName, setDocumentName] = useState<any>('')
const [textSource, setTextSource] = useState<string>('');
const [text, setText] = useState<string>('');
const [originFileObj, setOriginFileObj] = useState<any>(null)
const props: UploadProps = {
name: 'file',
@ -264,7 +267,6 @@ const Documents = () => {
onChange={(e: any) => setDocumentName(e.target.value)}
sx={{ marginBottom: '20px' }}
/>
Web Page URL:
{documentType === 'webPage' ? (
<>
Web Page URL:
@ -293,7 +295,20 @@ const Documents = () => {
</Dragger>
</>
) : (
<></>
<>
Text Source(Optional):
<Input
placeholder="Please input the text source"
onChange={(e: any) => setTextSource(e.target.value)}
sx={{ marginBottom: '20px' }}
/>
Text:
<Textarea
onChange={(e: any) => setText(e.target.value)}
minRows={4}
sx={{ marginBottom: '20px' }}
/>
</>
)}
</Box>
<Button
@ -379,6 +394,47 @@ const Documents = () => {
} else {
message.error(data.err_msg || 'failed')
}
} else {
if (text === '') {
message.error('Please input the text')
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,
source: textSource,
content: text,
doc_type: 'TEXT'
})
}
)
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')
}
}
}}
>

View File

@ -13,6 +13,7 @@ import {
Stack,
Box,
Input,
Textarea,
styled
} from '@/lib/mui'
@ -62,6 +63,8 @@ const Index = () => {
const [knowledgeSpaceName, setKnowledgeSpaceName] = useState<string>('')
const [webPageUrl, setWebPageUrl] = useState<string>('')
const [documentName, setDocumentName] = useState<any>('')
const [textSource, setTextSource] = useState<string>('');
const [text, setText] = useState<string>('');
const [originFileObj, setOriginFileObj] = useState<any>(null)
const props: UploadProps = {
name: 'file',
@ -304,7 +307,20 @@ const Index = () => {
</Dragger>
</>
) : (
<></>
<>
Text Source(Optional):
<Input
placeholder="Please input the text source"
onChange={(e: any) => setTextSource(e.target.value)}
sx={{ marginBottom: '20px' }}
/>
Text:
<Textarea
onChange={(e: any) => setText(e.target.value)}
minRows={4}
sx={{ marginBottom: '20px' }}
/>
</>
)}
</Box>
<Button
@ -362,6 +378,33 @@ const Index = () => {
} else {
message.error(data.err_msg || 'failed')
}
} else {
if (text === '') {
message.error('Please input the text')
return
}
const res = await fetch(
`http://localhost:8000/knowledge/${knowledgeSpaceName}/document/add`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
doc_name: documentName,
source: textSource,
content: text,
doc_type: 'TEXT'
})
}
)
const data = await res.json()
if (data.success) {
message.success('success')
setIsAddKnowledgeSpaceModalShow(false)
} else {
message.error(data.err_msg || 'failed')
}
}
}}
>

View File

@ -5,12 +5,12 @@ import LeftSider from '@/components/leftSider';
import { CssVarsProvider, ThemeProvider } from '@mui/joy/styles';
import { joyTheme } from './defaultTheme';
import TopProgressBar from '@/components/topProgressBar';
export default function RootLayout({
function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className="h-full font-sans">
<body className={`h-full font-sans`}>
@ -31,3 +31,5 @@ export default function RootLayout({
</html>
)
}
export default RootLayout;

View File

@ -28,8 +28,7 @@ export default function Home() {
chat_mode: 'chat_normal'
});
if (res?.success && res?.data?.conv_uid) {
// router.push(`/agents/${res?.data?.conv_uid}?newMessage=${query}`);
// await refreshDialogList();
router.push(`/agents/${res?.data?.conv_uid}?initMessage=${query}`);
}
} catch (err) {
} finally {

View File

@ -17,8 +17,9 @@ type Props = {
messages: Message[];
onSubmit: (message: string) => Promise<any>;
messageTemplates?: string[];
initialMessage?: Message;
initialMessage?: string;
readOnly?: boolean;
clearIntialMessage?: () => void;
};
const Schema = z.object({ query: z.string().min(1) });
@ -29,6 +30,7 @@ const ChatBoxComp = ({
messageTemplates,
initialMessage,
readOnly,
clearIntialMessage
}: Props) => {
const scrollableRef = React.useRef<HTMLDivElement>(null);
const [isLoading, setIsLoading] = useState(false);
@ -52,6 +54,11 @@ const ChatBoxComp = ({
}
};
const handleInitMessage = async () => {
await submit({ query: (initialMessage as string) });
clearIntialMessage?.();
}
React.useEffect(() => {
if (!scrollableRef.current) {
return;
@ -61,11 +68,9 @@ const ChatBoxComp = ({
}, [messages?.length]);
React.useEffect(() => {
setTimeout(() => {
setFirstMsg(
initialMessage ? initialMessage : undefined
);
}, 0);
if (initialMessage && messages.length <= 0) {
handleInitMessage();
}
}, [initialMessage]);
return (
@ -116,7 +121,7 @@ const ChatBoxComp = ({
</Card>
)}
{messages.map((each, index) => (
{messages.filter(item => ['ai', 'human'].includes(item.role)).map((each, index) => (
<Stack
key={index}
sx={{

View File

@ -1,14 +1,13 @@
"use client";
import React, { useMemo, useState } from 'react';
import { usePathname } from 'next/navigation';
import React, { useEffect, useMemo, useState } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import Image from 'next/image';
import { Box, List, ListItem, ListItemButton, ListItemDecorator, ListItemContent, Typography, Button, useColorScheme, Alert } from '@/lib/mui';
import { Box, List, ListItem, ListItemButton, ListItemDecorator, ListItemContent, Typography, Button, useColorScheme } from '@/lib/mui';
import SmartToyRoundedIcon from '@mui/icons-material/SmartToyRounded'; // Icons import
import StorageRoundedIcon from '@mui/icons-material/StorageRounded';
import DarkModeIcon from '@mui/icons-material/DarkMode';
import WbSunnyIcon from '@mui/icons-material/WbSunny';
import HomeIcon from '@mui/icons-material/Home';
import MenuIcon from '@mui/icons-material/Menu';
import AddIcon from '@mui/icons-material/Add';
import { useQueryDialog } from '@/hooks/useQueryDialogue';
@ -16,16 +15,10 @@ import { useQueryDialog } from '@/hooks/useQueryDialogue';
const LeftSider = () => {
const pathname = usePathname();
const { mode, setMode } = useColorScheme();
const [chatSelect, setChatSelect] = useState();
const { dialogueList } = useQueryDialog();
const menus = useMemo(() => {
return [{
label: 'Home',
icon: <HomeIcon fontSize="small" />,
route: '/',
active: pathname === '/',
}, {
label: 'Agents',
icon: <SmartToyRoundedIcon fontSize="small" />,
route: '/agents',
@ -53,7 +46,7 @@ const LeftSider = () => {
<MenuIcon />
</div>
<span className='truncate px-4'>New Chat</span>
<a href='javascript: void(0)' className='-mr-3 flex h-9 w-9 shrink-0 items-center justify-center'>
<a href='' className='-mr-3 flex h-9 w-9 shrink-0 items-center justify-center'>
<AddIcon />
</a>
</nav>
@ -99,7 +92,9 @@ const LeftSider = () => {
px: 2
}}
>
<Button variant="outlined" color="primary" className='w-full'>+ </Button>
<Link href={`/`}>
<Button variant="outlined" color="primary" className='w-full'>+ </Button>
</Link>
</Box>
<Box
sx={{
@ -121,26 +116,25 @@ const LeftSider = () => {
'& .JoyListItemButton-root': { p: '8px' },
}}
>
{dialogueList?.data?.map((each) => (
<ListItem key={each.conv_uid}>
<ListItemButton
selected={chatSelect === each.conv_uid}
variant={chatSelect === each.conv_uid ? 'soft' : 'plain'}
onClick={() => {
setChatSelect(each.conv_uid);
}}
>
<ListItemContent>
<Link href={`/agents/${each.conv_uid}`}>
<Typography fontSize={14} noWrap={true}>
{each?.user_name || each?.user_input || 'undefined'}
</Typography>
</Link>
</ListItemContent>
</ListItemButton>
</ListItem>
))}
{dialogueList?.data?.map((each) => {
const isSelect = pathname === `/agents/${each.conv_uid}`;
return (
<ListItem key={each.conv_uid}>
<ListItemButton
selected={isSelect}
variant={isSelect ? 'soft' : 'plain'}
>
<ListItemContent>
<Link href={`/agents/${each.conv_uid}`}>
<Typography fontSize={14} noWrap={true}>
{each?.user_name || each?.user_input || 'undefined'}
</Typography>
</Link>
</ListItemContent>
</ListItemButton>
</ListItem>
)
})}
</List>
</ListItem>
</List>
@ -178,7 +172,6 @@ const LeftSider = () => {
color="neutral"
selected={each.active}
variant={each.active ? 'soft' : 'plain'}
onClick={() => { setChatSelect(undefined); }}
>
<ListItemDecorator
sx={{

View File

@ -2,35 +2,31 @@ import {
EventStreamContentType,
fetchEventSource,
} from '@microsoft/fetch-event-source';
import { ApiError, ApiErrorType } from '@/utils/api-error';
import useStateReducer from './useStateReducer';
import useVisitorId from './useVisitorId';
import { Message } from '@/types';
import { useEffect } from 'react';
type Props = {
queryAgentURL: string;
queryHistoryURL?: string;
channel?: "dashboard" | "website" | "slack" | "crisp";
queryBody?: any;
initHistory: Message[];
};
const useAgentChat = ({
queryAgentURL,
queryHistoryURL,
channel,
queryBody,
initHistory
}: Props) => {
const [state, setState] = useStateReducer({
history: [{
role: 'human',
context: 'hello',
}, {
role: 'agent',
context: 'Hello! How can I assist you today?',
}] as { role: 'human' | 'agent'; context: string; id?: string }[],
history: (initHistory || []) as { role: 'human' | 'ai'; context: string; id?: string }[],
});
const { visitorId } = useVisitorId();
useEffect(() => {
if (initHistory) setState({ history: initHistory });
}, [initHistory]);
const handleChatSubmit = async (context: string) => {
if (!context) {
return;
@ -50,9 +46,6 @@ import {
const ctrl = new AbortController();
let buffer = '';
class RetriableError extends Error {}
class FatalError extends Error {}
await fetchEventSource(queryAgentURL, {
method: 'POST',
headers: {
@ -60,9 +53,7 @@ import {
},
body: JSON.stringify({
...queryBody,
streaming: true,
query: context,
visitorId: visitorId,
user_input: context,
channel,
}),
signal: ctrl.signal,
@ -79,64 +70,59 @@ import {
response.status !== 429
) {
if (response.status === 402) {
throw new ApiError(ApiErrorType.USAGE_LIMIT);
//throw new ApiError(ApiErrorType.USAGE_LIMIT);
}
throw new FatalError();
// client-side errors are usually non-retriable:
//throw new FatalError();
} else {
throw new RetriableError();
//throw new RetriableError();
}
},
onclose() {
throw new RetriableError();
// if the server closes the connection unexpectedly, retry:
console.log('onclose');
//throw new RetriableError();
},
onerror(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) => {
event.data = event.data.replaceAll('\\n', '\n');
console.log(event, 'event');
if (event.data === '[DONE]') {
ctrl.abort();
} else if (event.data?.startsWith('[ERROR]')) {
ctrl.abort();
setState({
history: [
...history,
{
role: 'agent',
role: 'ai',
context: event.data.replace('[ERROR]', ''),
} as any,
],
});
} else {
buffer += decodeURIComponent(event.data) as string;
const h = [...history];
if (h?.[nextIndex]) {
h[nextIndex].context = `${buffer}`;
} else {
h.push({ role: 'agent', context: buffer });
if (event.data) {
if (h?.[nextIndex]) {
h[nextIndex].context = `${event.data}`;
} else {
h.push({ role: 'ai', context: event.data });
}
setState({
history: h as any,
});
}
setState({
history: h as any,
});
}
},
});
} catch (err) {
console.log('err', err);
setState({
history: [
...history,
{ role: 'agent', context: answer || '请求出错' as string },
{ role: 'ai', context: answer || '请求出错' as string },
] as any,
});
// if (err instanceof ApiError) {
@ -156,13 +142,12 @@ import {
// setState({
// history: [
// ...history,
// { from: 'agent', message: answer as string },
// { from: 'ai', message: answer as string },
// ] as any,
// });
// }
}
};
return {
handleChatSubmit,
history: state.history,

View File

@ -0,0 +1,10 @@
import { useState } from 'react';
export const useNewChat = () => {
const [message, setMessage] = useState<string | undefined>("hello");
return {
message,
setMessage
};
}

View File

@ -1,7 +1,7 @@
import axios from 'axios';
import { isPlainObject } from 'lodash';
axios.defaults.baseURL = 'http://30.183.153.244:5000';
axios.defaults.baseURL = 'http://30.183.154.8:5000';
axios.defaults.timeout = 10000;