Native data AI application framework based on AWEL+AGENT (#1152)

Co-authored-by: Fangyin Cheng <staneyffer@gmail.com>
Co-authored-by: lcx01800250 <lcx01800250@alibaba-inc.com>
Co-authored-by: licunxing <864255598@qq.com>
Co-authored-by: Aralhi <xiaoping0501@gmail.com>
Co-authored-by: xuyuan23 <643854343@qq.com>
Co-authored-by: aries_ckt <916701291@qq.com>
Co-authored-by: hzh97 <2976151305@qq.com>
This commit is contained in:
明天
2024-02-07 17:43:27 +08:00
committed by GitHub
parent dbb9ac83b1
commit d5afa6e206
328 changed files with 22606 additions and 3282 deletions

View File

@@ -0,0 +1,112 @@
import useChat from '@/hooks/use-chat';
import CompletionInput from './completion-input';
import { useCallback, useState } from 'react';
import { IChatDialogueMessageSchema, IChatDialogueSchema } from '@/types/chat';
import AgentContent from '../chat/agent-content';
import { renderModelIcon } from '../chat/header/model-selector';
import MyEmpty from './MyEmpty';
import { CaretLeftOutlined } from '@ant-design/icons';
import classNames from 'classnames';
import { useRequest } from 'ahooks';
import { apiInterceptors, newDialogue } from '@/client/api';
import ChatContent from '../chat/chat-content';
interface Props {
title?: string;
completionApi?: string;
chatMode: IChatDialogueSchema['chat_mode'];
chatParams?: {
select_param?: string;
} & Record<string, string>;
model?: string;
}
function ChatDialog({ title, chatMode, completionApi, chatParams, model = '' }: Props) {
const chat = useChat({ queryAgentURL: completionApi });
const [loading, setLoading] = useState(false);
const [list, setList] = useState<IChatDialogueMessageSchema[]>([]);
const [open, setOpen] = useState(false);
const { data } = useRequest(
async () => {
const [, res] = await apiInterceptors(newDialogue({ chat_mode: chatMode }));
return res;
},
{
ready: !!chatMode,
},
);
const handleChat = useCallback(
(content: string) => {
if (!data) return;
return new Promise<void>((resolve) => {
const tempList: IChatDialogueMessageSchema[] = [
...list,
{ role: 'human', context: content, model_name: model, order: 0, time_stamp: 0 },
{ role: 'view', context: '', model_name: model, order: 0, time_stamp: 0 },
];
const index = tempList.length - 1;
setList([...tempList]);
setLoading(true);
chat({
chatId: data?.conv_uid,
data: { ...chatParams, chat_mode: chatMode, model_name: model, user_input: content },
onMessage: (message) => {
tempList[index].context = message;
setList([...tempList]);
},
onDone: () => {
resolve();
},
onClose: () => {
resolve();
},
onError: (message) => {
tempList[index].context = message;
setList([...tempList]);
resolve();
},
}).finally(() => {
setLoading(false);
});
});
},
[chat, list, data?.conv_uid],
);
return (
<div
className={classNames(
'fixed top-0 right-0 w-[30rem] h-screen flex flex-col bg-white dark:bg-theme-dark-container shadow-[-5px_0_40px_-4px_rgba(100,100,100,.1)] transition-transform duration-300',
{
'translate-x-0': open,
'translate-x-full': !open,
},
)}
>
{title && <div className="p-4 border-b border-solid border-gray-100">{title}</div>}
<div className="flex-1 overflow-y-auto px-2">
{list.map((item, index) => (
<>{chatParams?.chat_mode === 'chat_agent' ? <AgentContent key={index} content={item} /> : <ChatContent key={index} content={item} />}</>
))}
{!list.length && <MyEmpty description="" />}
</div>
<div className="flex w-full p-4 border-t border-solid border-gray-100 items-center">
{model && <div className="mr-2 flex">{renderModelIcon(model)}</div>}
<CompletionInput loading={loading} onSubmit={handleChat} />
</div>
<div
className="flex items-center justify-center rounded-tl rounded-bl cursor-pointer w-5 h-11 absolute top-[50%] -left-5 -translate-y-[50%] bg-white"
onClick={() => {
setOpen(!open);
}}
>
<CaretLeftOutlined />
</div>
</div>
);
}
export default ChatDialog;

View File

@@ -0,0 +1,135 @@
import React, { HtmlHTMLAttributes, PropsWithChildren, ReactNode, memo, useCallback, useMemo } from 'react';
import { Tag, TagProps, Tooltip } from 'antd';
import classNames from 'classnames';
import Image from 'next/image';
interface Props {
title: string;
desc?: string;
disabled?: boolean;
tags?: (
| string
| {
text: ReactNode;
/** @default false */
border?: boolean;
/** @default default */
color?: TagProps['color'];
}
)[];
operations?: {
children: ReactNode;
label?: string;
onClick?: () => void;
}[];
icon?: ReactNode;
iconBorder?: boolean;
onClick?: () => void;
}
function GPTCard({
icon,
iconBorder = true,
title,
desc,
tags,
children,
disabled,
operations,
className,
...props
}: PropsWithChildren<HtmlHTMLAttributes<HTMLDivElement> & Props>) {
const iconNode = useMemo(() => {
if (!icon) return null;
if (typeof icon === 'string') {
return (
<Image
className={classNames('w-11 h-11 rounded-full mr-4 object-contain bg-white', {
'border border-gray-200': iconBorder,
})}
width={44}
height={44}
src={icon}
alt={title}
/>
);
}
return icon;
}, [icon]);
const tagNode = useMemo(() => {
if (!tags || !tags.length) return null;
return (
<div className="flex items-center mt-1 flex-wrap">
{tags.map((tag, index) => {
if (typeof tag === 'string') {
return (
<Tag key={index} className="text-xs" bordered={false} color="default">
{tag}
</Tag>
);
}
return (
<Tag key={index} className="text-xs" bordered={tag.border ?? false} color={tag.color}>
{tag.text}
</Tag>
);
})}
</div>
);
}, [tags]);
return (
<div
className={classNames(
'group/card relative flex flex-col w-72 rounded justify-between text-black bg-white shadow-[0_8px_16px_-10px_rgba(100,100,100,.08)] hover:shadow-[0_14px_20px_-10px_rgba(100,100,100,.15)] dark:bg-[#232734] dark:text-white dark:hover:border-white transition-[transfrom_shadow] duration-300 hover:-translate-y-1 min-h-fit',
{
'grayscale cursor-no-drop': disabled,
'cursor-pointer': !disabled && !!props.onClick,
},
className,
)}
{...props}
>
<div className="p-4">
<div className="flex items-center">
{iconNode}
<div className="flex flex-col">
<h2 className="text-sm font-semibold">{title}</h2>
{tagNode}
</div>
</div>
{desc && (
<Tooltip title={desc}>
<p className="mt-2 text-sm text-gray-500 font-normal line-clamp-2">{desc}</p>
</Tooltip>
)}
</div>
<div>
{children}
{operations && !!operations.length && (
<div className="flex flex-wrap items-center justify-center border-t border-solid border-gray-100 dark:border-theme-dark">
{operations.map((item, index) => (
<Tooltip key={`operation-${index}`} title={item.label}>
<div
className="relative flex flex-1 items-center justify-center h-11 text-gray-400 hover:text-blue-500 transition-colors duration-300 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
item.onClick?.();
}}
>
{item.children}
{index < operations.length - 1 && <div className="w-[1px] h-6 absolute top-2 right-0 bg-gray-100 rounded dark:bg-theme-dark" />}
</div>
</Tooltip>
))}
</div>
)}
</div>
</div>
);
}
export default memo(GPTCard);

View File

@@ -0,0 +1,23 @@
import classNames from 'classnames';
import React from 'react';
interface IconWrapperProps {
children: React.ReactNode;
className?: string;
}
// Icon wrapper, with background color and hover color. same width and height
const IconWrapper: React.FC<IconWrapperProps> = ({ children, className }) => {
return (
<div
className={classNames(
'flex justify-center items-center w-8 h-8 rounded-full dark:bg-zinc-700 hover:bg-stone-200 dark:hover:bg-zinc-900',
className,
)}
>
{children}
</div>
);
};
export default IconWrapper;