mirror of
https://github.com/tencentmusic/supersonic.git
synced 2026-04-18 04:14:21 +08:00
Integrate Chat and Copilot into chat-sdk, and add SQL parse display (#166)
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import LeftAvatar from '../CopilotAvatar';
|
||||
import Message from '../Message';
|
||||
import styles from './style.module.less';
|
||||
import { AgentType } from '../../type';
|
||||
import { isMobile } from '../../../utils/utils';
|
||||
|
||||
type Props = {
|
||||
currentAgent?: AgentType;
|
||||
onSendMsg: (value: string) => void;
|
||||
};
|
||||
|
||||
const AgentTip: React.FC<Props> = ({ currentAgent, onSendMsg }) => {
|
||||
if (!currentAgent) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={styles.agentTip}>
|
||||
{!isMobile && <LeftAvatar />}
|
||||
<Message position="left" bubbleClassName={styles.agentTipMsg}>
|
||||
<div className={styles.title}>
|
||||
您好,智能助理【{currentAgent.name}
|
||||
】将与您对话,试着问:
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.examples}>
|
||||
{currentAgent.examples?.length > 0 ? (
|
||||
currentAgent.examples.map(example => (
|
||||
<div
|
||||
key={example}
|
||||
className={styles.example}
|
||||
onClick={() => {
|
||||
onSendMsg(example);
|
||||
}}
|
||||
>
|
||||
“{example}”
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className={styles.example}>{currentAgent.description}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Message>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentTip;
|
||||
@@ -0,0 +1,44 @@
|
||||
.agentTip {
|
||||
display: flex;
|
||||
|
||||
.agentTipMsg {
|
||||
padding: 12px 20px 20px !important;
|
||||
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
column-gap: 14px;
|
||||
|
||||
.topBar {
|
||||
.tip {
|
||||
margin-top: 2px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.examples {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 13px;
|
||||
row-gap: 8px;
|
||||
|
||||
.example {
|
||||
color: var(--chat-blue);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&.fullscreen {
|
||||
flex: none;
|
||||
width: 280px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Form, Input, Modal } from 'antd';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { updateConversationName } from '../../service';
|
||||
import type { ConversationDetailType } from '../../type';
|
||||
import { CHAT_TITLE } from '../../constants';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
type Props = {
|
||||
visible: boolean;
|
||||
editConversation?: ConversationDetailType;
|
||||
onClose: () => void;
|
||||
onFinish: (conversationName: string) => void;
|
||||
};
|
||||
|
||||
const layout = {
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 18 },
|
||||
};
|
||||
|
||||
const ConversationModal: React.FC<Props> = ({ visible, editConversation, onClose, onFinish }) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const conversationNameInputRef = useRef<any>();
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
form.setFieldsValue({ conversationName: editConversation!.chatName });
|
||||
setTimeout(() => {
|
||||
conversationNameInputRef.current.focus({
|
||||
cursor: 'all',
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const onConfirm = async () => {
|
||||
const values = await form.validateFields();
|
||||
setLoading(true);
|
||||
await updateConversationName(values.conversationName, editConversation!.chatId);
|
||||
setLoading(false);
|
||||
onFinish(values.conversationName);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`修改${CHAT_TITLE}问答名称`}
|
||||
open={visible}
|
||||
onCancel={onClose}
|
||||
onOk={onConfirm}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form {...layout} form={form}>
|
||||
<FormItem name="conversationName" label="名称" rules={[{ required: true }]}>
|
||||
<Input
|
||||
placeholder={`请输入${CHAT_TITLE}问答名称`}
|
||||
ref={conversationNameInputRef}
|
||||
onPressEnter={onConfirm}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConversationModal;
|
||||
@@ -0,0 +1,8 @@
|
||||
import IconFont from '../../../components/IconFont';
|
||||
import styles from './style.module.less';
|
||||
|
||||
const CopilotAvatar = () => {
|
||||
return <IconFont type="icon-zhinengsuanfa" className={styles.leftAvatar} />;
|
||||
};
|
||||
|
||||
export default CopilotAvatar;
|
||||
@@ -0,0 +1,13 @@
|
||||
.leftAvatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 6px;
|
||||
margin-right: 6px;
|
||||
color: var(--chat-blue);
|
||||
font-size: 40px;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
38
webapp/packages/chat-sdk/src/Chat/components/Message.tsx
Normal file
38
webapp/packages/chat-sdk/src/Chat/components/Message.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import classNames from 'classnames';
|
||||
import styles from './style.module.less';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type Props = {
|
||||
position: 'left' | 'right';
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
bubbleClassName?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
const Message: React.FC<Props> = ({ position, width, height, children, bubbleClassName }) => {
|
||||
const messageClass = classNames(styles.message, {
|
||||
[styles.left]: position === 'left',
|
||||
[styles.right]: position === 'right',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={messageClass} style={{ width }}>
|
||||
<div className={styles.messageContent}>
|
||||
<div className={styles.messageBody}>
|
||||
<div
|
||||
className={`${styles.bubble}${bubbleClassName ? ` ${bubbleClassName}` : ''}`}
|
||||
style={{ height }}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Message;
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import LeftAvatar from '../CopilotAvatar';
|
||||
import Message from '../Message';
|
||||
import styles from './style.module.less';
|
||||
import { queryRecommendQuestions } from '../../service';
|
||||
import { isMobile } from '../../../utils/utils';
|
||||
|
||||
type Props = {
|
||||
onSelectQuestion: (value: string) => void;
|
||||
};
|
||||
|
||||
const RecommendQuestions: React.FC<Props> = ({ onSelectQuestion }) => {
|
||||
const [questions, setQuestions] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const initData = async () => {
|
||||
setLoading(true);
|
||||
const res = await queryRecommendQuestions();
|
||||
setLoading(false);
|
||||
setQuestions(
|
||||
res.data?.reduce((result: any[], item: any) => {
|
||||
result = [
|
||||
...result,
|
||||
...item.recommendedQuestions.slice(0, 20).map((item: any) => item.question),
|
||||
];
|
||||
return result;
|
||||
}, []) || []
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.recommendQuestions}>
|
||||
{!isMobile && <LeftAvatar />}
|
||||
{loading ? (
|
||||
<></>
|
||||
) : questions.length > 0 ? (
|
||||
<Message position="left" bubbleClassName={styles.recommendQuestionsMsg}>
|
||||
<div className={styles.title}>推荐问题:</div>
|
||||
<div className={styles.content}>
|
||||
{questions.map((question, index) => (
|
||||
<div
|
||||
key={`${question}_${index}`}
|
||||
className={styles.question}
|
||||
onClick={() => {
|
||||
onSelectQuestion(question);
|
||||
}}
|
||||
>
|
||||
{question}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Message>
|
||||
) : (
|
||||
<Message position="left">您好,请问有什么我可以帮您吗?</Message>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecommendQuestions;
|
||||
@@ -0,0 +1,36 @@
|
||||
.recommendQuestions {
|
||||
display: flex;
|
||||
|
||||
.recommendQuestionsMsg {
|
||||
padding: 12px 20px 20px !important;
|
||||
|
||||
.title {
|
||||
margin-bottom: 12px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
column-gap: 16px;
|
||||
row-gap: 20px;
|
||||
|
||||
.question {
|
||||
height: 22px;
|
||||
padding: 0 6px;
|
||||
color: var(--text-color);
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
background-color: #f4f4f4;
|
||||
border-radius: 11px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--chat-blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
webapp/packages/chat-sdk/src/Chat/components/Text.tsx
Normal file
29
webapp/packages/chat-sdk/src/Chat/components/Text.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { isMobile } from '../../utils/utils';
|
||||
import { Avatar } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import LeftAvatar from './CopilotAvatar';
|
||||
import Message from './Message';
|
||||
import styles from './style.module.less';
|
||||
|
||||
type Props = {
|
||||
position: 'left' | 'right';
|
||||
data: any;
|
||||
quote?: string;
|
||||
};
|
||||
|
||||
const Text: React.FC<Props> = ({ position, data, quote }) => {
|
||||
const textWrapperClass = classNames(styles.textWrapper, {
|
||||
[styles.rightTextWrapper]: position === 'right',
|
||||
});
|
||||
return (
|
||||
<div className={textWrapperClass}>
|
||||
{!isMobile && position === 'left' && <LeftAvatar />}
|
||||
<Message position={position} bubbleClassName={styles.textBubble}>
|
||||
{position === 'right' && quote && <div className={styles.quote}>{quote}</div>}
|
||||
<div className={styles.text}>{data}</div>
|
||||
</Message>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Text;
|
||||
311
webapp/packages/chat-sdk/src/Chat/components/style.module.less
Normal file
311
webapp/packages/chat-sdk/src/Chat/components/style.module.less
Normal file
@@ -0,0 +1,311 @@
|
||||
.message {
|
||||
.messageTitleBar {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 6px;
|
||||
column-gap: 10px;
|
||||
|
||||
.modelName {
|
||||
margin-left: 4px;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.messageTopBar {
|
||||
position: relative;
|
||||
max-width: 80%;
|
||||
overflow: hidden;
|
||||
color: var(--text-color-third);
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
.messageContent {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
.messageBody {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
box-sizing: border-box;
|
||||
min-width: 1px;
|
||||
max-width: 100%;
|
||||
padding: 8px 16px 10px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12);
|
||||
|
||||
.text {
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.textMsg {
|
||||
padding: 12px 0 5px;
|
||||
}
|
||||
|
||||
.topBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
padding: 4px 0 8px;
|
||||
overflow-x: auto;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.03);
|
||||
|
||||
.messageTitleWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.messageTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
.messageContent {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.bubble {
|
||||
float: right;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 16px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
background: linear-gradient(81.62deg, #2870ea 8.72%, var(--chat-blue) 85.01%);
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12);
|
||||
|
||||
.text {
|
||||
&::selection {
|
||||
background: #1ba1f7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textBubble {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.listenerSex {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.listenerArea {
|
||||
padding-top: 24px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.typing {
|
||||
width: 100%;
|
||||
padding: 0 5px;
|
||||
|
||||
:global {
|
||||
.ant-spin-dot {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messageEntityName {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.messageAvatar {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.dataHolder {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
margin-left: 20px;
|
||||
color: var(--text-color-third);
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
|
||||
.subTitleValue {
|
||||
margin-left: 6px;
|
||||
color: var(--text-color);
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatarPopover {
|
||||
:global {
|
||||
.ant-popover-inner-content {
|
||||
padding: 3px 4px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.moreOption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
color: var(--text-color-fourth);
|
||||
font-size: 12px;
|
||||
|
||||
.selectOthers {
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.indicators {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 12px;
|
||||
column-gap: 12px;
|
||||
|
||||
.indicator {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contentName {
|
||||
max-width: 350px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.aggregatorIndicator {
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.entityId {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 12px;
|
||||
column-gap: 4px;
|
||||
|
||||
.idTitle {
|
||||
color: var(--text-color-fourth);
|
||||
font-size: 12px;
|
||||
}
|
||||
.idValue {
|
||||
color: var(--text-color-fourth);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.typingBubble {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.quote {
|
||||
margin-bottom: 4px;
|
||||
padding: 0 4px 0 6px;
|
||||
color: var(--border-color-base);
|
||||
font-size: 13px;
|
||||
border-left: 4px solid var(--border-color-base);
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
}
|
||||
|
||||
.filterSection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-color-secondary);
|
||||
font-weight: normal;
|
||||
font-size: 13px;
|
||||
.filterItem {
|
||||
padding: 2px 12px;
|
||||
color: var(--text-color-secondary);
|
||||
background-color: #edf2f2;
|
||||
border-radius: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.noPermissionTip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-left: 6px;
|
||||
color: var(--text-color-third);
|
||||
}
|
||||
|
||||
.infoBar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
column-gap: 20px;
|
||||
}
|
||||
|
||||
.mainEntityInfo {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
column-gap: 20px;
|
||||
|
||||
.infoItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.infoName {
|
||||
color: var(--text-color-fourth);
|
||||
}
|
||||
|
||||
.infoValue {
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.textWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.rightTextWrapper {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.rightAvatar {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user